diff --git a/.drone.env b/.drone.env index 8eb79bdd6f..7a92e2cd66 100644 --- a/.drone.env +++ b/.drone.env @@ -3,5 +3,5 @@ CORE_COMMITID=4ee9352f4df70cb49cdd539f5a6f0275d369605a CORE_BRANCH=master # The test runner source for UI tests -WEB_COMMITID=41c62d459c4e7bc4a04367eb80f45a8c29aa3baa +WEB_COMMITID=4d0b216f0ef6ad9708d414aca60920fdb877e5ab WEB_BRANCH=master diff --git a/.drone.star b/.drone.star index 54021c12d7..5d4687fb5c 100644 --- a/.drone.star +++ b/.drone.star @@ -44,17 +44,34 @@ config = { "modules": [ # if you add a module here please also add it to the root level Makefile "extensions/accounts", + "extensions/app-provider", + "extensions/app-registry", "extensions/audit", + "extensions/auth-basic", + "extensions/auth-bearer", + "extensions/auth-machine", + "extensions/frontend", + "extensions/gateway", "extensions/glauth", "extensions/graph-explorer", "extensions/graph", + "extensions/group", + "extensions/idm", "extensions/idp", + "extensions/nats", + "extensions/notifications", + "extensions/ocdav", "extensions/ocs", "extensions/proxy", "extensions/settings", - "extensions/storage", + "extensions/sharing", + "extensions/storage-metadata", + "extensions/storage-publiclink", + "extensions/storage-shares", + "extensions/storage-users", "extensions/store", "extensions/thumbnails", + "extensions/user", "extensions/web", "extensions/webdav", "ocis-pkg", @@ -110,12 +127,6 @@ config = { "cron": "nightly", }, }, - "graphApiTests": { - "skip": True, - "earlyFali": False, - "numberOfParts": 10, - "skipExceptParts": [], - }, "rocketchat": { "channel": "ocis-internal", "from_secret": "private_rocketchat", @@ -301,9 +312,6 @@ def testPipelines(ctx): if "skip" not in config["parallelApiTests"] or not config["parallelApiTests"]["skip"]: pipelines += parallelDeployAcceptancePipeline(ctx) - if "skip" not in config["graphApiTests"] or not config["graphApiTests"]["skip"]: - pipelines += graphApiAcceptancePipeline(ctx) - return pipelines def testOcisModule(ctx, module): @@ -573,7 +581,7 @@ def cs3ApiTests(ctx, storage, accounts_hash_difficulty = 4): def coreApiTests(ctx, part_number = 1, number_of_parts = 1, storage = "ocis", accounts_hash_difficulty = 4): early_fail = config["apiTests"]["earlyFail"] if "earlyFail" in config["apiTests"] else False filterTags = "~@skipOnGraph&&~@skipOnOcis&&~@notToImplementOnOCIS&&~@toImplementOnOCIS&&~comments-app-required&&~@federation-app-required&&~@notifications-app-required&&~systemtags-app-required&&~@local_storage&&~@skipOnOcis-%s-Storage&&~@issue-ocis-3023" % ("OC" if storage == "owncloud" else "OCIS") - expectedFailuresFile = "/drone/src/tests/acceptance/expected-failures-graphAPI-on-%s-storage.md" % (storage.upper()) + expectedFailuresFile = "/drone/src/tests/acceptance/expected-failures-API-on-%s-storage.md" % (storage.upper()) return { "kind": "pipeline", @@ -1629,73 +1637,6 @@ def notify(ctx): }, } -def ocisServerWithAccounts(storage, accounts_hash_difficulty = 4, volumes = [], depends_on = []): - environment = { - "GRAPH_IDENTITY_BACKEND": "cs3", - "GRAPH_LDAP_SERVER_WRITE_ENABLED": "false", - "LDAP_URI": "ldaps://0.0.0.0:9126", - "LDAP_INSECURE": "true", - "LDAP_BIND_DN": "cn=admin,dc=ocis,dc=test", - "LDAP_BIND_PASSWORD": "admin", - "LDAP_USER_BASE_DN": "dc=ocis,dc=test", - "LDAP_USER_SCHEMA_ID": "ownclouduuid", - "LDAP_USER_SCHEMA_MAIL": "mail", - "LDAP_USER_SCHEMA_USERNAME": "cn", - "LDAP_USER_OBJECTCLASS": "posixAccount", - "LDAP_GROUP_BASE_DN": "dc=ocis,dc=test", - "LDAP_GROUP_SCHEMA_ID": "cn", - "LDAP_GROUP_SCHEMA_MAIL": "mail", - "LDAP_GROUP_SCHEMA_GROUPNAME": "cn", - "LDAP_GROUP_SCHEMA_MEMBER": "cn", - "LDAP_GROUP_OBJECTCLASS": "posixGroup", - "IDP_LDAP_BIND_DN": "cn=admin,dc=ocis,dc=test", - "LDAP_CACERT": "/root/.ocis/ldap/ldap.crt", - "IDP_LDAP_BIND_PASSWORD": "admin", - "IDP_LDAP_LOGIN_ATTRIBUTE": "uid", - "PROXY_ACCOUNT_BACKEND_TYPE": "accounts", - "OCS_ACCOUNT_BACKEND_TYPE": "accounts", - "OCIS_RUN_EXTENSIONS": "settings,storage-metadata,graph,graph-explorer,ocs,store,thumbnails,web,webdav,frontend,gateway,user,group,auth-basic,auth-bearer,auth-machine,storage-users,storage-shares,storage-publiclink,appprovider,sharing,proxy,idp,nats,accounts,glauth,ocdav", - "OCIS_INSECURE": "true", - "PROXY_ENABLE_BASIC_AUTH": "true", - "IDP_INSECURE": "true", - "OCIS_LOG_LEVEL": "error", - "OCIS_URL": "https://ocis-server:9200", - "ACCOUNTS_DEMO_USERS_AND_GROUPS": True, - "STORAGE_HOME_DRIVER": "%s" % (storage), - "STORAGE_USERS_DRIVER": "%s" % (storage), - "WEB_UI_CONFIG": "/drone/src/tests/config/drone/ocis-config.json", - } - - # Pass in "default" accounts_hash_difficulty to not set this environment variable. - # That will allow OCIS to use whatever its built-in default is. - # Otherwise pass in a value from 4 to about 11 or 12 (default 4, for making regular tests fast) - # The high values cause lots of CPU to be used when hashing passwords, and really slow down the tests. - if (accounts_hash_difficulty != "default"): - environment["ACCOUNTS_HASH_DIFFICULTY"] = accounts_hash_difficulty - - return [ - { - "name": "ocis-server", - "image": OC_CI_ALPINE, - "detach": True, - "environment": environment, - "commands": [ - "ocis/bin/ocis init --insecure true", - "ocis/bin/ocis server", - ], - "volumes": volumes, - "depends_on": depends_on, - }, - { - "name": "wait-for-ocis-server", - "image": OC_CI_ALPINE, - "commands": [ - "curl -k -u admin:admin --fail --retry-connrefused --retry 10 --retry-all-errors 'https://ocis-server:9200/graph/v1.0/users/ddc2004c-0977-11eb-9d3f-a793888cd0f8'", - ], - "depends_on": depends_on, - }, - ] - def ocisServer(storage, accounts_hash_difficulty = 4, volumes = [], depends_on = [], testing_parallel_deploy = False): if not testing_parallel_deploy: user = "0:0" @@ -1763,28 +1704,24 @@ def ocisServer(storage, accounts_hash_difficulty = 4, volumes = [], depends_on = "LDAP_LOGIN_ATTRIBUTES": "uid,mail", # ownCloudSQL storage driver "STORAGE_USERS_DRIVER": "owncloudsql", - "STORAGE_METADATA_DRIVER": "ocis", - "STORAGE_USERS_DRIVER_OWNCLOUDSQL_DATADIR": "/mnt/data/files", - "STORAGE_USERS_DRIVER_OWNCLOUDSQL_UPLOADINFO_DIR": "/tmp", - "STORAGE_USERS_DRIVER_OWNCLOUDSQL_SHARE_FOLDER": "/Shares", - "STORAGE_USERS_DRIVER_OWNCLOUDSQL_LAYOUT": "{{.Username}}", - "STORAGE_USERS_DRIVER_OWNCLOUDSQL_DBUSERNAME": "owncloud", - "STORAGE_USERS_DRIVER_OWNCLOUDSQL_DBPASSWORD": "owncloud", - "STORAGE_USERS_DRIVER_OWNCLOUDSQL_DBHOST": "oc10-db", - "STORAGE_USERS_DRIVER_OWNCLOUDSQL_DBPORT": 3306, - "STORAGE_USERS_DRIVER_OWNCLOUDSQL_DBNAME": "owncloud", - # TODO: redis is not yet supported - "STORAGE_USERS_DRIVER_OWNCLOUDSQL_REDIS_ADDR": "redis:6379", + "STORAGE_USERS_OWNCLOUDSQL_DATADIR": "/mnt/data/files", + "STORAGE_USERS_OWNCLOUDSQL_SHARE_FOLDER": "/Shares", + "STORAGE_USERS_OWNCLOUDSQL_LAYOUT": "{{.Username}}", + "STORAGE_USERS_OWNCLOUDSQL_DB_USERNAME": "owncloud", + "STORAGE_USERS_OWNCLOUDSQL_DB_PASSWORD": "owncloud", + "STORAGE_USERS_OWNCLOUDSQL_DB_HOST": "oc10-db", + "STORAGE_USERS_OWNCLOUDSQL_DB_PORT": 3306, + "STORAGE_USERS_OWNCLOUDSQL_DB_NAME": "owncloud", # ownCloudSQL sharing driver "SHARING_USER_DRIVER": "owncloudsql", - "SHARING_USER_SQL_USERNAME": "owncloud", - "SHARING_USER_SQL_PASSWORD": "owncloud", - "SHARING_USER_SQL_HOST": "oc10-db", - "SHARING_USER_SQL_PORT": 3306, - "SHARING_USER_SQL_NAME": "owncloud", + "SHARING_USER_OWNCLOUDSQL_DB_USERNAME": "owncloud", + "SHARING_USER_OWNCLOUDSQL_DB_PASSWORD": "owncloud", + "SHARING_USER_OWNCLOUDSQL_DB_HOST": "oc10-db", + "SHARING_USER_OWNCLOUDSQL_DB_PORT": 3306, + "SHARING_USER_OWNCLOUDSQL_DB_NAME": "owncloud", # General oCIS config # OCIS_RUN_EXTENSIONS specifies to start all extensions except glauth, idp and accounts. These are replaced by external services - "OCIS_RUN_EXTENSIONS": "settings,storage-metadata,graph,graph-explorer,ocs,store,thumbnails,web,webdav,frontend,gateway,user,group,auth-basic,auth-bearer,auth-machine,storage-users,storage-shares,storage-publiclink,appprovider,sharing,proxy,nats,ocdav", + "OCIS_RUN_EXTENSIONS": "settings,storage-metadata,graph,graph-explorer,ocs,store,thumbnails,web,webdav,frontend,gateway,user,group,auth-basic,auth-bearer,auth-machine,storage-users,storage-shares,storage-publiclink,app-provider,sharing,proxy,nats,ocdav", "OCIS_LOG_LEVEL": "info", "OCIS_URL": OCIS_URL, "OCIS_BASE_DATA_PATH": "/mnt/data/ocis", @@ -2492,74 +2429,6 @@ def parallelDeploymentOC10Server(): }, ] -def graphApiAcceptancePipeline(ctx): - pipelines = [] - - debugParts = config["graphApiTests"]["skipExceptParts"] - debugPartsEnabled = (len(debugParts) != 0) - for runPart in range(1, config["graphApiTests"]["numberOfParts"] + 1): - if (not debugPartsEnabled or (debugPartsEnabled and runPart in debugParts)): - pipelines.append( - graphApiTests(ctx, runPart, config["graphApiTests"]["numberOfParts"]), - ) - - return pipelines - -def graphApiTests(ctx, part_number = 1, number_of_parts = 1): - storage = "ocis" - early_fail = config["graphApiTests"]["earlyFail"] if "earlyFail" in config["graphApiTests"] else False - filterTags = "~@skipOnGraph&&~@skipOnOcis&&~@notToImplementOnOCIS&&~@toImplementOnOCIS&&~comments-app-required&&~@federation-app-required&&~@notifications-app-required&&~systemtags-app-required&&~@local_storage&&~@skipOnOcis-OCIS-Storage&&~@issue-ocis-3023" - expectedFailuresFile = "/drone/src/tests/acceptance/expected-failures-graphAPI-on-OCIS-storage.md" - - return { - "kind": "pipeline", - "type": "docker", - "name": "Graph-Core-API-Tests-%s-storage-%s" % (storage, part_number), - "platform": { - "os": "linux", - "arch": "amd64", - }, - "steps": skipIfUnchanged(ctx, "acceptance-tests") + - restoreBuildArtifactCache(ctx, "ocis-binary-amd64", "ocis/bin/ocis") + - ocisServer() + - cloneCoreRepos() + [ - { - "name": "Graph-oC10ApiTests-%s-storage-%s" % (storage, part_number), - "image": OC_CI_PHP % DEFAULT_PHP_VERSION, - "environment": { - "TEST_WITH_GRAPH_API": "true", - "PATH_TO_OCIS": "/drone/src", - "PATH_TO_CORE": "/srv/app/testrunner", - "TEST_SERVER_URL": "https://ocis-server:9200", - "SKELETON_DIR": "/srv/app/tmp/testing/data/apiSkeleton", - "OCIS_SKELETON_STRATEGY": "upload", - "TEST_OCIS": "true", - "SEND_SCENARIO_LINE_REFERENCES": "true", - "STORAGE_DRIVER": storage, - "BEHAT_FILTER_TAGS": filterTags, - "DIVIDE_INTO_NUM_PARTS": number_of_parts, - "RUN_PART": part_number, - "UPLOAD_DELETE_WAIT_TIME": 0, - "EXPECTED_FAILURES_FILE": expectedFailuresFile, - }, - "commands": [ - "cd /srv/app/testrunner", - "make test-acceptance-api", - ], - "volumes": [stepVolumeOC10Tests], - }, - ] + failEarly(ctx, early_fail), - "depends_on": getPipelineNames([buildOcisBinaryForTesting(ctx)]), - "trigger": { - "ref": [ - "refs/heads/master", - "refs/tags/v*", - "refs/pull/**", - ], - }, - "volumes": [pipelineVolumeOC10Tests], - } - def ldapService(): return [{ "name": "openldap", @@ -2607,6 +2476,7 @@ def copyConfigs(): # ocis proxy config "mkdir -p /etc/ocis", "cp %s/ocis/proxy.yaml /etc/ocis/proxy.yaml" % (PARALLEL_DEPLOY_CONFIG_PATH), + "chown -R 33:33 /etc/ocis", # oc10 configs "mkdir -p /etc/templates", "mkdir -p /etc/pre_server.d", diff --git a/.vscode/launch.json b/.vscode/launch.json index e7629b586f..21b5235837 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -41,7 +41,7 @@ // metadata storage "METADATA_USER_ID": "some-metadata-user-id" // OCIS_RUN_EXTENSIONS allows to start a subset of extensions even in the supervised mode - //"OCIS_RUN_EXTENSIONS": "settings,storage-metadata,glauth,graph,graph-explorer,idp,ocs,store,thumbnails,web,webdav,storage-frontend,storage-gateway,storage-userprovider,storage-groupprovider,storage-authbasic,storage-authbearer,storage-authmachine,storage-users,storage-shares,storage-public-link,storage-appprovider,storage-sharing,accounts,proxy,ocdav", + //"OCIS_RUN_EXTENSIONS": "settings,storage-metadata,glauth,graph,graph-explorer,idp,ocs,store,thumbnails,web,webdav,frontend,gateway,user,group,auth-basic,auth-bearer,storage-authmachine,storage-users,storage-shares,storage-publiclink,app-provider,sharing,accounts,proxy,ocdav", } } ] diff --git a/CHANGELOG.md b/CHANGELOG.md index e4c58665da..a268f420c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,11 @@ The following sections list the changes for unreleased. * Bugfix - Return proper errors when ocs/cloud/users is using the cs3 backend: [#3483](https://github.com/owncloud/ocis/issues/3483) * Bugfix - URL encode the webdav url in the graph API: [#3597](https://github.com/owncloud/ocis/pull/3597) * Change - Load configuration files just from one directory: [#3587](https://github.com/owncloud/ocis/pull/3587) +* Change - Reduce permissions on docker image predeclared volumes: [#3641](https://github.com/owncloud/ocis/pull/3641) * Change - Introduce `ocis init` and remove all default secrets: [#3551](https://github.com/owncloud/ocis/pull/3551) * Change - Reduce drives in graph /me/drives API: [#3629](https://github.com/owncloud/ocis/pull/3629) * Change - Switched default configuration to use libregraph/idm: [#3331](https://github.com/owncloud/ocis/pull/3331) +* Change - Use new space ID util functions: [#3648](https://github.com/owncloud/ocis/pull/3648) * Enhancement - Align service naming: [#3606](https://github.com/owncloud/ocis/pull/3606) * Enhancement - Wrap metadata storage with dedicated reva gateway: [#3602](https://github.com/owncloud/ocis/pull/3602) * Enhancement - Add initial version of the search extensions: [#3635](https://github.com/owncloud/ocis/pull/3635) @@ -20,6 +22,7 @@ The following sections list the changes for unreleased. * Enhancement - Added `share_jail` and `projects` feature flags in spaces capability: [#3626](https://github.com/owncloud/ocis/pull/3626) * Enhancement - Update linkshare capabilities: [#3579](https://github.com/owncloud/ocis/pull/3579) * Enhancement - Update reva to v2.x.x: [#3552](https://github.com/owncloud/ocis/pull/3552) +* Enhancement - Update ownCloud Web to v5.5.0-rc.2: [#6854](https://github.com/owncloud/web/pull/6854) ## Details @@ -58,6 +61,16 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/pull/3587 +* Change - Reduce permissions on docker image predeclared volumes: [#3641](https://github.com/owncloud/ocis/pull/3641) + + We've lowered the permissions on the predeclared volumes of the oCIS docker image from 777 to + 750. + + This change doesn't affect you, unless you use the docker image with the non default uid/guid to + start oCIS (default is 1000:1000). + + https://github.com/owncloud/ocis/pull/3641 + * Change - Introduce `ocis init` and remove all default secrets: [#3551](https://github.com/owncloud/ocis/pull/3551) We've removed all default secrets. This means you can't start oCIS any longer without setting @@ -85,6 +98,13 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/pull/3331 https://github.com/owncloud/ocis/pull/3633 +* Change - Use new space ID util functions: [#3648](https://github.com/owncloud/ocis/pull/3648) + + Changed code to use the new space ID util functions so that everything works with the new spaces + ID format. + + https://github.com/owncloud/ocis/pull/3648 + * Enhancement - Align service naming: [#3606](https://github.com/owncloud/ocis/pull/3606) We now reflect the configured service names when listing them in the ocis runtime @@ -148,6 +168,17 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/pull/3611 https://github.com/owncloud/ocis/pull/3637 https://github.com/owncloud/ocis/pull/3652 + +* Enhancement - Update ownCloud Web to v5.5.0-rc.2: [#6854](https://github.com/owncloud/web/pull/6854) + + Tags: web + + We updated ownCloud Web to v5.5.0-rc.2. Please refer to the changelog (linked) for details on + the web release. + + https://github.com/owncloud/web/pull/6854 + https://github.com/owncloud/ocis/pull/3664 + https://github.com/owncloud/web/releases/tag/v5.5.0-rc.2 # Changelog for [1.20.0] (2022-04-13) The following sections list the changes for 1.20.0. diff --git a/Makefile b/Makefile index 287c567d43..430252af56 100644 --- a/Makefile +++ b/Makefile @@ -17,17 +17,34 @@ L10N_MODULES := $(shell find . -path '*.tx*' -name 'config' | sed 's|/[^/]*$$||' # if you add a module here please also add it to the .drone.star file OCIS_MODULES = \ extensions/accounts \ + extensions/app-provider \ + extensions/app-registry \ extensions/audit \ + extensions/auth-basic \ + extensions/auth-bearer \ + extensions/auth-machine \ + extensions/frontend \ + extensions/gateway \ extensions/glauth \ extensions/graph \ extensions/graph-explorer \ + extensions/group \ + extensions/idm \ extensions/idp \ + extensions/nats \ + extensions/notifications \ + extensions/ocdav \ extensions/ocs \ extensions/proxy \ extensions/settings \ - extensions/storage \ + extensions/sharing \ + extensions/storage-metadata \ + extensions/storage-publiclink \ + extensions/storage-shares \ + extensions/storage-users \ extensions/store \ extensions/thumbnails \ + extensions/user \ extensions/web \ extensions/webdav\ ocis \ @@ -111,7 +128,7 @@ composer.lock: composer.json .PHONY: generate generate: @for mod in $(OCIS_MODULES); do \ - $(MAKE) --no-print-directory -C $$mod generate || exit 1; \ + $(MAKE) -C $$mod generate || exit 1; \ done .PHONY: vet diff --git a/changelog/unreleased/change-ocis-docker-volume-permissions.md b/changelog/unreleased/change-ocis-docker-volume-permissions.md new file mode 100644 index 0000000000..7069b14ae1 --- /dev/null +++ b/changelog/unreleased/change-ocis-docker-volume-permissions.md @@ -0,0 +1,9 @@ +Change: Reduce permissions on docker image predeclared volumes + +We've lowered the permissions on the predeclared volumes of the oCIS +docker image from 777 to 750. + +This change doesn't affect you, unless you use the docker image with +the non default uid/guid to start oCIS (default is 1000:1000). + +https://github.com/owncloud/ocis/pull/3641 diff --git a/changelog/unreleased/new-space-id-functions.md b/changelog/unreleased/new-space-id-functions.md new file mode 100644 index 0000000000..e399665dc8 --- /dev/null +++ b/changelog/unreleased/new-space-id-functions.md @@ -0,0 +1,5 @@ +Change: Use new space ID util functions + +Changed code to use the new space ID util functions so that everything works with the new spaces ID format. + +https://github.com/owncloud/ocis/pull/3648 diff --git a/changelog/unreleased/update-web-5.5.0.md b/changelog/unreleased/update-web-5.5.0.md new file mode 100644 index 0000000000..cb1bfe545b --- /dev/null +++ b/changelog/unreleased/update-web-5.5.0.md @@ -0,0 +1,9 @@ +Enhancement: Update ownCloud Web to v5.5.0-rc.2 + +Tags: web + +We updated ownCloud Web to v5.5.0-rc.2. Please refer to the changelog (linked) for details on the web release. + +https://github.com/owncloud/web/pull/6854 +https://github.com/owncloud/ocis/pull/3664 +https://github.com/owncloud/web/releases/tag/v5.5.0-rc.2 diff --git a/deployments/examples/oc10_ocis_parallel/docker-compose.yml b/deployments/examples/oc10_ocis_parallel/docker-compose.yml index edd065c6c3..23882fac2a 100644 --- a/deployments/examples/oc10_ocis_parallel/docker-compose.yml +++ b/deployments/examples/oc10_ocis_parallel/docker-compose.yml @@ -108,7 +108,7 @@ services: OCIS_STORAGE_READ_ONLY: "false" # TODO: conflict with OWNCLOUDSQL -> https://github.com/owncloud/ocis/issues/2303 # General oCIS config # OCIS_RUN_EXTENSIONS specifies to start all extensions except glauth, idp and accounts. These are replaced by external services - OCIS_RUN_EXTENSIONS: settings,storage-metadata,graph,graph-explorer,ocs,store,thumbnails,web,webdav,storage-frontend,storage-gateway,storage-userprovider,storage-groupprovider,storage-authbasic,storage-authbearer,storage-authmachine,storage-users,storage-shares,storage-public-link,storage-appprovider,storage-sharing,proxy,nats + OCIS_RUN_EXTENSIONS: settings,storage-metadata,graph,graph-explorer,ocs,store,thumbnails,web,webdav,storage-frontend,storage-gateway,storage-userprovider,storage-groupprovider,storage-authbasic,storage-authbearer,storage-authmachine,storage-users,storage-shares,storage-public-link,app-provider,storage-sharing,proxy,nats OCIS_LOG_LEVEL: ${OCIS_LOG_LEVEL:-error} # make oCIS less verbose OCIS_URL: https://${CLOUD_DOMAIN:-cloud.owncloud.test} PROXY_TLS: "false" # do not use SSL between Traefik and oCIS diff --git a/deployments/examples/ocis_individual_services/docker-compose.yml b/deployments/examples/ocis_individual_services/docker-compose.yml index 2af750b9a9..634a1cce57 100644 --- a/deployments/examples/ocis_individual_services/docker-compose.yml +++ b/deployments/examples/ocis_individual_services/docker-compose.yml @@ -664,7 +664,8 @@ services: STORAGE_FRONTEND_LOG_COLOR: "${OCIS_LOG_COLOR:-false}" STORAGE_FRONTEND_LOG_PRETTY: "${OCIS_LOG_PRETTY:-false}" - STORAGE_FRONTEND_APPPROVIDER_INSECURE: "true" + # FIXME this now lives in a dedicated service + APP_PROVIDER_WOPI_INSECURE: "true" STORAGE_FRONTEND_ARCHIVER_INSECURE: "true" STORAGE_FRONTEND_OCDAV_INSECURE: "true" diff --git a/docs/extensions/app-provider/_index.md b/docs/extensions/app-provider/_index.md new file mode 100644 index 0000000000..cbe1886e55 --- /dev/null +++ b/docs/extensions/app-provider/_index.md @@ -0,0 +1,16 @@ +--- +title: App Provider +date: 2022-03-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/app-provider +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/extensions/app-provider/configuration.md b/docs/extensions/app-provider/configuration.md new file mode 100644 index 0000000000..6f4d3e066d --- /dev/null +++ b/docs/extensions/app-provider/configuration.md @@ -0,0 +1,15 @@ +--- +title: Service Configuration +date: 2018-05-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/app-provider +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="extensions/_includes/app-provider-config-example.yaml" language="yaml" >}} + +{{< include file="extensions/_includes/app-provider_configvars.md" >}} diff --git a/docs/extensions/app-registry/_index.md b/docs/extensions/app-registry/_index.md new file mode 100644 index 0000000000..9b79ea4a89 --- /dev/null +++ b/docs/extensions/app-registry/_index.md @@ -0,0 +1,16 @@ +--- +title: App Registry +date: 2022-03-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/app-registry +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/extensions/storage/apps.md b/docs/extensions/app-registry/apps.md similarity index 99% rename from docs/extensions/storage/apps.md rename to docs/extensions/app-registry/apps.md index 93d7b19e77..7bcff036b1 100644 --- a/docs/extensions/storage/apps.md +++ b/docs/extensions/app-registry/apps.md @@ -3,7 +3,7 @@ title: "Apps" date: 2018-05-02T00:00:00+00:00 weight: 10 geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/storage +geekdocEditPath: edit/master/docs/extensions/app-registry geekdocFilePath: apps.md --- diff --git a/docs/extensions/app-registry/configuration.md b/docs/extensions/app-registry/configuration.md new file mode 100644 index 0000000000..f1a18da072 --- /dev/null +++ b/docs/extensions/app-registry/configuration.md @@ -0,0 +1,15 @@ +--- +title: Service Configuration +date: 2018-05-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/app-registry +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="extensions/_includes/app-registry-config-example.yaml" language="yaml" >}} + +{{< include file="extensions/_includes/app-registry_configvars.md" >}} diff --git a/docs/extensions/auth-basic/_index.md b/docs/extensions/auth-basic/_index.md new file mode 100644 index 0000000000..0f47e22979 --- /dev/null +++ b/docs/extensions/auth-basic/_index.md @@ -0,0 +1,16 @@ +--- +title: Auth-Basic +date: 2022-03-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/auth-basic +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/extensions/auth-basic/configuration.md b/docs/extensions/auth-basic/configuration.md new file mode 100644 index 0000000000..4fdd2f9225 --- /dev/null +++ b/docs/extensions/auth-basic/configuration.md @@ -0,0 +1,15 @@ +--- +title: Service Configuration +date: 2018-05-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/auth-basic +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="extensions/_includes/auth-basic-config-example.yaml" language="yaml" >}} + +{{< include file="extensions/_includes/auth-basic_configvars.md" >}} diff --git a/docs/extensions/auth-bearer/_index.md b/docs/extensions/auth-bearer/_index.md new file mode 100644 index 0000000000..853f8938fa --- /dev/null +++ b/docs/extensions/auth-bearer/_index.md @@ -0,0 +1,16 @@ +--- +title: Auth-Bearer +date: 2022-03-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/auth-bearer +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/extensions/auth-bearer/configuration.md b/docs/extensions/auth-bearer/configuration.md new file mode 100644 index 0000000000..853ef9acc7 --- /dev/null +++ b/docs/extensions/auth-bearer/configuration.md @@ -0,0 +1,15 @@ +--- +title: Service Configuration +date: 2018-05-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/auth-bearer +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="extensions/_includes/auth-bearer-config-example.yaml" language="yaml" >}} + +{{< include file="extensions/_includes/auth-bearer_configvars.md" >}} diff --git a/docs/extensions/auth-machine/_index.md b/docs/extensions/auth-machine/_index.md new file mode 100644 index 0000000000..cba78fad0d --- /dev/null +++ b/docs/extensions/auth-machine/_index.md @@ -0,0 +1,16 @@ +--- +title: Auth-Machine +date: 2022-03-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/auth-machine +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/extensions/auth-machine/configuration.md b/docs/extensions/auth-machine/configuration.md new file mode 100644 index 0000000000..96fb78c4b8 --- /dev/null +++ b/docs/extensions/auth-machine/configuration.md @@ -0,0 +1,15 @@ +--- +title: Service Configuration +date: 2018-05-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/auth-machine +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="extensions/_includes/auth-machine-config-example.yaml" language="yaml" >}} + +{{< include file="extensions/_includes/auth-machine_configvars.md" >}} diff --git a/docs/extensions/frontend/_index.md b/docs/extensions/frontend/_index.md new file mode 100644 index 0000000000..3e6321e9dd --- /dev/null +++ b/docs/extensions/frontend/_index.md @@ -0,0 +1,16 @@ +--- +title: Frontend +date: 2022-03-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/frontend +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/extensions/frontend/configuration.md b/docs/extensions/frontend/configuration.md new file mode 100644 index 0000000000..fb164e6bab --- /dev/null +++ b/docs/extensions/frontend/configuration.md @@ -0,0 +1,15 @@ +--- +title: Service Configuration +date: 2018-05-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/frontend +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="extensions/_includes/frontend-config-example.yaml" language="yaml" >}} + +{{< include file="extensions/_includes/frontend_configvars.md" >}} diff --git a/docs/extensions/gateway/_index.md b/docs/extensions/gateway/_index.md new file mode 100644 index 0000000000..4a89602b6c --- /dev/null +++ b/docs/extensions/gateway/_index.md @@ -0,0 +1,16 @@ +--- +title: Gateway +date: 2022-03-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/gateway +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/extensions/gateway/configuration.md b/docs/extensions/gateway/configuration.md new file mode 100644 index 0000000000..3ff17a8e8f --- /dev/null +++ b/docs/extensions/gateway/configuration.md @@ -0,0 +1,15 @@ +--- +title: Service Configuration +date: 2018-05-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/gateway +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="extensions/_includes/gateway-config-example.yaml" language="yaml" >}} + +{{< include file="extensions/_includes/gateway_configvars.md" >}} diff --git a/docs/extensions/group/_index.md b/docs/extensions/group/_index.md new file mode 100644 index 0000000000..00fabc8b7a --- /dev/null +++ b/docs/extensions/group/_index.md @@ -0,0 +1,16 @@ +--- +title: Group +date: 2022-03-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/group +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/extensions/group/configuration.md b/docs/extensions/group/configuration.md new file mode 100644 index 0000000000..b177bf5a5d --- /dev/null +++ b/docs/extensions/group/configuration.md @@ -0,0 +1,15 @@ +--- +title: Service Configuration +date: 2018-05-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/group +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="extensions/_includes/group-config-example.yaml" language="yaml" >}} + +{{< include file="extensions/_includes/group_configvars.md" >}} diff --git a/docs/extensions/ocdav/_index.md b/docs/extensions/ocdav/_index.md new file mode 100644 index 0000000000..62f55ff1b5 --- /dev/null +++ b/docs/extensions/ocdav/_index.md @@ -0,0 +1,16 @@ +--- +title: ocDAV +date: 2022-03-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/ocdav +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/extensions/ocdav/configuration.md b/docs/extensions/ocdav/configuration.md new file mode 100644 index 0000000000..9ca88c1de0 --- /dev/null +++ b/docs/extensions/ocdav/configuration.md @@ -0,0 +1,15 @@ +--- +title: Service Configuration +date: 2018-05-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/ocdav +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="extensions/_includes/ocdav-config-example.yaml" language="yaml" >}} + +{{< include file="extensions/_includes/ocdav_configvars.md" >}} diff --git a/docs/extensions/port-ranges.md b/docs/extensions/port-ranges.md index bc3143c08e..7aa428f9cd 100644 --- a/docs/extensions/port-ranges.md +++ b/docs/extensions/port-ranges.md @@ -46,7 +46,7 @@ We also suggest to use the last port in your extensions' range as a debug/metric | 9225-9229 | photoprism (state: PoC) | | 9230-9234 | [nats](https://github.com/owncloud/ocis/tree/master/nats) | | 9235-9239 | idm TBD | -| 9240-9244 | FREE | +| 9240-9244 | [app-registry](https://github.com/owncloud/ocis/tree/master/extensions/app-registry) | | 9245-9249 | FREE | | 9250-9254 | oCIS Runtime | | 9255-9259 | FREE | diff --git a/docs/extensions/storage-metadata/_index.md b/docs/extensions/storage-metadata/_index.md new file mode 100644 index 0000000000..04266f260a --- /dev/null +++ b/docs/extensions/storage-metadata/_index.md @@ -0,0 +1,16 @@ +--- +title: Metadata Storage +date: 2022-03-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/storage-metadata +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/extensions/storage-metadata/configuration.md b/docs/extensions/storage-metadata/configuration.md new file mode 100644 index 0000000000..a53ad63b93 --- /dev/null +++ b/docs/extensions/storage-metadata/configuration.md @@ -0,0 +1,15 @@ +--- +title: Service Configuration +date: 2018-05-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/storage-metadata +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="extensions/_includes/storage-metadata-config-example.yaml" language="yaml" >}} + +{{< include file="extensions/_includes/storage-metadata_configvars.md" >}} diff --git a/docs/extensions/storage-publiclink/_index.md b/docs/extensions/storage-publiclink/_index.md new file mode 100644 index 0000000000..cbe0f2a248 --- /dev/null +++ b/docs/extensions/storage-publiclink/_index.md @@ -0,0 +1,16 @@ +--- +title: Publiclink Storage +date: 2022-03-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/storage-publiclink +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/extensions/storage-publiclink/configuration.md b/docs/extensions/storage-publiclink/configuration.md new file mode 100644 index 0000000000..6b741c9c15 --- /dev/null +++ b/docs/extensions/storage-publiclink/configuration.md @@ -0,0 +1,15 @@ +--- +title: Service Configuration +date: 2018-05-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/storage-publiclink +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="extensions/_includes/storage-publiclink-config-example.yaml" language="yaml" >}} + +{{< include file="extensions/_includes/storage-publiclink_configvars.md" >}} diff --git a/docs/extensions/storage-shares/_index.md b/docs/extensions/storage-shares/_index.md new file mode 100644 index 0000000000..e878e42b8d --- /dev/null +++ b/docs/extensions/storage-shares/_index.md @@ -0,0 +1,16 @@ +--- +title: Shares Storage +date: 2022-03-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/storage-shares +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/extensions/storage-shares/configuration.md b/docs/extensions/storage-shares/configuration.md new file mode 100644 index 0000000000..5a63a77c45 --- /dev/null +++ b/docs/extensions/storage-shares/configuration.md @@ -0,0 +1,15 @@ +--- +title: Service Configuration +date: 2018-05-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/storage-shares +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="extensions/_includes/storage-shares-config-example.yaml" language="yaml" >}} + +{{< include file="extensions/_includes/storage-shares_configvars.md" >}} diff --git a/docs/extensions/storage-users/_index.md b/docs/extensions/storage-users/_index.md new file mode 100644 index 0000000000..e66f413d45 --- /dev/null +++ b/docs/extensions/storage-users/_index.md @@ -0,0 +1,16 @@ +--- +title: Users Storage +date: 2022-03-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/storage-users +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/extensions/storage-users/configuration.md b/docs/extensions/storage-users/configuration.md new file mode 100644 index 0000000000..0b121cfdcd --- /dev/null +++ b/docs/extensions/storage-users/configuration.md @@ -0,0 +1,15 @@ +--- +title: Service Configuration +date: 2018-05-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/storage-users +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="extensions/_includes/storage-users-config-example.yaml" language="yaml" >}} + +{{< include file="extensions/_includes/storage-users_configvars.md" >}} diff --git a/docs/extensions/storage/ports.md b/docs/extensions/storage/ports.md index 62bd6ac82e..b594dd85bb 100644 --- a/docs/extensions/storage/ports.md +++ b/docs/extensions/storage/ports.md @@ -34,8 +34,8 @@ For now, the storage service uses these ports to preconfigure those services: | 9159 | storage users debug | | 9160 | groups | | 9161 | groups debug | -| 9164 | storage appprovider | -| 9165 | storage appprovider debug | +| 9164 | storage app-provider | +| 9165 | storage app-provider debug | | 9178 | storage public link | | 9179 | storage public link data | | 9180 | accounts grpc | diff --git a/docs/extensions/user/_index.md b/docs/extensions/user/_index.md new file mode 100644 index 0000000000..8c4078068f --- /dev/null +++ b/docs/extensions/user/_index.md @@ -0,0 +1,16 @@ +--- +title: User +date: 2022-03-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/user +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/extensions/user/configuration.md b/docs/extensions/user/configuration.md new file mode 100644 index 0000000000..683e05b24e --- /dev/null +++ b/docs/extensions/user/configuration.md @@ -0,0 +1,15 @@ +--- +title: Service Configuration +date: 2018-05-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/extensions/user +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="extensions/_includes/user-config-example.yaml" language="yaml" >}} + +{{< include file="extensions/_includes/user_configvars.md" >}} diff --git a/docs/helpers/configenvextractor.go b/docs/helpers/configenvextractor.go index 6b49f9dc1a..f8f071ae02 100644 --- a/docs/helpers/configenvextractor.go +++ b/docs/helpers/configenvextractor.go @@ -13,9 +13,9 @@ import ( ) var targets = map[string]string{ - "adoc-generator.go.tmpl": "output/adoc/adoc-generator.go", - "example-config-generator.go.tmpl": "output/exampleconfig/example-config-generator.go", - "extractor.go.tmpl": "output/env/runner.go", + "adoc-generator.go.tmpl": "output/adoc/adoc-generator.go", + "example-config-generator.go.tmpl": "output/exampleconfig/example-config-generator.go", + "environment-variable-docs-generator.go.tmpl": "output/env/environment-variable-docs-generator.go", } func main() { diff --git a/docs/helpers/extractor.go.tmpl b/docs/helpers/environment-variable-docs-generator.go.tmpl similarity index 100% rename from docs/helpers/extractor.go.tmpl rename to docs/helpers/environment-variable-docs-generator.go.tmpl diff --git a/extensions/accounts/pkg/command/root.go b/extensions/accounts/pkg/command/root.go index aee23e547d..bb68f61678 100644 --- a/extensions/accounts/pkg/command/root.go +++ b/extensions/accounts/pkg/command/root.go @@ -34,7 +34,7 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the ocis-accounts command. func Execute(cfg *config.Config) error { app := clihelper.DefaultApp(&cli.App{ - Name: "ocis-accounts", + Name: "accounts", Usage: "Provide accounts and groups for oCIS", Commands: GetCommands(cfg), }) diff --git a/extensions/app-provider/Makefile b/extensions/app-provider/Makefile new file mode 100644 index 0000000000..54cdae4059 --- /dev/null +++ b/extensions/app-provider/Makefile @@ -0,0 +1,37 @@ +SHELL := bash +NAME := app-provider + +include ../../.make/recursion.mk + +############ tooling ############ +ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI +include ../../.bingo/Variables.mk +endif + +############ go tooling ############ +include ../../.make/go.mk + +############ release ############ +include ../../.make/release.mk + +############ docs generate ############ +include ../../.make/docs.mk + +.PHONY: docs-generate +docs-generate: config-docs-generate + +############ generate ############ +include ../../.make/generate.mk + +.PHONY: ci-go-generate +ci-go-generate: # CI runs ci-node-generate automatically before this target + +.PHONY: ci-node-generate +ci-node-generate: + +############ licenses ############ +.PHONY: ci-node-check-licenses +ci-node-check-licenses: + +.PHONY: ci-node-save-licenses +ci-node-save-licenses: diff --git a/extensions/app-provider/cmd/app-provider/main.go b/extensions/app-provider/cmd/app-provider/main.go new file mode 100644 index 0000000000..e16c590b72 --- /dev/null +++ b/extensions/app-provider/cmd/app-provider/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/app-provider/pkg/command" + "github.com/owncloud/ocis/extensions/app-provider/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/app-provider/pkg/command/health.go b/extensions/app-provider/pkg/command/health.go new file mode 100644 index 0000000000..64c46626d0 --- /dev/null +++ b/extensions/app-provider/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/app-provider/pkg/config" + "github.com/owncloud/ocis/extensions/app-provider/pkg/config/parser" + "github.com/owncloud/ocis/extensions/app-provider/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/app-provider/pkg/command/root.go b/extensions/app-provider/pkg/command/root.go new file mode 100644 index 0000000000..eb42d7289b --- /dev/null +++ b/extensions/app-provider/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/app-provider/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-accounts command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "app-provider", + Usage: "Provide apps for oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the accounts command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new accounts.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.AppProvider.Commons = cfg.Commons + return SutureService{ + cfg: cfg.AppProvider, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/app-provider/pkg/command/server.go b/extensions/app-provider/pkg/command/server.go new file mode 100644 index 0000000000..78ee4ef420 --- /dev/null +++ b/extensions/app-provider/pkg/command/server.go @@ -0,0 +1,107 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/app-provider/pkg/config" + "github.com/owncloud/ocis/extensions/app-provider/pkg/config/parser" + "github.com/owncloud/ocis/extensions/app-provider/pkg/logging" + "github.com/owncloud/ocis/extensions/app-provider/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/app-provider/pkg/server/debug" + "github.com/owncloud/ocis/extensions/app-provider/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/sync" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.AppProviderConfigFromStruct(cfg) + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + if err := external.RegisterGRPCEndpoint( + ctx, + cfg.GRPC.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.GRPC.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/app-provider/pkg/command/version.go b/extensions/app-provider/pkg/command/version.go new file mode 100644 index 0000000000..9ca6e23d86 --- /dev/null +++ b/extensions/app-provider/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/app-provider/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/app-provider/pkg/config/config.go b/extensions/app-provider/pkg/config/config.go new file mode 100644 index 0000000000..ca0c569ff7 --- /dev/null +++ b/extensions/app-provider/pkg/config/config.go @@ -0,0 +1,74 @@ +package config + +import ( + "context" + + "github.com/owncloud/ocis/ocis-pkg/shared" +) + +type Config struct { + *shared.Commons `yaml:"-"` + Service Service `yaml:"-"` + Tracing *Tracing `yaml:"tracing"` + Log *Log `yaml:"log"` + Debug Debug `yaml:"debug"` + + GRPC GRPCConfig `yaml:"grpc"` + + TokenManager *TokenManager `yaml:"token_manager"` + Reva *Reva `yaml:"reva"` + + ExternalAddr string `yaml:"external_addr"` + Driver string `yaml:"driver"` + Drivers Drivers `yaml:"drivers"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` +} + +type Tracing struct { + Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;APP_PROVIDER_TRACING_ENABLED" desc:"Activates tracing."` + Type string `yaml:"type" env:"OCIS_TRACING_TYPE;APP_PROVIDER_TRACING_TYPE"` + Endpoint string `yaml:"endpoint" env:"OCIS_TRACING_ENDPOINT;APP_PROVIDER_TRACING_ENDPOINT" desc:"The endpoint to the tracing collector."` + Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;APP_PROVIDER_TRACING_COLLECTOR"` +} + +type Log struct { + Level string `yaml:"level" env:"OCIS_LOG_LEVEL;APP_PROVIDER_LOG_LEVEL" desc:"The log level."` + Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;APP_PROVIDER_LOG_PRETTY" desc:"Activates pretty log output."` + Color bool `yaml:"color" env:"OCIS_LOG_COLOR;APP_PROVIDER_LOG_COLOR" desc:"Activates colorized log output."` + File string `yaml:"file" env:"OCIS_LOG_FILE;APP_PROVIDER_LOG_FILE" desc:"The target log file."` +} + +type Service struct { + Name string `yaml:"-"` +} + +type Debug struct { + Addr string `yaml:"addr" env:"APP_PROVIDER_DEBUG_ADDR"` + Token string `yaml:"token" env:"APP_PROVIDER_DEBUG_TOKEN"` + Pprof bool `yaml:"pprof" env:"APP_PROVIDER_DEBUG_PPROF"` + Zpages bool `yaml:"zpages" env:"APP_PROVIDER_DEBUG_ZPAGES"` +} + +type GRPCConfig struct { + Addr string `yaml:"addr" env:"APP_PROVIDER_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"APP_PROVIDER_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` +} + +type Drivers struct { + WOPI WOPIDriver `yaml:"wopi" desc:"driver for the CS3org WOPI server"` +} + +type WOPIDriver struct { + AppAPIKey string `yaml:"app_api_key" env:"APP_PROVIDER_WOPI_APP_API_KEY" desc:"api key for the wopi app"` + AppDesktopOnly bool `yaml:"app_desktop_only" env:"APP_PROVIDER_WOPI_APP_DESKTOP_ONLY" desc:"offer this app only on desktop"` + AppIconURI string `yaml:"app_icon_uri" env:"APP_PROVIDER_WOPI_APP_ICON_URI" desc:"uri to an app icon to be used by clients"` + AppInternalURL string `yaml:"app_internal_url" env:"APP_PROVIDER_WOPI_APP_INTERNAL_URL" desc:"internal url to the app, eg in your DMZ"` + AppName string `yaml:"app_name" env:"APP_PROVIDER_WOPI_APP_NAME" desc:"human readable app name"` + AppURL string `yaml:"app_url" env:"APP_PROVIDER_WOPI_APP_URL" desc:"url for end users to access the app"` + Insecure bool `yaml:"insecure" env:"APP_PROVIDER_WOPI_INSECURE" desc:"allow insecure connections to the app"` + IopSecret string `yaml:"wopi_server_iop_secret" env:"APP_PROVIDER_WOPI_WOPI_SERVER_IOP_SECRET" desc:"shared secret of the CS3org WOPI server"` + WopiURL string `yaml:"wopi_server_external_url" env:"APP_PROVIDER_WOPI_WOPI_SERVER_EXTERNAL_URL" desc:"external url of the CS3org WOPI server"` +} diff --git a/extensions/appprovider/pkg/config/defaults/defaultconfig.go b/extensions/app-provider/pkg/config/defaults/defaultconfig.go similarity index 86% rename from extensions/appprovider/pkg/config/defaults/defaultconfig.go rename to extensions/app-provider/pkg/config/defaults/defaultconfig.go index 978c6d2edb..22a4d5a0fd 100644 --- a/extensions/appprovider/pkg/config/defaults/defaultconfig.go +++ b/extensions/app-provider/pkg/config/defaults/defaultconfig.go @@ -1,7 +1,7 @@ package defaults import ( - "github.com/owncloud/ocis/extensions/appprovider/pkg/config" + "github.com/owncloud/ocis/extensions/app-provider/pkg/config" "github.com/owncloud/ocis/ocis-pkg/shared" ) @@ -21,11 +21,12 @@ func DefaultConfig() *config.Config { Zpages: false, }, GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9164", - Protocol: "tcp", + Addr: "127.0.0.1:9164", + Namespace: "com.owncloud.api", + Protocol: "tcp", }, Service: config.Service{ - Name: "appprovider", + Name: "app-provider", }, Reva: &config.Reva{ Address: "127.0.0.1:9142", @@ -39,15 +40,15 @@ func DefaultConfig() *config.Config { func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/appprovider/pkg/config/parser/parse.go b/extensions/app-provider/pkg/config/parser/parse.go similarity index 86% rename from extensions/appprovider/pkg/config/parser/parse.go rename to extensions/app-provider/pkg/config/parser/parse.go index ff554af475..78439f269d 100644 --- a/extensions/appprovider/pkg/config/parser/parse.go +++ b/extensions/app-provider/pkg/config/parser/parse.go @@ -3,8 +3,8 @@ package parser import ( "errors" - "github.com/owncloud/ocis/extensions/appprovider/pkg/config" - "github.com/owncloud/ocis/extensions/appprovider/pkg/config/defaults" + "github.com/owncloud/ocis/extensions/app-provider/pkg/config" + "github.com/owncloud/ocis/extensions/app-provider/pkg/config/defaults" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" "github.com/owncloud/ocis/ocis-pkg/shared" diff --git a/extensions/appprovider/pkg/config/reva.go b/extensions/app-provider/pkg/config/reva.go similarity index 100% rename from extensions/appprovider/pkg/config/reva.go rename to extensions/app-provider/pkg/config/reva.go diff --git a/extensions/app-provider/pkg/logging/logging.go b/extensions/app-provider/pkg/logging/logging.go new file mode 100644 index 0000000000..33f9128ba8 --- /dev/null +++ b/extensions/app-provider/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/app-provider/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/app-provider/pkg/revaconfig/config.go b/extensions/app-provider/pkg/revaconfig/config.go new file mode 100644 index 0000000000..3f4217e9f3 --- /dev/null +++ b/extensions/app-provider/pkg/revaconfig/config.go @@ -0,0 +1,47 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/extensions/app-provider/pkg/config" +) + +// AppProviderConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func AppProviderConfigFromStruct(cfg *config.Config) map[string]interface{} { + + rcfg := map[string]interface{}{ + "core": map[string]interface{}{ + "tracing_enabled": cfg.Tracing.Enabled, + "tracing_endpoint": cfg.Tracing.Endpoint, + "tracing_collector": cfg.Tracing.Collector, + "tracing_service_name": cfg.Service.Name, + }, + "shared": map[string]interface{}{ + "jwt_secret": cfg.TokenManager.JWTSecret, + "gatewaysvc": cfg.Reva.Address, + }, + "grpc": map[string]interface{}{ + "network": cfg.GRPC.Protocol, + "address": cfg.GRPC.Addr, + "services": map[string]interface{}{ + "appprovider": map[string]interface{}{ + "app_provider_url": cfg.ExternalAddr, + "driver": cfg.Driver, + "drivers": map[string]interface{}{ + "wopi": map[string]interface{}{ + "app_api_key": cfg.Drivers.WOPI.AppAPIKey, + "app_desktop_only": cfg.Drivers.WOPI.AppDesktopOnly, + "app_icon_uri": cfg.Drivers.WOPI.AppIconURI, + "app_int_url": cfg.Drivers.WOPI.AppInternalURL, + "app_name": cfg.Drivers.WOPI.AppName, + "app_url": cfg.Drivers.WOPI.AppURL, + "insecure_connections": cfg.Drivers.WOPI.Insecure, + "iop_secret": cfg.Drivers.WOPI.IopSecret, + "jwt_secret": cfg.TokenManager.JWTSecret, + "wopi_url": cfg.Drivers.WOPI.WopiURL, + }, + }, + }, + }, + }, + } + return rcfg +} diff --git a/extensions/app-provider/pkg/server/debug/option.go b/extensions/app-provider/pkg/server/debug/option.go new file mode 100644 index 0000000000..fb0d4fdf43 --- /dev/null +++ b/extensions/app-provider/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/extensions/app-provider/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Logger log.Logger + Context context.Context + Config *config.Config +} + +// newOptions initializes the available default options. +func newOptions(opts ...Option) Options { + opt := Options{} + + for _, o := range opts { + o(&opt) + } + + return opt +} + +// Logger provides a function to set the logger option. +func Logger(val log.Logger) Option { + return func(o *Options) { + o.Logger = val + } +} + +// Context provides a function to set the context option. +func Context(val context.Context) Option { + return func(o *Options) { + o.Context = val + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} diff --git a/extensions/app-provider/pkg/server/debug/server.go b/extensions/app-provider/pkg/server/debug/server.go new file mode 100644 index 0000000000..04b52c3ce9 --- /dev/null +++ b/extensions/app-provider/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/extensions/app-provider/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/service/debug" + "github.com/owncloud/ocis/ocis-pkg/version" +) + +// Server initializes the debug service and server. +func Server(opts ...Option) (*http.Server, error) { + options := newOptions(opts...) + + return debug.NewService( + debug.Logger(options.Logger), + debug.Name(options.Config.Service.Name), + debug.Version(version.String), + debug.Address(options.Config.Debug.Addr), + debug.Token(options.Config.Debug.Token), + debug.Pprof(options.Config.Debug.Pprof), + debug.Zpages(options.Config.Debug.Zpages), + debug.Health(health(options.Config)), + debug.Ready(ready(options.Config)), + //debug.CorsAllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins), + //debug.CorsAllowedMethods(options.Config.HTTP.CORS.AllowedMethods), + //debug.CorsAllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders), + //debug.CorsAllowCredentials(options.Config.HTTP.CORS.AllowCredentials), + ), nil +} + +// health implements the health check. +func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} + +// ready implements the ready check. +func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} diff --git a/extensions/app-provider/pkg/tracing/tracing.go b/extensions/app-provider/pkg/tracing/tracing.go new file mode 100644 index 0000000000..fa358604ba --- /dev/null +++ b/extensions/app-provider/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/app-provider/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/app-registry/Makefile b/extensions/app-registry/Makefile new file mode 100644 index 0000000000..8097c27d96 --- /dev/null +++ b/extensions/app-registry/Makefile @@ -0,0 +1,37 @@ +SHELL := bash +NAME := app-registry + +include ../../.make/recursion.mk + +############ tooling ############ +ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI +include ../../.bingo/Variables.mk +endif + +############ go tooling ############ +include ../../.make/go.mk + +############ release ############ +include ../../.make/release.mk + +############ docs generate ############ +include ../../.make/docs.mk + +.PHONY: docs-generate +docs-generate: config-docs-generate + +############ generate ############ +include ../../.make/generate.mk + +.PHONY: ci-go-generate +ci-go-generate: # CI runs ci-node-generate automatically before this target + +.PHONY: ci-node-generate +ci-node-generate: + +############ licenses ############ +.PHONY: ci-node-check-licenses +ci-node-check-licenses: + +.PHONY: ci-node-save-licenses +ci-node-save-licenses: diff --git a/extensions/app-registry/cmd/app-registry/main.go b/extensions/app-registry/cmd/app-registry/main.go new file mode 100644 index 0000000000..19565e1e81 --- /dev/null +++ b/extensions/app-registry/cmd/app-registry/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/app-registry/pkg/command" + "github.com/owncloud/ocis/extensions/app-registry/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/app-registry/pkg/command/health.go b/extensions/app-registry/pkg/command/health.go new file mode 100644 index 0000000000..c60fd662a9 --- /dev/null +++ b/extensions/app-registry/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/app-registry/pkg/config" + "github.com/owncloud/ocis/extensions/app-registry/pkg/config/parser" + "github.com/owncloud/ocis/extensions/app-registry/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/app-registry/pkg/command/root.go b/extensions/app-registry/pkg/command/root.go new file mode 100644 index 0000000000..4525aeefc4 --- /dev/null +++ b/extensions/app-registry/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/app-registry/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-app-registry command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "app-registry", + Usage: "Provide a app registry for oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the app-registry command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new app-registry.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.AppRegistry.Commons = cfg.Commons + return SutureService{ + cfg: cfg.AppRegistry, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/app-registry/pkg/command/server.go b/extensions/app-registry/pkg/command/server.go new file mode 100644 index 0000000000..0ab6f3301c --- /dev/null +++ b/extensions/app-registry/pkg/command/server.go @@ -0,0 +1,102 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/app-registry/pkg/config" + "github.com/owncloud/ocis/extensions/app-registry/pkg/config/parser" + "github.com/owncloud/ocis/extensions/app-registry/pkg/logging" + "github.com/owncloud/ocis/extensions/app-registry/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/app-registry/pkg/server/debug" + "github.com/owncloud/ocis/extensions/app-registry/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.AppRegistryConfigFromStruct(cfg, logger) + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if err := external.RegisterGRPCEndpoint( + ctx, + cfg.GRPC.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.GRPC.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/app-registry/pkg/command/version.go b/extensions/app-registry/pkg/command/version.go new file mode 100644 index 0000000000..04b2d20c61 --- /dev/null +++ b/extensions/app-registry/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/app-registry/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/app-registry/pkg/config/config.go b/extensions/app-registry/pkg/config/config.go new file mode 100644 index 0000000000..d3efe36e50 --- /dev/null +++ b/extensions/app-registry/pkg/config/config.go @@ -0,0 +1,70 @@ +package config + +import ( + "context" + + "github.com/owncloud/ocis/ocis-pkg/shared" +) + +type Config struct { + *shared.Commons `yaml:"-"` + + Service Service `yaml:"-"` + Tracing *Tracing `yaml:"tracing"` + Log *Log `yaml:"log"` + Debug Debug `yaml:"debug"` + + GRPC GRPCConfig `yaml:"grpc"` + + TokenManager *TokenManager `yaml:"token_manager"` + Reva *Reva `yaml:"reva"` + + AppRegistry AppRegistry `yaml:"app_registry"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` +} +type Tracing struct { + Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;APP_REGISTRY_TRACING_ENABLED" desc:"Activates tracing."` + Type string `yaml:"type" env:"OCIS_TRACING_TYPE;APP_REGISTRY_TRACING_TYPE"` + Endpoint string `yaml:"endpoint" env:"OCIS_TRACING_ENDPOINT;APP_REGISTRY_TRACING_ENDPOINT" desc:"The endpoint to the tracing collector."` + Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;APP_REGISTRY_TRACING_COLLECTOR"` +} + +type Log struct { + Level string `yaml:"level" env:"OCIS_LOG_LEVEL;APP_REGISTRY_LOG_LEVEL" desc:"The log level."` + Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;APP_REGISTRY_LOG_PRETTY" desc:"Activates pretty log output."` + Color bool `yaml:"color" env:"OCIS_LOG_COLOR;APP_REGISTRY_LOG_COLOR" desc:"Activates colorized log output."` + File string `yaml:"file" env:"OCIS_LOG_FILE;APP_REGISTRY_LOG_FILE" desc:"The target log file."` +} + +type Service struct { + Name string `yaml:"-"` +} + +type Debug struct { + Addr string `yaml:"addr" env:"APP_REGISTRY_DEBUG_ADDR"` + Token string `yaml:"token" env:"APP_REGISTRY_DEBUG_TOKEN"` + Pprof bool `yaml:"pprof" env:"APP_REGISTRY_DEBUG_PPROF"` + Zpages bool `yaml:"zpages" env:"APP_REGISTRY_DEBUG_ZPAGES"` +} + +type GRPCConfig struct { + Addr string `yaml:"addr" env:"APP_REGISTRY_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"APP_REGISTRY_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` +} + +type AppRegistry struct { + MimeTypeConfig []MimeTypeConfig `yaml:"mimetypes"` +} + +type MimeTypeConfig struct { + MimeType string `yaml:"mime_type" mapstructure:"mime_type"` + Extension string `yaml:"extension" mapstructure:"extension"` + Name string `yaml:"name" mapstructure:"name"` + Description string `yaml:"description" mapstructure:"description"` + Icon string `yaml:"icon" mapstructure:"icon"` + DefaultApp string `yaml:"default_app" mapstructure:"default_app"` + AllowCreation bool `yaml:"allow_creation" mapstructure:"allow_creation"` +} diff --git a/extensions/app-registry/pkg/config/defaults/defaultconfig.go b/extensions/app-registry/pkg/config/defaults/defaultconfig.go new file mode 100644 index 0000000000..8e24bab744 --- /dev/null +++ b/extensions/app-registry/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,155 @@ +package defaults + +import ( + "github.com/owncloud/ocis/extensions/app-registry/pkg/config" +) + +func FullDefaultConfig() *config.Config { + cfg := DefaultConfig() + EnsureDefaults(cfg) + Sanitize(cfg) + return cfg +} + +func DefaultConfig() *config.Config { + return &config.Config{ + Debug: config.Debug{ + Addr: "127.0.0.1:9243", + Token: "", + Pprof: false, + Zpages: false, + }, + GRPC: config.GRPCConfig{ + Addr: "127.0.0.1:9242", + Namespace: "com.owncloud.api", + Protocol: "tcp", + }, + Service: config.Service{ + Name: "app-registry", + }, + Reva: &config.Reva{ + Address: "127.0.0.1:9142", + }, + AppRegistry: config.AppRegistry{ + MimeTypeConfig: defaultMimeTypeConfig(), + }, + } +} + +func defaultMimeTypeConfig() []config.MimeTypeConfig { + return []config.MimeTypeConfig{ + { + MimeType: "application/pdf", + Extension: "pdf", + Name: "PDF", + Description: "PDF document", + }, + { + MimeType: "application/vnd.oasis.opendocument.text", + Extension: "odt", + Name: "OpenDocument", + Description: "OpenDocument text document", + AllowCreation: true, + }, + { + MimeType: "application/vnd.oasis.opendocument.spreadsheet", + Extension: "ods", + Name: "OpenSpreadsheet", + Description: "OpenDocument spreadsheet document", + AllowCreation: true, + }, + { + MimeType: "application/vnd.oasis.opendocument.presentation", + Extension: "odp", + Name: "OpenPresentation", + Description: "OpenDocument presentation document", + AllowCreation: true, + }, + { + MimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + Extension: "docx", + Name: "Microsoft Word", + Description: "Microsoft Word document", + AllowCreation: true, + }, + { + MimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + Extension: "xlsx", + Name: "Microsoft Excel", + Description: "Microsoft Excel document", + AllowCreation: true, + }, + { + MimeType: "application/vnd.openxmlformats-officedocument.presentationml.presentation", + Extension: "pptx", + Name: "Microsoft PowerPoint", + Description: "Microsoft PowerPoint document", + AllowCreation: true, + }, + { + MimeType: "application/vnd.jupyter", + Extension: "ipynb", + Name: "Jupyter Notebook", + Description: "Jupyter Notebook", + }, + { + MimeType: "text/markdown", + Extension: "md", + Name: "Markdown file", + Description: "Markdown file", + AllowCreation: true, + }, + { + MimeType: "application/compressed-markdown", + Extension: "zmd", + Name: "Compressed markdown file", + Description: "Compressed markdown file", + }, + } +} + +func EnsureDefaults(cfg *config.Config) { + // provide with defaults for shared logging, since we need a valid destination address for BindEnv. + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ + Level: cfg.Commons.Log.Level, + Pretty: cfg.Commons.Log.Pretty, + Color: cfg.Commons.Log.Color, + File: cfg.Commons.Log.File, + } + } else if cfg.Log == nil { + cfg.Log = &config.Log{} + } + // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. + if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { + cfg.Tracing = &config.Tracing{ + Enabled: cfg.Commons.Tracing.Enabled, + Type: cfg.Commons.Tracing.Type, + Endpoint: cfg.Commons.Tracing.Endpoint, + Collector: cfg.Commons.Tracing.Collector, + } + } else if cfg.Tracing == nil { + cfg.Tracing = &config.Tracing{} + } + + if cfg.Reva == nil && cfg.Commons != nil && cfg.Commons.Reva != nil { + cfg.Reva = &config.Reva{ + Address: cfg.Commons.Reva.Address, + } + } else if cfg.Reva == nil { + cfg.Reva = &config.Reva{} + } + + if cfg.TokenManager == nil && cfg.Commons != nil && cfg.Commons.TokenManager != nil { + cfg.TokenManager = &config.TokenManager{ + JWTSecret: cfg.Commons.TokenManager.JWTSecret, + } + } else if cfg.TokenManager == nil { + cfg.TokenManager = &config.TokenManager{} + } + +} + +func Sanitize(cfg *config.Config) { + // nothing to sanitize here atm +} diff --git a/extensions/storage/pkg/config/parser/parse.go b/extensions/app-registry/pkg/config/parser/parse.go similarity index 66% rename from extensions/storage/pkg/config/parser/parse.go rename to extensions/app-registry/pkg/config/parser/parse.go index d486f6dad4..894f938b6e 100644 --- a/extensions/storage/pkg/config/parser/parse.go +++ b/extensions/app-registry/pkg/config/parser/parse.go @@ -3,9 +3,10 @@ package parser import ( "errors" - "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config" - "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config/defaults" + "github.com/owncloud/ocis/extensions/app-registry/pkg/config" + "github.com/owncloud/ocis/extensions/app-registry/pkg/config/defaults" ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/shared" "github.com/owncloud/ocis/ocis-pkg/config/envdecode" ) @@ -33,5 +34,13 @@ func ParseConfig(cfg *config.Config) error { } func Validate(cfg *config.Config) error { + if cfg.TokenManager.JWTSecret == "" { + return shared.MissingJWTTokenError(cfg.Service.Name) + } + + if cfg.TransferSecret == "" { + return shared.MissingRevaTransferSecretError(cfg.Service.Name) + } + return nil } diff --git a/extensions/app-registry/pkg/config/reva.go b/extensions/app-registry/pkg/config/reva.go new file mode 100644 index 0000000000..e1648edc4d --- /dev/null +++ b/extensions/app-registry/pkg/config/reva.go @@ -0,0 +1,11 @@ +package config + +// Reva defines all available REVA configuration. +type Reva struct { + Address string `yaml:"address" env:"REVA_GATEWAY"` +} + +// TokenManager is the config for using the reva token manager +type TokenManager struct { + JWTSecret string `yaml:"jwt_secret" env:"OCIS_JWT_SECRET;APP_REGISTRY_JWT_SECRET"` +} diff --git a/extensions/app-registry/pkg/logging/logging.go b/extensions/app-registry/pkg/logging/logging.go new file mode 100644 index 0000000000..f887afef4c --- /dev/null +++ b/extensions/app-registry/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/app-registry/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/app-registry/pkg/revaconfig/config.go b/extensions/app-registry/pkg/revaconfig/config.go new file mode 100644 index 0000000000..576f1643c6 --- /dev/null +++ b/extensions/app-registry/pkg/revaconfig/config.go @@ -0,0 +1,48 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/ocis-pkg/log" + + "github.com/mitchellh/mapstructure" + "github.com/owncloud/ocis/extensions/app-registry/pkg/config" +) + +// AppRegistryConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func AppRegistryConfigFromStruct(cfg *config.Config, logger log.Logger) map[string]interface{} { + rcfg := map[string]interface{}{ + "core": map[string]interface{}{ + "tracing_enabled": cfg.Tracing.Enabled, + "tracing_endpoint": cfg.Tracing.Endpoint, + "tracing_collector": cfg.Tracing.Collector, + "tracing_service_name": cfg.Service.Name, + }, + "shared": map[string]interface{}{ + "jwt_secret": cfg.TokenManager.JWTSecret, + "gatewaysvc": cfg.Reva.Address, + }, + "grpc": map[string]interface{}{ + "network": cfg.GRPC.Protocol, + "address": cfg.GRPC.Addr, + "services": map[string]interface{}{ + "appregistry": map[string]interface{}{ + "driver": "static", + "drivers": map[string]interface{}{ + "static": map[string]interface{}{ + "mime_types": mimetypes(cfg, logger), + }, + }, + }, + }, + }, + } + return rcfg +} + +func mimetypes(cfg *config.Config, logger log.Logger) []map[string]interface{} { + var m []map[string]interface{} + if err := mapstructure.Decode(cfg.AppRegistry.MimeTypeConfig, &m); err != nil { + logger.Error().Err(err).Msg("Failed to decode appregistry mimetypes to mapstructure") + return nil + } + return m +} diff --git a/extensions/app-registry/pkg/server/debug/option.go b/extensions/app-registry/pkg/server/debug/option.go new file mode 100644 index 0000000000..2ccb53ed45 --- /dev/null +++ b/extensions/app-registry/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/extensions/app-registry/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Logger log.Logger + Context context.Context + Config *config.Config +} + +// newOptions initializes the available default options. +func newOptions(opts ...Option) Options { + opt := Options{} + + for _, o := range opts { + o(&opt) + } + + return opt +} + +// Logger provides a function to set the logger option. +func Logger(val log.Logger) Option { + return func(o *Options) { + o.Logger = val + } +} + +// Context provides a function to set the context option. +func Context(val context.Context) Option { + return func(o *Options) { + o.Context = val + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} diff --git a/extensions/app-registry/pkg/server/debug/server.go b/extensions/app-registry/pkg/server/debug/server.go new file mode 100644 index 0000000000..cf106ef99b --- /dev/null +++ b/extensions/app-registry/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/extensions/app-registry/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/service/debug" + "github.com/owncloud/ocis/ocis-pkg/version" +) + +// Server initializes the debug service and server. +func Server(opts ...Option) (*http.Server, error) { + options := newOptions(opts...) + + return debug.NewService( + debug.Logger(options.Logger), + debug.Name(options.Config.Service.Name), + debug.Version(version.String), + debug.Address(options.Config.Debug.Addr), + debug.Token(options.Config.Debug.Token), + debug.Pprof(options.Config.Debug.Pprof), + debug.Zpages(options.Config.Debug.Zpages), + debug.Health(health(options.Config)), + debug.Ready(ready(options.Config)), + //debug.CorsAllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins), + //debug.CorsAllowedMethods(options.Config.HTTP.CORS.AllowedMethods), + //debug.CorsAllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders), + //debug.CorsAllowCredentials(options.Config.HTTP.CORS.AllowCredentials), + ), nil +} + +// health implements the health check. +func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} + +// ready implements the ready check. +func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} diff --git a/extensions/app-registry/pkg/tracing/tracing.go b/extensions/app-registry/pkg/tracing/tracing.go new file mode 100644 index 0000000000..8d3a224445 --- /dev/null +++ b/extensions/app-registry/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/app-registry/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/appprovider/pkg/command/command.go b/extensions/appprovider/pkg/command/command.go deleted file mode 100644 index f638e3c98c..0000000000 --- a/extensions/appprovider/pkg/command/command.go +++ /dev/null @@ -1,170 +0,0 @@ -package command - -import ( - "context" - "flag" - "fmt" - "os" - "path" - - "github.com/cs3org/reva/v2/cmd/revad/runtime" - "github.com/gofrs/uuid" - "github.com/oklog/run" - "github.com/owncloud/ocis/extensions/appprovider/pkg/config" - "github.com/owncloud/ocis/extensions/appprovider/pkg/config/parser" - "github.com/owncloud/ocis/extensions/storage/pkg/server/debug" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" -) - -// AppProvider is the entrypoint for the app provider command. -func AppProvider(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "app-provider", - Usage: "start appprovider for providing apps", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - uuid := uuid.Must(uuid.NewV4()) - pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+uuid.String()+".pid") - - rcfg := appProviderConfigFromStruct(c, cfg) - - gr.Add(func() error { - runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) - return nil - }, func(_ error) { - logger.Info(). - Str("server", c.Command.Name). - Msg("Shutting down server") - - cancel() - }) - - debugServer, err := debug.Server( - debug.Name(c.Command.Name+"-debug"), - debug.Addr(cfg.Debug.Addr), - debug.Logger(logger), - debug.Context(ctx), - debug.Pprof(cfg.Debug.Pprof), - debug.Zpages(cfg.Debug.Zpages), - debug.Token(cfg.Debug.Token), - ) - - if err != nil { - logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") - return err - } - - gr.Add(debugServer.ListenAndServe, func(_ error) { - cancel() - }) - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() - }, - } -} - -// appProviderConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func appProviderConfigFromStruct(c *cli.Context, cfg *config.Config) map[string]interface{} { - - rcfg := map[string]interface{}{ - "core": map[string]interface{}{ - "tracing_enabled": cfg.Tracing.Enabled, - "tracing_endpoint": cfg.Tracing.Endpoint, - "tracing_collector": cfg.Tracing.Collector, - "tracing_service_name": c.Command.Name, - }, - "shared": map[string]interface{}{ - "jwt_secret": cfg.TokenManager.JWTSecret, - "gatewaysvc": cfg.Reva.Address, - "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, - }, - "grpc": map[string]interface{}{ - "network": cfg.GRPC.Protocol, - "address": cfg.GRPC.Addr, - // TODO build services dynamically - "services": map[string]interface{}{ - "appprovider": map[string]interface{}{ - "app_provider_url": cfg.ExternalAddr, - "driver": cfg.Driver, - "drivers": map[string]interface{}{ - "wopi": map[string]interface{}{ - "app_api_key": cfg.Drivers.WOPI.AppAPIKey, - "app_desktop_only": cfg.Drivers.WOPI.AppDesktopOnly, - "app_icon_uri": cfg.Drivers.WOPI.AppIconURI, - "app_int_url": cfg.Drivers.WOPI.AppInternalURL, - "app_name": cfg.Drivers.WOPI.AppName, - "app_url": cfg.Drivers.WOPI.AppURL, - "insecure_connections": cfg.Drivers.WOPI.Insecure, - "iop_secret": cfg.Drivers.WOPI.IopSecret, - "jwt_secret": cfg.TokenManager.JWTSecret, - "wopi_url": cfg.Drivers.WOPI.WopiURL, - }, - }, - }, - }, - }, - } - return rcfg -} - -// AppProviderSutureService allows for the app-provider command to be embedded and supervised by a suture supervisor tree. -type AppProviderSutureService struct { - cfg *config.Config -} - -// NewAppProvider creates a new store.AppProviderSutureService -func NewAppProvider(cfg *ociscfg.Config) suture.Service { - cfg.AppProvider.Commons = cfg.Commons - return AppProviderSutureService{ - cfg: cfg.AppProvider, - } -} - -func (s AppProviderSutureService) Serve(ctx context.Context) error { - cmd := AppProvider(s.cfg) - f := &flag.FlagSet{} - cmdFlags := cmd.Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if cmd.Before != nil { - if err := cmd.Before(cliCtx); err != nil { - return err - } - } - if err := cmd.Action(cliCtx); err != nil { - return err - } - - return nil -} diff --git a/extensions/appprovider/pkg/config/config.go b/extensions/appprovider/pkg/config/config.go deleted file mode 100644 index 9f0c0e9c55..0000000000 --- a/extensions/appprovider/pkg/config/config.go +++ /dev/null @@ -1,68 +0,0 @@ -package config - -import "github.com/owncloud/ocis/ocis-pkg/shared" - -type Config struct { - *shared.Commons `yaml:"-"` - Service Service `yaml:"-"` - Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` - Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` - - GRPC GRPCConfig `yaml:"grpc"` - - TokenManager *TokenManager `yaml:"token_manager"` - Reva *Reva `yaml:"reva"` - - SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token"` - ExternalAddr string `yaml:"external_addr"` - Driver string `yaml:"driver"` - Drivers Drivers `yaml:"drivers"` -} - -type Tracing struct { - Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;APP_PROVIDER_TRACING_ENABLED" desc:"Activates tracing."` - Type string `yaml:"type" env:"OCIS_TRACING_TYPE;APP_PROVIDER_TRACING_TYPE"` - Endpoint string `yaml:"endpoint" env:"OCIS_TRACING_ENDPOINT;APP_PROVIDER_TRACING_ENDPOINT" desc:"The endpoint to the tracing collector."` - Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;APP_PROVIDER_TRACING_COLLECTOR"` -} - -type Logging struct { - Level string `yaml:"level" env:"OCIS_LOG_LEVEL;APP_PROVIDER_LOG_LEVEL" desc:"The log level."` - Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;APP_PROVIDER_LOG_PRETTY" desc:"Activates pretty log output."` - Color bool `yaml:"color" env:"OCIS_LOG_COLOR;APP_PROVIDER_LOG_COLOR" desc:"Activates colorized log output."` - File string `yaml:"file" env:"OCIS_LOG_FILE;APP_PROVIDER_LOG_FILE" desc:"The target log file."` -} - -type Service struct { - Name string `yaml:"-"` -} - -type Debug struct { - Addr string `yaml:"addr" env:"APP_PROVIDER_DEBUG_ADDR"` - Token string `yaml:"token" env:"APP_PROVIDER_DEBUG_TOKEN"` - Pprof bool `yaml:"pprof" env:"APP_PROVIDER_DEBUG_PPROF"` - Zpages bool `yaml:"zpages" env:"APP_PROVIDER_DEBUG_ZPAGES"` -} - -type GRPCConfig struct { - Addr string `yaml:"addr" env:"APP_PROVIDER_GRPC_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"APP_PROVIDER_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` -} - -type Drivers struct { - WOPI WOPIDriver -} - -type WOPIDriver struct { - AppAPIKey string `yaml:"app_api_key"` - AppDesktopOnly bool `yaml:"app_desktop_only"` - AppIconURI string `yaml:"app_icon_uri"` - AppInternalURL string `yaml:"app_internal_url"` - AppName string `yaml:"app_name"` - AppURL string `yaml:"app_url"` - Insecure bool `yaml:"insecure"` - IopSecret string `yaml:"ipo_secret"` - WopiURL string `yaml:"wopi_url"` -} diff --git a/extensions/audit/pkg/command/root.go b/extensions/audit/pkg/command/root.go index d3cd62ee46..0920b22629 100644 --- a/extensions/audit/pkg/command/root.go +++ b/extensions/audit/pkg/command/root.go @@ -48,7 +48,7 @@ type SutureService struct { // NewSutureService creates a new audit.SutureService func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Settings.Commons = cfg.Commons + cfg.Audit.Commons = cfg.Commons return SutureService{ cfg: cfg.Audit, } diff --git a/extensions/audit/pkg/config/config.go b/extensions/audit/pkg/config/config.go index b14a78a752..6d21112898 100644 --- a/extensions/audit/pkg/config/config.go +++ b/extensions/audit/pkg/config/config.go @@ -23,9 +23,9 @@ type Config struct { // Events combines the configuration options for the event bus. type Events struct { - Endpoint string `yaml:"events_endpoint" env:"AUDIT_EVENTS_ENDPOINT" desc:"the address of the streaming service"` - Cluster string `yaml:"events_cluster" env:"AUDIT_EVENTS_CLUSTER" desc:"the clusterID of the streaming service. Mandatory when using nats"` - ConsumerGroup string `yaml:"events_group" env:"AUDIT_EVENTS_GROUP" desc:"the customergroup of the service. One group will only get one vopy of an event"` + Endpoint string `yaml:"endpoint" env:"AUDIT_EVENTS_ENDPOINT" desc:"the address of the streaming service"` + Cluster string `yaml:"cluster" env:"AUDIT_EVENTS_CLUSTER" desc:"the clusterID of the streaming service. Mandatory when using nats"` + ConsumerGroup string `yaml:"group" env:"AUDIT_EVENTS_GROUP" desc:"the customergroup of the service. One group will only get one copy of an event"` } // Auditlog holds audit log information diff --git a/extensions/audit/pkg/types/conversion.go b/extensions/audit/pkg/types/conversion.go index fbb789ea59..2140f485f8 100644 --- a/extensions/audit/pkg/types/conversion.go +++ b/extensions/audit/pkg/types/conversion.go @@ -5,7 +5,7 @@ import ( "time" "github.com/cs3org/reva/v2/pkg/events" - "github.com/cs3org/reva/v2/pkg/utils" + "github.com/cs3org/reva/v2/pkg/storagespace" group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" @@ -453,7 +453,7 @@ func extractFileDetails(ref *provider.Reference, owner *user.UserId) (string, st id, path := "", "" if ref != nil { path = ref.GetPath() - id, _ = utils.FormatStorageSpaceReference(ref) + id, _ = storagespace.FormatReference(ref) } uid := "" diff --git a/extensions/auth-basic/Makefile b/extensions/auth-basic/Makefile new file mode 100644 index 0000000000..a87c8190d6 --- /dev/null +++ b/extensions/auth-basic/Makefile @@ -0,0 +1,37 @@ +SHELL := bash +NAME := auth-basic + +include ../../.make/recursion.mk + +############ tooling ############ +ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI +include ../../.bingo/Variables.mk +endif + +############ go tooling ############ +include ../../.make/go.mk + +############ release ############ +include ../../.make/release.mk + +############ docs generate ############ +include ../../.make/docs.mk + +.PHONY: docs-generate +docs-generate: config-docs-generate + +############ generate ############ +include ../../.make/generate.mk + +.PHONY: ci-go-generate +ci-go-generate: # CI runs ci-node-generate automatically before this target + +.PHONY: ci-node-generate +ci-node-generate: + +############ licenses ############ +.PHONY: ci-node-check-licenses +ci-node-check-licenses: + +.PHONY: ci-node-save-licenses +ci-node-save-licenses: diff --git a/extensions/auth-basic/cmd/auth-basic/main.go b/extensions/auth-basic/cmd/auth-basic/main.go new file mode 100644 index 0000000000..de391e6c67 --- /dev/null +++ b/extensions/auth-basic/cmd/auth-basic/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/auth-basic/pkg/command" + "github.com/owncloud/ocis/extensions/auth-basic/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/auth-basic/pkg/command/command.go b/extensions/auth-basic/pkg/command/command.go deleted file mode 100644 index cd08691a56..0000000000 --- a/extensions/auth-basic/pkg/command/command.go +++ /dev/null @@ -1,227 +0,0 @@ -package command - -import ( - "context" - "flag" - "fmt" - "os" - "path" - "path/filepath" - - "github.com/cs3org/reva/v2/cmd/revad/runtime" - "github.com/gofrs/uuid" - "github.com/oklog/run" - "github.com/owncloud/ocis/extensions/auth-basic/pkg/config" - "github.com/owncloud/ocis/extensions/auth-basic/pkg/config/parser" - "github.com/owncloud/ocis/extensions/storage/pkg/server/debug" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/ldap" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" -) - -// Command is the entrypoint for the auth-basic command. -func AuthBasic(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "auth-basic", - Usage: "start authprovider for basic auth", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // pre-create folders - if cfg.AuthProvider == "json" && cfg.AuthProviders.JSON.File != "" { - if err := os.MkdirAll(filepath.Dir(cfg.AuthProviders.JSON.File), os.FileMode(0700)); err != nil { - return err - } - } - - uuid := uuid.Must(uuid.NewV4()) - - pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+uuid.String()+".pid") - - rcfg := authBasicConfigFromStruct(c, cfg) - logger.Debug(). - Str("server", "authbasic"). - Interface("reva-config", rcfg). - Msg("config") - - if cfg.AuthProvider == "ldap" { - ldapCfg := cfg.AuthProviders.LDAP - if err := ldap.WaitForCA(logger, ldapCfg.Insecure, ldapCfg.CACert); err != nil { - logger.Error().Err(err).Msg("The configured LDAP CA cert does not exist") - return err - } - } - - gr.Add(func() error { - runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) - return nil - }, func(_ error) { - logger.Info(). - Str("server", c.Command.Name). - Msg("Shutting down server") - - cancel() - }) - - debugServer, err := debug.Server( - debug.Name(c.Command.Name+"-debug"), - debug.Addr(cfg.Debug.Addr), - debug.Logger(logger), - debug.Context(ctx), - debug.Pprof(cfg.Debug.Pprof), - debug.Zpages(cfg.Debug.Zpages), - debug.Token(cfg.Debug.Token), - ) - - if err != nil { - logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") - return err - } - - gr.Add(debugServer.ListenAndServe, func(_ error) { - cancel() - }) - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() - }, - } -} - -// authBasicConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func authBasicConfigFromStruct(c *cli.Context, cfg *config.Config) map[string]interface{} { - rcfg := map[string]interface{}{ - "core": map[string]interface{}{ - "tracing_enabled": cfg.Tracing.Enabled, - "tracing_endpoint": cfg.Tracing.Endpoint, - "tracing_collector": cfg.Tracing.Collector, - "tracing_service_name": c.Command.Name, - }, - "shared": map[string]interface{}{ - "jwt_secret": cfg.TokenManager.JWTSecret, - "gatewaysvc": cfg.Reva.Address, - "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, - }, - "grpc": map[string]interface{}{ - "network": cfg.GRPC.Protocol, - "address": cfg.GRPC.Addr, - // TODO build services dynamically - "services": map[string]interface{}{ - "authprovider": map[string]interface{}{ - "auth_manager": cfg.AuthProvider, - "auth_managers": map[string]interface{}{ - "json": map[string]interface{}{ - "users": cfg.AuthProviders.JSON.File, - }, - "ldap": ldapConfigFromString(cfg.AuthProviders.LDAP), - "owncloudsql": map[string]interface{}{ - "dbusername": cfg.AuthProviders.OwnCloudSQL.DBUsername, - "dbpassword": cfg.AuthProviders.OwnCloudSQL.DBPassword, - "dbhost": cfg.AuthProviders.OwnCloudSQL.DBHost, - "dbport": cfg.AuthProviders.OwnCloudSQL.DBPort, - "dbname": cfg.AuthProviders.OwnCloudSQL.DBName, - "idp": cfg.AuthProviders.OwnCloudSQL.IDP, - "nobody": cfg.AuthProviders.OwnCloudSQL.Nobody, - "join_username": cfg.AuthProviders.OwnCloudSQL.JoinUsername, - "join_ownclouduuid": cfg.AuthProviders.OwnCloudSQL.JoinOwnCloudUUID, - }, - }, - }, - }, - }, - } - return rcfg -} - -// AuthBasicSutureService allows for the storage-authbasic command to be embedded and supervised by a suture supervisor tree. -type AuthBasicSutureService struct { - cfg *config.Config -} - -// NewAuthBasicSutureService creates a new store.AuthBasicSutureService -func NewAuthBasic(cfg *ociscfg.Config) suture.Service { - cfg.AuthBasic.Commons = cfg.Commons - return AuthBasicSutureService{ - cfg: cfg.AuthBasic, - } -} - -func (s AuthBasicSutureService) Serve(ctx context.Context) error { - f := &flag.FlagSet{} - cmdFlags := AuthBasic(s.cfg).Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if AuthBasic(s.cfg).Before != nil { - if err := AuthBasic(s.cfg).Before(cliCtx); err != nil { - return err - } - } - if err := AuthBasic(s.cfg).Action(cliCtx); err != nil { - return err - } - - return nil -} - -func ldapConfigFromString(cfg config.LDAPProvider) map[string]interface{} { - return map[string]interface{}{ - "uri": cfg.URI, - "cacert": cfg.CACert, - "insecure": cfg.Insecure, - "bind_username": cfg.BindDN, - "bind_password": cfg.BindPassword, - "user_base_dn": cfg.UserBaseDN, - "group_base_dn": cfg.GroupBaseDN, - "user_filter": cfg.UserFilter, - "group_filter": cfg.GroupFilter, - "user_scope": cfg.UserScope, - "group_scope": cfg.GroupScope, - "user_objectclass": cfg.UserObjectClass, - "group_objectclass": cfg.GroupObjectClass, - "login_attributes": cfg.LoginAttributes, - "idp": cfg.IDP, - "user_schema": map[string]interface{}{ - "id": cfg.UserSchema.ID, - "idIsOctetString": cfg.UserSchema.IDIsOctetString, - "mail": cfg.UserSchema.Mail, - "displayName": cfg.UserSchema.DisplayName, - "userName": cfg.UserSchema.Username, - }, - "group_schema": map[string]interface{}{ - "id": cfg.GroupSchema.ID, - "idIsOctetString": cfg.GroupSchema.IDIsOctetString, - "mail": cfg.GroupSchema.Mail, - "displayName": cfg.GroupSchema.DisplayName, - "groupName": cfg.GroupSchema.Groupname, - "member": cfg.GroupSchema.Member, - }, - } -} diff --git a/extensions/auth-basic/pkg/command/health.go b/extensions/auth-basic/pkg/command/health.go new file mode 100644 index 0000000000..877e1d0c1b --- /dev/null +++ b/extensions/auth-basic/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/auth-basic/pkg/config" + "github.com/owncloud/ocis/extensions/auth-basic/pkg/config/parser" + "github.com/owncloud/ocis/extensions/auth-basic/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/auth-basic/pkg/command/root.go b/extensions/auth-basic/pkg/command/root.go new file mode 100644 index 0000000000..735941988a --- /dev/null +++ b/extensions/auth-basic/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/auth-basic/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-auth-basic command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "auth-basic", + Usage: "Provide basic authentication for oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the auth-basic command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new auth-basic.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.AuthBasic.Commons = cfg.Commons + return SutureService{ + cfg: cfg.AuthBasic, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/auth-basic/pkg/command/server.go b/extensions/auth-basic/pkg/command/server.go new file mode 100644 index 0000000000..642bb75903 --- /dev/null +++ b/extensions/auth-basic/pkg/command/server.go @@ -0,0 +1,120 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/auth-basic/pkg/config" + "github.com/owncloud/ocis/extensions/auth-basic/pkg/config/parser" + "github.com/owncloud/ocis/extensions/auth-basic/pkg/logging" + "github.com/owncloud/ocis/extensions/auth-basic/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/auth-basic/pkg/server/debug" + "github.com/owncloud/ocis/extensions/auth-basic/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/ldap" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/sync" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.AuthBasicConfigFromStruct(cfg) + + // the reva runtime calls os.Exit in the case of a failure and there is no way for the oCIS + // runtime to catch it and restart a reva service. Therefore we need to ensure the service has + // everything it needs, before starting the service. + // In this case: CA certificates + if cfg.AuthProvider == "ldap" { + ldapCfg := cfg.AuthProviders.LDAP + if err := ldap.WaitForCA(logger, ldapCfg.Insecure, ldapCfg.CACert); err != nil { + logger.Error().Err(err).Msg("The configured LDAP CA cert does not exist") + return err + } + } + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + if err := external.RegisterGRPCEndpoint( + ctx, + cfg.GRPC.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.GRPC.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/auth-basic/pkg/command/version.go b/extensions/auth-basic/pkg/command/version.go new file mode 100644 index 0000000000..f6c6415e74 --- /dev/null +++ b/extensions/auth-basic/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/auth-basic/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/auth-basic/pkg/config/config.go b/extensions/auth-basic/pkg/config/config.go index 2632ac9b16..b09148fb13 100644 --- a/extensions/auth-basic/pkg/config/config.go +++ b/extensions/auth-basic/pkg/config/config.go @@ -1,23 +1,29 @@ package config -import "github.com/owncloud/ocis/ocis-pkg/shared" +import ( + "context" + + "github.com/owncloud/ocis/ocis-pkg/shared" +) type Config struct { *shared.Commons `yaml:"-"` Service Service `yaml:"-"` Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` + Log *Log `yaml:"log"` Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` GRPC GRPCConfig `yaml:"grpc"` TokenManager *TokenManager `yaml:"token_manager"` Reva *Reva `yaml:"reva"` - SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token"` + SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"AUTH_BASIC_SKIP_USER_GROUPS_IN_TOKEN"` AuthProvider string `yaml:"auth_provider" env:"AUTH_BASIC_AUTH_PROVIDER" desc:"The auth provider which should be used by the service"` AuthProviders AuthProviders `yaml:"auth_providers"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` } type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;AUTH_BASIC_TRACING_ENABLED" desc:"Activates tracing."` @@ -26,7 +32,7 @@ type Tracing struct { Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;AUTH_BASIC_TRACING_COLLECTOR"` } -type Logging struct { +type Log struct { Level string `yaml:"level" env:"OCIS_LOG_LEVEL;AUTH_BASIC_LOG_LEVEL" desc:"The log level."` Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;AUTH_BASIC_LOG_PRETTY" desc:"Activates pretty log output."` Color bool `yaml:"color" env:"OCIS_LOG_COLOR;AUTH_BASIC_LOG_COLOR" desc:"Activates colorized log output."` @@ -45,66 +51,66 @@ type Debug struct { } type GRPCConfig struct { - Addr string `yaml:"addr" env:"AUTH_BASIC_GRPC_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"AUTH_BASIC_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` + Addr string `yaml:"addr" env:"AUTH_BASIC_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"AUTH_BASIC_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` } type AuthProviders struct { - JSON JSONProvider `yaml:"json"` LDAP LDAPProvider `yaml:"ldap"` - OwnCloudSQL OwnCloudSQLProvider `yaml:"owncloud_sql"` + OwnCloudSQL OwnCloudSQLProvider `yaml:"owncloudsql"` + JSON JSONProvider `yaml:"json,omitempty"` // not supported by the oCIS product, therefore not part of docs } type JSONProvider struct { - File string `yaml:"file" env:"AUTH_BASIC_JSON_PROVIDER_FILE" desc:"The file to which the json provider writes the data."` + File string `yaml:"file,omitempty"` } type LDAPProvider struct { - URI string `env:"LDAP_URI;AUTH_BASIC_LDAP_URI"` - CACert string `env:"LDAP_CACERT;AUTH_BASIC_LDAP_CACERT"` - Insecure bool `env:"LDAP_INSECURE;AUTH_BASIC_LDAP_INSECURE"` - BindDN string `env:"LDAP_BIND_DN;AUTH_BASIC_LDAP_BIND_DN"` - BindPassword string `yaml:"bind_password" env:"LDAP_BIND_PASSWORD;AUTH_BASIC_LDAP_BIND_PASSWORD"` - UserBaseDN string `env:"LDAP_USER_BASE_DN;AUTH_BASIC_LDAP_USER_BASE_DN"` - GroupBaseDN string `env:"LDAP_GROUP_BASE_DN;AUTH_BASIC_LDAP_GROUP_BASE_DN"` - UserScope string `env:"LDAP_USER_SCOPE;AUTH_BASIC_LDAP_USER_SCOPE"` - GroupScope string `env:"LDAP_GROUP_SCOPE;AUTH_BASIC_LDAP_GROUP_SCOPE"` - UserFilter string `env:"LDAP_USERFILTER;AUTH_BASIC_LDAP_USERFILTER"` - GroupFilter string `env:"LDAP_GROUPFILTER;AUTH_BASIC_LDAP_USERFILTER"` - UserObjectClass string `env:"LDAP_USER_OBJECTCLASS;AUTH_BASIC_LDAP_USER_OBJECTCLASS"` - GroupObjectClass string `env:"LDAP_GROUP_OBJECTCLASS;AUTH_BASIC_LDAP_GROUP_OBJECTCLASS"` - LoginAttributes []string `env:"LDAP_LOGIN_ATTRIBUTES;AUTH_BASIC_LDAP_LOGIN_ATTRIBUTES"` - IDP string `env:"OCIS_URL;AUTH_BASIC_IDP_URL"` // TODO what is this for? - GatewayEndpoint string // TODO do we need this here? - UserSchema LDAPUserSchema - GroupSchema LDAPGroupSchema + URI string `yaml:"uri" env:"LDAP_URI;AUTH_BASIC_LDAP_URI"` + CACert string `yaml:"ca_cert" env:"LDAP_CACERT;AUTH_BASIC_LDAP_CACERT"` + Insecure bool `yaml:"insecure" env:"LDAP_INSECURE;AUTH_BASIC_LDAP_INSECURE"` + BindDN string `yaml:"bind_dn" env:"LDAP_BIND_DN;AUTH_BASIC_LDAP_BIND_DN"` + BindPassword string `yaml:"bind_password" env:"LDAP_BIND_PASSWORD;AUTH_BASIC_LDAP_BIND_PASSWORD"` + UserBaseDN string `yaml:"user_base_dn" env:"LDAP_USER_BASE_DN;AUTH_BASIC_LDAP_USER_BASE_DN"` + GroupBaseDN string `yaml:"group_base_dn" env:"LDAP_GROUP_BASE_DN;AUTH_BASIC_LDAP_GROUP_BASE_DN"` + UserScope string `yaml:"user_scope" env:"LDAP_USER_SCOPE;AUTH_BASIC_LDAP_USER_SCOPE"` + GroupScope string `yaml:"group_scope" env:"LDAP_GROUP_SCOPE;AUTH_BASIC_LDAP_GROUP_SCOPE"` + UserFilter string `yaml:"user_filter" env:"LDAP_USERFILTER;AUTH_BASIC_LDAP_USERFILTER"` + GroupFilter string `yaml:"group_filter" env:"LDAP_GROUPFILTER;AUTH_BASIC_LDAP_USERFILTER"` + UserObjectClass string `yaml:"user_object_filter" env:"LDAP_USER_OBJECTCLASS;AUTH_BASIC_LDAP_USER_OBJECTCLASS"` + GroupObjectClass string `yaml:"group_object_class" env:"LDAP_GROUP_OBJECTCLASS;AUTH_BASIC_LDAP_GROUP_OBJECTCLASS"` + LoginAttributes []string `yaml:"login_attributes" env:"LDAP_LOGIN_ATTRIBUTES;AUTH_BASIC_LDAP_LOGIN_ATTRIBUTES"` + IDP string `yaml:"idp" env:"OCIS_URL;AUTH_BASIC_IDP_URL"` + UserSchema LDAPUserSchema `yaml:"user_schema"` + GroupSchema LDAPGroupSchema `yaml:"group_schema"` } type LDAPUserSchema struct { - ID string `env:"LDAP_USER_SCHEMA_ID;AUTH_BASIC_LDAP_USER_SCHEMA_ID"` - IDIsOctetString bool `env:"LDAP_USER_SCHEMA_ID_IS_OCTETSTRING;AUTH_BASIC_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING"` - Mail string `env:"LDAP_USER_SCHEMA_MAIL;AUTH_BASIC_LDAP_USER_SCHEMA_MAIL"` - DisplayName string `env:"LDAP_USER_SCHEMA_DISPLAYNAME;AUTH_BASIC_LDAP_USER_SCHEMA_DISPLAYNAME"` - Username string `env:"LDAP_USER_SCHEMA_USERNAME;AUTH_BASIC_LDAP_USER_SCHEMA_USERNAME"` + ID string `yaml:"id" env:"LDAP_USER_SCHEMA_ID;AUTH_BASIC_LDAP_USER_SCHEMA_ID"` + IDIsOctetString bool `yaml:"id_is_octet_string" env:"LDAP_USER_SCHEMA_ID_IS_OCTETSTRING;AUTH_BASIC_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING"` + Mail string `yaml:"mail" env:"LDAP_USER_SCHEMA_MAIL;AUTH_BASIC_LDAP_USER_SCHEMA_MAIL"` + DisplayName string `yaml:"display_name" env:"LDAP_USER_SCHEMA_DISPLAYNAME;AUTH_BASIC_LDAP_USER_SCHEMA_DISPLAYNAME"` + Username string `yaml:"user_name" env:"LDAP_USER_SCHEMA_USERNAME;AUTH_BASIC_LDAP_USER_SCHEMA_USERNAME"` } type LDAPGroupSchema struct { - ID string `env:"LDAP_GROUP_SCHEMA_ID;AUTH_BASIC_LDAP_GROUP_SCHEMA_ID"` - IDIsOctetString bool `env:"LDAP_GROUP_SCHEMA_ID_IS_OCTETSTRING;AUTH_BASIC_LDAP_GROUP_SCHEMA_ID_IS_OCTETSTRING"` - Mail string `env:"LDAP_GROUP_SCHEMA_MAIL;AUTH_BASIC_LDAP_GROUP_SCHEMA_MAIL"` - DisplayName string `env:"LDAP_GROUP_SCHEMA_DISPLAYNAME;AUTH_BASIC_LDAP_GROUP_SCHEMA_DISPLAYNAME"` - Groupname string `env:"LDAP_GROUP_SCHEMA_GROUPNAME;AUTH_BASIC_LDAP_GROUP_SCHEMA_GROUPNAME"` - Member string `env:"LDAP_GROUP_SCHEMA_MEMBER;AUTH_BASIC_LDAP_GROUP_SCHEMA_MEMBER"` + ID string `yaml:"id" env:"LDAP_GROUP_SCHEMA_ID;AUTH_BASIC_LDAP_GROUP_SCHEMA_ID"` + IDIsOctetString bool `yaml:"id_is_octet_string" env:"LDAP_GROUP_SCHEMA_ID_IS_OCTETSTRING;AUTH_BASIC_LDAP_GROUP_SCHEMA_ID_IS_OCTETSTRING"` + Mail string `yaml:"mail" env:"LDAP_GROUP_SCHEMA_MAIL;AUTH_BASIC_LDAP_GROUP_SCHEMA_MAIL"` + DisplayName string `yaml:"display_name" env:"LDAP_GROUP_SCHEMA_DISPLAYNAME;AUTH_BASIC_LDAP_GROUP_SCHEMA_DISPLAYNAME"` + Groupname string `yaml:"group_name" env:"LDAP_GROUP_SCHEMA_GROUPNAME;AUTH_BASIC_LDAP_GROUP_SCHEMA_GROUPNAME"` + Member string `yaml:"member" env:"LDAP_GROUP_SCHEMA_MEMBER;AUTH_BASIC_LDAP_GROUP_SCHEMA_MEMBER"` } type OwnCloudSQLProvider struct { - DBUsername string - DBPassword string - DBHost string - DBPort int - DBName string - IDP string // TODO do we need this? - Nobody int64 // TODO what is this? - JoinUsername bool - JoinOwnCloudUUID bool + DBUsername string `yaml:"db_username" env:"AUTH_BASIC_OWNCLOUDSQL_DB_USERNAME"` + DBPassword string `yaml:"db_password" env:"AUTH_BASIC_OWNCLOUDSQL_DB_PASSWORD"` + DBHost string `yaml:"db_host" env:"AUTH_BASIC_OWNCLOUDSQL_DB_HOST"` + DBPort int `yaml:"db_port" env:"AUTH_BASIC_OWNCLOUDSQL_DB_PORT"` + DBName string `yaml:"db_name" env:"AUTH_BASIC_OWNCLOUDSQL_DB_NAME"` + IDP string `yaml:"idp" env:"AUTH_BASIC_OWNCLOUDSQL_IDP"` + Nobody int64 `yaml:"nobody" env:"AUTH_BASIC_OWNCLOUDSQL_NOBODY"` // TODO what is this? + JoinUsername bool `yaml:"join_username" env:"AUTH_BASIC_OWNCLOUDSQL_JOIN_USERNAME"` + JoinOwnCloudUUID bool `yaml:"join_owncloud_uuid" env:"AUTH_BASIC_OWNCLOUDSQL_JOIN_OWNCLOUD_UUID"` } diff --git a/extensions/auth-basic/pkg/config/defaults/defaultconfig.go b/extensions/auth-basic/pkg/config/defaults/defaultconfig.go index 3bfbaf800f..6224e1fb6b 100644 --- a/extensions/auth-basic/pkg/config/defaults/defaultconfig.go +++ b/extensions/auth-basic/pkg/config/defaults/defaultconfig.go @@ -23,8 +23,9 @@ func DefaultConfig() *config.Config { Zpages: false, }, GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9146", - Protocol: "tcp", + Addr: "127.0.0.1:9146", + Namespace: "com.owncloud.api", + Protocol: "tcp", }, Service: config.Service{ Name: "auth-basic", @@ -80,15 +81,15 @@ func DefaultConfig() *config.Config { func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/auth-basic/pkg/logging/logging.go b/extensions/auth-basic/pkg/logging/logging.go new file mode 100644 index 0000000000..3f997d8502 --- /dev/null +++ b/extensions/auth-basic/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/auth-basic/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/auth-basic/pkg/revaconfig/config.go b/extensions/auth-basic/pkg/revaconfig/config.go new file mode 100644 index 0000000000..59ea585f76 --- /dev/null +++ b/extensions/auth-basic/pkg/revaconfig/config.go @@ -0,0 +1,83 @@ +package revaconfig + +import "github.com/owncloud/ocis/extensions/auth-basic/pkg/config" + +// AuthBasicConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func AuthBasicConfigFromStruct(cfg *config.Config) map[string]interface{} { + rcfg := map[string]interface{}{ + "core": map[string]interface{}{ + "tracing_enabled": cfg.Tracing.Enabled, + "tracing_endpoint": cfg.Tracing.Endpoint, + "tracing_collector": cfg.Tracing.Collector, + "tracing_service_name": cfg.Service.Name, + }, + "shared": map[string]interface{}{ + "jwt_secret": cfg.TokenManager.JWTSecret, + "gatewaysvc": cfg.Reva.Address, + "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, + }, + "grpc": map[string]interface{}{ + "network": cfg.GRPC.Protocol, + "address": cfg.GRPC.Addr, + // TODO build services dynamically + "services": map[string]interface{}{ + "authprovider": map[string]interface{}{ + "auth_manager": cfg.AuthProvider, + "auth_managers": map[string]interface{}{ + "json": map[string]interface{}{ + "users": cfg.AuthProviders.JSON.File, + }, + "ldap": ldapConfigFromString(cfg.AuthProviders.LDAP), + "owncloudsql": map[string]interface{}{ + "dbusername": cfg.AuthProviders.OwnCloudSQL.DBUsername, + "dbpassword": cfg.AuthProviders.OwnCloudSQL.DBPassword, + "dbhost": cfg.AuthProviders.OwnCloudSQL.DBHost, + "dbport": cfg.AuthProviders.OwnCloudSQL.DBPort, + "dbname": cfg.AuthProviders.OwnCloudSQL.DBName, + "idp": cfg.AuthProviders.OwnCloudSQL.IDP, + "nobody": cfg.AuthProviders.OwnCloudSQL.Nobody, + "join_username": cfg.AuthProviders.OwnCloudSQL.JoinUsername, + "join_ownclouduuid": cfg.AuthProviders.OwnCloudSQL.JoinOwnCloudUUID, + }, + }, + }, + }, + }, + } + return rcfg +} + +func ldapConfigFromString(cfg config.LDAPProvider) map[string]interface{} { + return map[string]interface{}{ + "uri": cfg.URI, + "cacert": cfg.CACert, + "insecure": cfg.Insecure, + "bind_username": cfg.BindDN, + "bind_password": cfg.BindPassword, + "user_base_dn": cfg.UserBaseDN, + "group_base_dn": cfg.GroupBaseDN, + "user_filter": cfg.UserFilter, + "group_filter": cfg.GroupFilter, + "user_scope": cfg.UserScope, + "group_scope": cfg.GroupScope, + "user_objectclass": cfg.UserObjectClass, + "group_objectclass": cfg.GroupObjectClass, + "login_attributes": cfg.LoginAttributes, + "idp": cfg.IDP, + "user_schema": map[string]interface{}{ + "id": cfg.UserSchema.ID, + "idIsOctetString": cfg.UserSchema.IDIsOctetString, + "mail": cfg.UserSchema.Mail, + "displayName": cfg.UserSchema.DisplayName, + "userName": cfg.UserSchema.Username, + }, + "group_schema": map[string]interface{}{ + "id": cfg.GroupSchema.ID, + "idIsOctetString": cfg.GroupSchema.IDIsOctetString, + "mail": cfg.GroupSchema.Mail, + "displayName": cfg.GroupSchema.DisplayName, + "groupName": cfg.GroupSchema.Groupname, + "member": cfg.GroupSchema.Member, + }, + } +} diff --git a/extensions/auth-basic/pkg/server/debug/option.go b/extensions/auth-basic/pkg/server/debug/option.go new file mode 100644 index 0000000000..ed1658963c --- /dev/null +++ b/extensions/auth-basic/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/extensions/auth-basic/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Logger log.Logger + Context context.Context + Config *config.Config +} + +// newOptions initializes the available default options. +func newOptions(opts ...Option) Options { + opt := Options{} + + for _, o := range opts { + o(&opt) + } + + return opt +} + +// Logger provides a function to set the logger option. +func Logger(val log.Logger) Option { + return func(o *Options) { + o.Logger = val + } +} + +// Context provides a function to set the context option. +func Context(val context.Context) Option { + return func(o *Options) { + o.Context = val + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} diff --git a/extensions/auth-basic/pkg/server/debug/server.go b/extensions/auth-basic/pkg/server/debug/server.go new file mode 100644 index 0000000000..e6f813ac97 --- /dev/null +++ b/extensions/auth-basic/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/extensions/auth-basic/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/service/debug" + "github.com/owncloud/ocis/ocis-pkg/version" +) + +// Server initializes the debug service and server. +func Server(opts ...Option) (*http.Server, error) { + options := newOptions(opts...) + + return debug.NewService( + debug.Logger(options.Logger), + debug.Name(options.Config.Service.Name), + debug.Version(version.String), + debug.Address(options.Config.Debug.Addr), + debug.Token(options.Config.Debug.Token), + debug.Pprof(options.Config.Debug.Pprof), + debug.Zpages(options.Config.Debug.Zpages), + debug.Health(health(options.Config)), + debug.Ready(ready(options.Config)), + //debug.CorsAllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins), + //debug.CorsAllowedMethods(options.Config.HTTP.CORS.AllowedMethods), + //debug.CorsAllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders), + //debug.CorsAllowCredentials(options.Config.HTTP.CORS.AllowCredentials), + ), nil +} + +// health implements the health check. +func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} + +// ready implements the ready check. +func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} diff --git a/extensions/auth-basic/pkg/tracing/tracing.go b/extensions/auth-basic/pkg/tracing/tracing.go new file mode 100644 index 0000000000..3d6cd1b15d --- /dev/null +++ b/extensions/auth-basic/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/auth-basic/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/auth-bearer/Makefile b/extensions/auth-bearer/Makefile new file mode 100644 index 0000000000..cd958c461e --- /dev/null +++ b/extensions/auth-bearer/Makefile @@ -0,0 +1,37 @@ +SHELL := bash +NAME := auth-bearer + +include ../../.make/recursion.mk + +############ tooling ############ +ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI +include ../../.bingo/Variables.mk +endif + +############ go tooling ############ +include ../../.make/go.mk + +############ release ############ +include ../../.make/release.mk + +############ docs generate ############ +include ../../.make/docs.mk + +.PHONY: docs-generate +docs-generate: config-docs-generate + +############ generate ############ +include ../../.make/generate.mk + +.PHONY: ci-go-generate +ci-go-generate: # CI runs ci-node-generate automatically before this target + +.PHONY: ci-node-generate +ci-node-generate: + +############ licenses ############ +.PHONY: ci-node-check-licenses +ci-node-check-licenses: + +.PHONY: ci-node-save-licenses +ci-node-save-licenses: diff --git a/extensions/auth-bearer/cmd/auth-bearer/main.go b/extensions/auth-bearer/cmd/auth-bearer/main.go new file mode 100644 index 0000000000..024879e452 --- /dev/null +++ b/extensions/auth-bearer/cmd/auth-bearer/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/command" + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/auth-bearer/pkg/command/command.go b/extensions/auth-bearer/pkg/command/command.go deleted file mode 100644 index ea41172d27..0000000000 --- a/extensions/auth-bearer/pkg/command/command.go +++ /dev/null @@ -1,165 +0,0 @@ -package command - -import ( - "context" - "flag" - "fmt" - "os" - "path" - - "github.com/cs3org/reva/v2/cmd/revad/runtime" - "github.com/gofrs/uuid" - "github.com/oklog/run" - "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config" - "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config/parser" - "github.com/owncloud/ocis/extensions/storage/pkg/server/debug" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" -) - -// AuthBearer is the entrypoint for the auth-bearer command. -func AuthBearer(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "auth-bearer", - Usage: "start authprovider for bearer auth", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - uuid := uuid.Must(uuid.NewV4()) - pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+uuid.String()+".pid") - rcfg := authBearerConfigFromStruct(c, cfg) - - gr.Add(func() error { - runtime.RunWithOptions( - rcfg, - pidFile, - runtime.WithLogger(&logger.Logger), - ) - return nil - }, func(_ error) { - logger.Info(). - Str("server", c.Command.Name). - Msg("Shutting down server") - - cancel() - }) - - debugServer, err := debug.Server( - debug.Name(c.Command.Name+"-debug"), - debug.Addr(cfg.Debug.Addr), - debug.Logger(logger), - debug.Context(ctx), - debug.Pprof(cfg.Debug.Pprof), - debug.Zpages(cfg.Debug.Zpages), - debug.Token(cfg.Debug.Token), - ) - - if err != nil { - logger.Info().Err(err).Str("server", "debug").Msg("failed to initialize server") - return err - } - - gr.Add(debugServer.ListenAndServe, func(_ error) { - cancel() - }) - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() - }, - } -} - -// authBearerConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func authBearerConfigFromStruct(c *cli.Context, cfg *config.Config) map[string]interface{} { - return map[string]interface{}{ - "core": map[string]interface{}{ - "tracing_enabled": cfg.Tracing.Enabled, - "tracing_endpoint": cfg.Tracing.Endpoint, - "tracing_collector": cfg.Tracing.Collector, - "tracing_service_name": c.Command.Name, - }, - "shared": map[string]interface{}{ - "jwt_secret": cfg.TokenManager.JWTSecret, - "gatewaysvc": cfg.Reva.Address, - "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, - }, - "grpc": map[string]interface{}{ - "network": cfg.GRPC.Protocol, - "address": cfg.GRPC.Addr, - // TODO build services dynamically - "services": map[string]interface{}{ - "authprovider": map[string]interface{}{ - "auth_manager": cfg.AuthProvider, - "auth_managers": map[string]interface{}{ - "oidc": map[string]interface{}{ - "issuer": cfg.AuthProviders.OIDC.Issuer, - "insecure": cfg.AuthProviders.OIDC.Insecure, - "id_claim": cfg.AuthProviders.OIDC.IDClaim, - "uid_claim": cfg.AuthProviders.OIDC.UIDClaim, - "gid_claim": cfg.AuthProviders.OIDC.GIDClaim, - }, - }, - }, - }, - }, - } -} - -// AuthBearerSutureService allows for the storage-gateway command to be embedded and supervised by a suture supervisor tree. -type AuthBearerSutureService struct { - cfg *config.Config -} - -// NewAuthBearerSutureService creates a new gateway.AuthBearerSutureService -func NewAuthBearer(cfg *ociscfg.Config) suture.Service { - cfg.AuthBearer.Commons = cfg.Commons - return AuthBearerSutureService{ - cfg: cfg.AuthBearer, - } -} - -func (s AuthBearerSutureService) Serve(ctx context.Context) error { - cmd := AuthBearer(s.cfg) - f := &flag.FlagSet{} - cmdFlags := cmd.Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if cmd.Before != nil { - if err := cmd.Before(cliCtx); err != nil { - return err - } - } - if err := cmd.Action(cliCtx); err != nil { - return err - } - - return nil -} diff --git a/extensions/auth-bearer/pkg/command/health.go b/extensions/auth-bearer/pkg/command/health.go new file mode 100644 index 0000000000..c38aefdc71 --- /dev/null +++ b/extensions/auth-bearer/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config" + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config/parser" + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/auth-bearer/pkg/command/root.go b/extensions/auth-bearer/pkg/command/root.go new file mode 100644 index 0000000000..41431fd498 --- /dev/null +++ b/extensions/auth-bearer/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-auth-bearer command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "auth-bearer", + Usage: "Provide bearer authentication for oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the accounts command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new auth-bearer.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.AuthBearer.Commons = cfg.Commons + return SutureService{ + cfg: cfg.AuthBearer, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/auth-bearer/pkg/command/server.go b/extensions/auth-bearer/pkg/command/server.go new file mode 100644 index 0000000000..c4d5211180 --- /dev/null +++ b/extensions/auth-bearer/pkg/command/server.go @@ -0,0 +1,107 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config" + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config/parser" + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/logging" + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/server/debug" + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/sync" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.AuthBearerConfigFromStruct(cfg) + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + if err := external.RegisterGRPCEndpoint( + ctx, + cfg.GRPC.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.GRPC.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/auth-bearer/pkg/command/version.go b/extensions/auth-bearer/pkg/command/version.go new file mode 100644 index 0000000000..c90f60e08f --- /dev/null +++ b/extensions/auth-bearer/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/auth-bearer/pkg/config/config.go b/extensions/auth-bearer/pkg/config/config.go index 984ac37984..6d8fc3faf6 100644 --- a/extensions/auth-bearer/pkg/config/config.go +++ b/extensions/auth-bearer/pkg/config/config.go @@ -1,23 +1,29 @@ package config -import "github.com/owncloud/ocis/ocis-pkg/shared" +import ( + "context" + + "github.com/owncloud/ocis/ocis-pkg/shared" +) type Config struct { *shared.Commons `yaml:"-"` Service Service `yaml:"-"` Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` + Log *Log `yaml:"log"` Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` GRPC GRPCConfig `yaml:"grpc"` TokenManager *TokenManager `yaml:"token_manager"` Reva *Reva `yaml:"reva"` - SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token"` - AuthProvider string `yaml:"auth_provider" env:"AUTH_BEARER_AUTH_PROVIDER" desc:"The auth provider which should be used by the service"` - AuthProviders AuthProviders `yaml:"auth_providers"` + SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"AUTH_BEARER_SKIP_USER_GROUPS_IN_TOKEN"` + + OIDC OIDC `yaml:"oidc"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` } type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;AUTH_BEARER_TRACING_ENABLED" desc:"Activates tracing."` @@ -26,7 +32,7 @@ type Tracing struct { Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;AUTH_BEARER_TRACING_COLLECTOR"` } -type Logging struct { +type Log struct { Level string `yaml:"level" env:"OCIS_LOG_LEVEL;AUTH_BEARER_LOG_LEVEL" desc:"The log level."` Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;AUTH_BEARER_LOG_PRETTY" desc:"Activates pretty log output."` Color bool `yaml:"color" env:"OCIS_LOG_COLOR;AUTH_BEARER_LOG_COLOR" desc:"Activates colorized log output."` @@ -45,18 +51,15 @@ type Debug struct { } type GRPCConfig struct { - Addr string `yaml:"addr" env:"AUTH_BEARER_GRPC_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"AUTH_BEARER_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` + Addr string `yaml:"addr" env:"AUTH_BEARER_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"AUTH_BEARER_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` } -type AuthProviders struct { - OIDC OIDCProvider `yaml:"oidc"` -} - -type OIDCProvider struct { +type OIDC struct { Issuer string `yaml:"issuer" env:"OCIS_URL;AUTH_BEARER_OIDC_ISSUER"` Insecure bool `yaml:"insecure" env:"OCIS_INSECURE;AUTH_BEARER_OIDC_INSECURE"` - IDClaim string `yaml:"id_claim"` - UIDClaim string `yaml:"uid_claim"` - GIDClaim string `yaml:"gid_claim"` + IDClaim string `yaml:"id_claim" env:"AUTH_BEARER_OIDC_ID_CLAIM"` + UIDClaim string `yaml:"uid_claim" env:"AUTH_BEARER_OIDC_UID_CLAIM"` + GIDClaim string `yaml:"gid_claim" env:"AUTH_BEARER_OIDC_GID_CLAIM"` } diff --git a/extensions/auth-bearer/pkg/config/defaults/defaultconfig.go b/extensions/auth-bearer/pkg/config/defaults/defaultconfig.go index 59d0acd706..6c37acaf77 100644 --- a/extensions/auth-bearer/pkg/config/defaults/defaultconfig.go +++ b/extensions/auth-bearer/pkg/config/defaults/defaultconfig.go @@ -20,8 +20,9 @@ func DefaultConfig() *config.Config { Zpages: false, }, GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9148", - Protocol: "tcp", + Addr: "127.0.0.1:9148", + Namespace: "com.owncloud.api", + Protocol: "tcp", }, Service: config.Service{ Name: "auth-bearer", @@ -29,28 +30,25 @@ func DefaultConfig() *config.Config { Reva: &config.Reva{ Address: "127.0.0.1:9142", }, - AuthProvider: "ldap", - AuthProviders: config.AuthProviders{ - OIDC: config.OIDCProvider{ - Issuer: "https://localhost:9200", - Insecure: false, - IDClaim: "preferred_username", - }, + OIDC: config.OIDC{ + Issuer: "https://localhost:9200", + Insecure: false, + IDClaim: "preferred_username", }, } } func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/auth-bearer/pkg/logging/logging.go b/extensions/auth-bearer/pkg/logging/logging.go new file mode 100644 index 0000000000..c92d63022c --- /dev/null +++ b/extensions/auth-bearer/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/auth-bearer/pkg/revaconfig/config.go b/extensions/auth-bearer/pkg/revaconfig/config.go new file mode 100644 index 0000000000..bb88e4b088 --- /dev/null +++ b/extensions/auth-bearer/pkg/revaconfig/config.go @@ -0,0 +1,38 @@ +package revaconfig + +import "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config" + +// AuthBearerConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func AuthBearerConfigFromStruct(cfg *config.Config) map[string]interface{} { + return map[string]interface{}{ + "core": map[string]interface{}{ + "tracing_enabled": cfg.Tracing.Enabled, + "tracing_endpoint": cfg.Tracing.Endpoint, + "tracing_collector": cfg.Tracing.Collector, + "tracing_service_name": cfg.Service.Name, + }, + "shared": map[string]interface{}{ + "jwt_secret": cfg.TokenManager.JWTSecret, + "gatewaysvc": cfg.Reva.Address, + "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, + }, + "grpc": map[string]interface{}{ + "network": cfg.GRPC.Protocol, + "address": cfg.GRPC.Addr, + "services": map[string]interface{}{ + "authprovider": map[string]interface{}{ + "auth_manager": "oidc", + "auth_managers": map[string]interface{}{ + "oidc": map[string]interface{}{ + "issuer": cfg.OIDC.Issuer, + "insecure": cfg.OIDC.Insecure, + "id_claim": cfg.OIDC.IDClaim, + "uid_claim": cfg.OIDC.UIDClaim, + "gid_claim": cfg.OIDC.GIDClaim, + }, + }, + }, + }, + }, + } +} diff --git a/extensions/auth-bearer/pkg/server/debug/option.go b/extensions/auth-bearer/pkg/server/debug/option.go new file mode 100644 index 0000000000..8ed17ad269 --- /dev/null +++ b/extensions/auth-bearer/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Logger log.Logger + Context context.Context + Config *config.Config +} + +// newOptions initializes the available default options. +func newOptions(opts ...Option) Options { + opt := Options{} + + for _, o := range opts { + o(&opt) + } + + return opt +} + +// Logger provides a function to set the logger option. +func Logger(val log.Logger) Option { + return func(o *Options) { + o.Logger = val + } +} + +// Context provides a function to set the context option. +func Context(val context.Context) Option { + return func(o *Options) { + o.Context = val + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} diff --git a/extensions/auth-bearer/pkg/server/debug/server.go b/extensions/auth-bearer/pkg/server/debug/server.go new file mode 100644 index 0000000000..f125a12870 --- /dev/null +++ b/extensions/auth-bearer/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/service/debug" + "github.com/owncloud/ocis/ocis-pkg/version" +) + +// Server initializes the debug service and server. +func Server(opts ...Option) (*http.Server, error) { + options := newOptions(opts...) + + return debug.NewService( + debug.Logger(options.Logger), + debug.Name(options.Config.Service.Name), + debug.Version(version.String), + debug.Address(options.Config.Debug.Addr), + debug.Token(options.Config.Debug.Token), + debug.Pprof(options.Config.Debug.Pprof), + debug.Zpages(options.Config.Debug.Zpages), + debug.Health(health(options.Config)), + debug.Ready(ready(options.Config)), + //debug.CorsAllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins), + //debug.CorsAllowedMethods(options.Config.HTTP.CORS.AllowedMethods), + //debug.CorsAllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders), + //debug.CorsAllowCredentials(options.Config.HTTP.CORS.AllowCredentials), + ), nil +} + +// health implements the health check. +func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} + +// ready implements the ready check. +func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} diff --git a/extensions/auth-bearer/pkg/tracing/tracing.go b/extensions/auth-bearer/pkg/tracing/tracing.go new file mode 100644 index 0000000000..cb139893c7 --- /dev/null +++ b/extensions/auth-bearer/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/auth-machine/Makefile b/extensions/auth-machine/Makefile new file mode 100644 index 0000000000..e53ab3cbec --- /dev/null +++ b/extensions/auth-machine/Makefile @@ -0,0 +1,37 @@ +SHELL := bash +NAME := auth-machine + +include ../../.make/recursion.mk + +############ tooling ############ +ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI +include ../../.bingo/Variables.mk +endif + +############ go tooling ############ +include ../../.make/go.mk + +############ release ############ +include ../../.make/release.mk + +############ docs generate ############ +include ../../.make/docs.mk + +.PHONY: docs-generate +docs-generate: config-docs-generate + +############ generate ############ +include ../../.make/generate.mk + +.PHONY: ci-go-generate +ci-go-generate: # CI runs ci-node-generate automatically before this target + +.PHONY: ci-node-generate +ci-node-generate: + +############ licenses ############ +.PHONY: ci-node-check-licenses +ci-node-check-licenses: + +.PHONY: ci-node-save-licenses +ci-node-save-licenses: diff --git a/extensions/auth-machine/cmd/auth-machine/main.go b/extensions/auth-machine/cmd/auth-machine/main.go new file mode 100644 index 0000000000..2a29a75e6e --- /dev/null +++ b/extensions/auth-machine/cmd/auth-machine/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/auth-machine/pkg/command" + "github.com/owncloud/ocis/extensions/auth-machine/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/auth-machine/pkg/command/command.go b/extensions/auth-machine/pkg/command/command.go deleted file mode 100644 index 1ab91220af..0000000000 --- a/extensions/auth-machine/pkg/command/command.go +++ /dev/null @@ -1,163 +0,0 @@ -package command - -import ( - "context" - "flag" - "fmt" - "os" - "path" - - "github.com/cs3org/reva/v2/cmd/revad/runtime" - "github.com/gofrs/uuid" - "github.com/oklog/run" - "github.com/owncloud/ocis/extensions/auth-machine/pkg/config" - "github.com/owncloud/ocis/extensions/auth-machine/pkg/config/parser" - "github.com/owncloud/ocis/extensions/storage/pkg/server/debug" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" -) - -// AuthMachine is the entrypoint for the auth-machine command. -func AuthMachine(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "auth-machine", - Usage: "start authprovider for machine auth", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - uuid := uuid.Must(uuid.NewV4()) - pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+uuid.String()+".pid") - rcfg := authMachineConfigFromStruct(c, cfg) - - gr.Add(func() error { - runtime.RunWithOptions( - rcfg, - pidFile, - runtime.WithLogger(&logger.Logger), - ) - return nil - }, func(_ error) { - logger.Info(). - Str("server", c.Command.Name). - Msg("Shutting down server") - - cancel() - }) - - debugServer, err := debug.Server( - debug.Name(c.Command.Name+"-debug"), - debug.Addr(cfg.Debug.Addr), - debug.Logger(logger), - debug.Context(ctx), - debug.Pprof(cfg.Debug.Pprof), - debug.Zpages(cfg.Debug.Zpages), - debug.Token(cfg.Debug.Token), - ) - - if err != nil { - logger.Info().Err(err).Str("server", "debug").Msg("failed to initialize server") - return err - } - - gr.Add(debugServer.ListenAndServe, func(_ error) { - cancel() - }) - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() - }, - } -} - -// authMachineConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func authMachineConfigFromStruct(c *cli.Context, cfg *config.Config) map[string]interface{} { - return map[string]interface{}{ - "core": map[string]interface{}{ - "tracing_enabled": cfg.Tracing.Enabled, - "tracing_endpoint": cfg.Tracing.Endpoint, - "tracing_collector": cfg.Tracing.Collector, - "tracing_service_name": c.Command.Name, - }, - "shared": map[string]interface{}{ - "jwt_secret": cfg.TokenManager.JWTSecret, - "gatewaysvc": cfg.Reva.Address, - "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, - }, - "grpc": map[string]interface{}{ - "network": cfg.GRPC.Protocol, - "address": cfg.GRPC.Addr, - // TODO build services dynamically - "services": map[string]interface{}{ - "authprovider": map[string]interface{}{ - "auth_manager": "machine", - "auth_managers": map[string]interface{}{ - "machine": map[string]interface{}{ - "api_key": cfg.AuthProviders.Machine.APIKey, - "gateway_addr": cfg.Reva.Address, - }, - }, - }, - }, - }, - } -} - -// AuthMachineSutureService allows for the storage-gateway command to be embedded and supervised by a suture supervisor tree. -type AuthMachineSutureService struct { - cfg *config.Config -} - -// NewAuthMachineSutureService creates a new gateway.AuthMachineSutureService -func NewAuthMachine(cfg *ociscfg.Config) suture.Service { - cfg.AuthMachine.Commons = cfg.Commons - return AuthMachineSutureService{ - cfg: cfg.AuthMachine, - } -} - -func (s AuthMachineSutureService) Serve(ctx context.Context) error { - // s.cfg.Reva.AuthMachine.Context = ctx - cmd := AuthMachine(s.cfg) - f := &flag.FlagSet{} - cmdFlags := cmd.Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if cmd.Before != nil { - if err := cmd.Before(cliCtx); err != nil { - return err - } - } - if err := cmd.Action(cliCtx); err != nil { - return err - } - - return nil -} diff --git a/extensions/auth-machine/pkg/command/health.go b/extensions/auth-machine/pkg/command/health.go new file mode 100644 index 0000000000..ad86b25787 --- /dev/null +++ b/extensions/auth-machine/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/auth-machine/pkg/config" + "github.com/owncloud/ocis/extensions/auth-machine/pkg/config/parser" + "github.com/owncloud/ocis/extensions/auth-machine/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/auth-machine/pkg/command/root.go b/extensions/auth-machine/pkg/command/root.go new file mode 100644 index 0000000000..593d455702 --- /dev/null +++ b/extensions/auth-machine/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/auth-machine/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-auth-machine command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "auth-machine", + Usage: "Provide machine authentication for oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the auth-machine command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new auth-machine.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.AuthMachine.Commons = cfg.Commons + return SutureService{ + cfg: cfg.AuthMachine, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/auth-machine/pkg/command/server.go b/extensions/auth-machine/pkg/command/server.go new file mode 100644 index 0000000000..e65fc00041 --- /dev/null +++ b/extensions/auth-machine/pkg/command/server.go @@ -0,0 +1,107 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/auth-machine/pkg/config" + "github.com/owncloud/ocis/extensions/auth-machine/pkg/config/parser" + "github.com/owncloud/ocis/extensions/auth-machine/pkg/logging" + "github.com/owncloud/ocis/extensions/auth-machine/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/auth-machine/pkg/server/debug" + "github.com/owncloud/ocis/extensions/auth-machine/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/sync" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.AuthMachineConfigFromStruct(cfg) + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + if err := external.RegisterGRPCEndpoint( + ctx, + cfg.GRPC.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.GRPC.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/auth-machine/pkg/command/version.go b/extensions/auth-machine/pkg/command/version.go new file mode 100644 index 0000000000..b2977c0953 --- /dev/null +++ b/extensions/auth-machine/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/auth-machine/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/auth-machine/pkg/config/config.go b/extensions/auth-machine/pkg/config/config.go index 19ff424c9b..d78749ce54 100644 --- a/extensions/auth-machine/pkg/config/config.go +++ b/extensions/auth-machine/pkg/config/config.go @@ -1,23 +1,29 @@ package config -import "github.com/owncloud/ocis/ocis-pkg/shared" +import ( + "context" + + "github.com/owncloud/ocis/ocis-pkg/shared" +) type Config struct { *shared.Commons `yaml:"-"` Service Service `yaml:"-"` Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` + Log *Log `yaml:"log"` Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` GRPC GRPCConfig `yaml:"grpc"` TokenManager *TokenManager `yaml:"token_manager"` Reva *Reva `yaml:"reva"` - SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token"` - AuthProvider string `yaml:"auth_provider" env:"AUTH_MACHINE_AUTH_PROVIDER" desc:"The auth provider which should be used by the service"` - AuthProviders AuthProviders `yaml:"auth_providers"` + SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"AUTH_MACHINE_SKIP_USER_GROUPS_IN_TOKEN"` + + MachineAuthAPIKey string `yaml:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY;AUTH_MACHINE_API_KEY"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` } type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;AUTH_MACHINE_TRACING_ENABLED" desc:"Activates tracing."` @@ -26,7 +32,7 @@ type Tracing struct { Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;AUTH_MACHINE_TRACING_COLLECTOR"` } -type Logging struct { +type Log struct { Level string `yaml:"level" env:"OCIS_LOG_LEVEL;AUTH_MACHINE_LOG_LEVEL" desc:"The log level."` Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;AUTH_MACHINE_LOG_PRETTY" desc:"Activates pretty log output."` Color bool `yaml:"color" env:"OCIS_LOG_COLOR;AUTH_MACHINE_LOG_COLOR" desc:"Activates colorized log output."` @@ -45,14 +51,7 @@ type Debug struct { } type GRPCConfig struct { - Addr string `yaml:"addr" env:"AUTH_MACHINE_GRPC_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"AUTH_MACHINE_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` -} - -type AuthProviders struct { - Machine MachineProvider `yaml:"machine"` -} - -type MachineProvider struct { - APIKey string `yaml:"api_key" env:"OCIS_MACHINE_AUTH_API_KEY;AUTH_MACHINE_PROVIDER_API_KEY" desc:"The api key for the machine auth provider."` + Addr string `yaml:"addr" env:"AUTH_MACHINE_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"AUTH_MACHINE_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` } diff --git a/extensions/auth-machine/pkg/config/defaults/defaultconfig.go b/extensions/auth-machine/pkg/config/defaults/defaultconfig.go index 47b0f1a16a..6f156446b4 100644 --- a/extensions/auth-machine/pkg/config/defaults/defaultconfig.go +++ b/extensions/auth-machine/pkg/config/defaults/defaultconfig.go @@ -20,8 +20,9 @@ func DefaultConfig() *config.Config { Zpages: false, }, GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9166", - Protocol: "tcp", + Addr: "127.0.0.1:9166", + Namespace: "com.owncloud.api", + Protocol: "tcp", }, Service: config.Service{ Name: "auth-machine", @@ -29,21 +30,20 @@ func DefaultConfig() *config.Config { Reva: &config.Reva{ Address: "127.0.0.1:9142", }, - AuthProvider: "ldap", } } func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { @@ -73,8 +73,8 @@ func EnsureDefaults(cfg *config.Config) { cfg.TokenManager = &config.TokenManager{} } - if cfg.AuthProviders.Machine.APIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" { - cfg.AuthProviders.Machine.APIKey = cfg.Commons.MachineAuthAPIKey + if cfg.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" { + cfg.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey } } diff --git a/extensions/auth-machine/pkg/config/parser/parse.go b/extensions/auth-machine/pkg/config/parser/parse.go index 2eb535806b..00a46b8469 100644 --- a/extensions/auth-machine/pkg/config/parser/parse.go +++ b/extensions/auth-machine/pkg/config/parser/parse.go @@ -38,7 +38,7 @@ func Validate(cfg *config.Config) error { return shared.MissingJWTTokenError(cfg.Service.Name) } - if cfg.AuthProviders.Machine.APIKey == "" { + if cfg.MachineAuthAPIKey == "" { return shared.MissingMachineAuthApiKeyError(cfg.Service.Name) } return nil diff --git a/extensions/auth-machine/pkg/logging/logging.go b/extensions/auth-machine/pkg/logging/logging.go new file mode 100644 index 0000000000..0d89a6f4b9 --- /dev/null +++ b/extensions/auth-machine/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/auth-machine/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/auth-machine/pkg/revaconfig/config.go b/extensions/auth-machine/pkg/revaconfig/config.go new file mode 100644 index 0000000000..c4a1514ec1 --- /dev/null +++ b/extensions/auth-machine/pkg/revaconfig/config.go @@ -0,0 +1,37 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/extensions/auth-machine/pkg/config" +) + +// AuthMachineConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func AuthMachineConfigFromStruct(cfg *config.Config) map[string]interface{} { + return map[string]interface{}{ + "core": map[string]interface{}{ + "tracing_enabled": cfg.Tracing.Enabled, + "tracing_endpoint": cfg.Tracing.Endpoint, + "tracing_collector": cfg.Tracing.Collector, + "tracing_service_name": cfg.Service.Name, + }, + "shared": map[string]interface{}{ + "jwt_secret": cfg.TokenManager.JWTSecret, + "gatewaysvc": cfg.Reva.Address, + "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, + }, + "grpc": map[string]interface{}{ + "network": cfg.GRPC.Protocol, + "address": cfg.GRPC.Addr, + "services": map[string]interface{}{ + "authprovider": map[string]interface{}{ + "auth_manager": "machine", + "auth_managers": map[string]interface{}{ + "machine": map[string]interface{}{ + "api_key": cfg.MachineAuthAPIKey, + "gateway_addr": cfg.Reva.Address, + }, + }, + }, + }, + }, + } +} diff --git a/extensions/auth-machine/pkg/server/debug/option.go b/extensions/auth-machine/pkg/server/debug/option.go new file mode 100644 index 0000000000..5666e1b3cc --- /dev/null +++ b/extensions/auth-machine/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/extensions/auth-machine/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Logger log.Logger + Context context.Context + Config *config.Config +} + +// newOptions initializes the available default options. +func newOptions(opts ...Option) Options { + opt := Options{} + + for _, o := range opts { + o(&opt) + } + + return opt +} + +// Logger provides a function to set the logger option. +func Logger(val log.Logger) Option { + return func(o *Options) { + o.Logger = val + } +} + +// Context provides a function to set the context option. +func Context(val context.Context) Option { + return func(o *Options) { + o.Context = val + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} diff --git a/extensions/auth-machine/pkg/server/debug/server.go b/extensions/auth-machine/pkg/server/debug/server.go new file mode 100644 index 0000000000..b523773ce9 --- /dev/null +++ b/extensions/auth-machine/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/extensions/auth-machine/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/service/debug" + "github.com/owncloud/ocis/ocis-pkg/version" +) + +// Server initializes the debug service and server. +func Server(opts ...Option) (*http.Server, error) { + options := newOptions(opts...) + + return debug.NewService( + debug.Logger(options.Logger), + debug.Name(options.Config.Service.Name), + debug.Version(version.String), + debug.Address(options.Config.Debug.Addr), + debug.Token(options.Config.Debug.Token), + debug.Pprof(options.Config.Debug.Pprof), + debug.Zpages(options.Config.Debug.Zpages), + debug.Health(health(options.Config)), + debug.Ready(ready(options.Config)), + //debug.CorsAllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins), + //debug.CorsAllowedMethods(options.Config.HTTP.CORS.AllowedMethods), + //debug.CorsAllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders), + //debug.CorsAllowCredentials(options.Config.HTTP.CORS.AllowCredentials), + ), nil +} + +// health implements the health check. +func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} + +// ready implements the ready check. +func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} diff --git a/extensions/auth-machine/pkg/tracing/tracing.go b/extensions/auth-machine/pkg/tracing/tracing.go new file mode 100644 index 0000000000..3536995a76 --- /dev/null +++ b/extensions/auth-machine/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/auth-machine/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/frontend/Makefile b/extensions/frontend/Makefile new file mode 100644 index 0000000000..d44b3161df --- /dev/null +++ b/extensions/frontend/Makefile @@ -0,0 +1,37 @@ +SHELL := bash +NAME := frontend + +include ../../.make/recursion.mk + +############ tooling ############ +ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI +include ../../.bingo/Variables.mk +endif + +############ go tooling ############ +include ../../.make/go.mk + +############ release ############ +include ../../.make/release.mk + +############ docs generate ############ +include ../../.make/docs.mk + +.PHONY: docs-generate +docs-generate: config-docs-generate + +############ generate ############ +include ../../.make/generate.mk + +.PHONY: ci-go-generate +ci-go-generate: # CI runs ci-node-generate automatically before this target + +.PHONY: ci-node-generate +ci-node-generate: + +############ licenses ############ +.PHONY: ci-node-check-licenses +ci-node-check-licenses: + +.PHONY: ci-node-save-licenses +ci-node-save-licenses: diff --git a/extensions/frontend/cmd/frontend/main.go b/extensions/frontend/cmd/frontend/main.go new file mode 100644 index 0000000000..a6135fa78a --- /dev/null +++ b/extensions/frontend/cmd/frontend/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/frontend/pkg/command" + "github.com/owncloud/ocis/extensions/frontend/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/frontend/pkg/command/health.go b/extensions/frontend/pkg/command/health.go new file mode 100644 index 0000000000..a4cedd367f --- /dev/null +++ b/extensions/frontend/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/frontend/pkg/config" + "github.com/owncloud/ocis/extensions/frontend/pkg/config/parser" + "github.com/owncloud/ocis/extensions/frontend/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/frontend/pkg/command/root.go b/extensions/frontend/pkg/command/root.go new file mode 100644 index 0000000000..4905363b29 --- /dev/null +++ b/extensions/frontend/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/frontend/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-frontend command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "frontend", + Usage: "Provide various ownCloud apis for oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the frontend command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new frontend.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.Frontend.Commons = cfg.Commons + return SutureService{ + cfg: cfg.Frontend, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/frontend/pkg/command/server.go b/extensions/frontend/pkg/command/server.go new file mode 100644 index 0000000000..88631228a2 --- /dev/null +++ b/extensions/frontend/pkg/command/server.go @@ -0,0 +1,107 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/frontend/pkg/config" + "github.com/owncloud/ocis/extensions/frontend/pkg/config/parser" + "github.com/owncloud/ocis/extensions/frontend/pkg/logging" + "github.com/owncloud/ocis/extensions/frontend/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/frontend/pkg/server/debug" + "github.com/owncloud/ocis/extensions/frontend/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/sync" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.FrontendConfigFromStruct(cfg) + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + if err := external.RegisterHTTPEndpoint( + ctx, + cfg.HTTP.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.HTTP.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the http endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/frontend/pkg/command/version.go b/extensions/frontend/pkg/command/version.go new file mode 100644 index 0000000000..d721db1236 --- /dev/null +++ b/extensions/frontend/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/frontend/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.HTTP.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/frontend/pkg/config/config.go b/extensions/frontend/pkg/config/config.go index a82e1fd42d..5029c44560 100644 --- a/extensions/frontend/pkg/config/config.go +++ b/extensions/frontend/pkg/config/config.go @@ -1,14 +1,17 @@ package config -import "github.com/owncloud/ocis/ocis-pkg/shared" +import ( + "context" + + "github.com/owncloud/ocis/ocis-pkg/shared" +) type Config struct { *shared.Commons `yaml:"-"` Service Service `yaml:"-"` Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` + Log *Log `yaml:"log"` Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` HTTP HTTPConfig `yaml:"http"` @@ -20,24 +23,26 @@ type Config struct { Reva *Reva `yaml:"reva"` MachineAuthAPIKey string `yaml:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY;FRONTEND_MACHINE_AUTH_API_KEY"` - SkipUserGroupsInToken bool `yaml:"skip_users_groups_in_token"` + SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"FRONTEND_SKIP_USER_GROUPS_IN_TOKEN"` - EnableFavorites bool `yaml:"favorites"` + EnableFavorites bool `yaml:"enable_favorites" env:"FRONTEND_ENABLE_FAVORITES"` EnableProjectSpaces bool `yaml:"enable_project_spaces" env:"FRONTEND_ENABLE_PROJECT_SPACES" desc:"Indicates to clients that project spaces are supposed to be made available."` EnableShareJail bool `yaml:"enable_share_jail" env:"FRONTEND_ENABLE_SHARE_JAIL" desc:"Indicates to clients that the share jail is supposed to be used."` - UploadMaxChunkSize int `yaml:"upload_max_chunk_size"` - UploadHTTPMethodOverride string `yaml:"upload_http_method_override"` - DefaultUploadProtocol string `yaml:"default_upload_protocol"` + UploadMaxChunkSize int `yaml:"upload_max_chunk_size" env:"FRONTEND_UPLOAD_MAX_CHUNK_SIZE"` + UploadHTTPMethodOverride string `yaml:"upload_http_method_override" env:"FRONTEND_UPLOAD_HTTP_METHOD_OVERRIDE"` + DefaultUploadProtocol string `yaml:"default_upload_protocol" env:"FRONTEND_DEFAULT_UPLOAD_PROTOCOL"` PublicURL string `yaml:"public_url" env:"OCIS_URL;FRONTEND_PUBLIC_URL"` Archiver Archiver `yaml:"archiver"` - AppProvider AppProvider `yaml:"app_provider"` DataGateway DataGateway `yaml:"data_gateway"` OCS OCS `yaml:"ocs"` Checksums Checksums `yaml:"checksums"` Middleware Middleware `yaml:"middleware"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` } type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;FRONTEND_TRACING_ENABLED" desc:"Activates tracing."` @@ -46,7 +51,7 @@ type Tracing struct { Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;FRONTEND_TRACING_COLLECTOR"` } -type Logging struct { +type Log struct { Level string `yaml:"level" env:"OCIS_LOG_LEVEL;FRONTEND_LOG_LEVEL" desc:"The log level."` Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;FRONTEND_LOG_PRETTY" desc:"Activates pretty log output."` Color bool `yaml:"color" env:"OCIS_LOG_COLOR;FRONTEND_LOG_COLOR" desc:"Activates colorized log output."` @@ -65,9 +70,10 @@ type Debug struct { } type HTTPConfig struct { - Addr string `yaml:"addr" env:"FRONTEND_HTTP_ADDR" desc:"The address of the http service."` - Protocol string `yaml:"protocol" env:"FRONTEND_HTTP_PROTOCOL" desc:"The transport protocol of the http service."` - Prefix string `yaml:"prefix"` + Addr string `yaml:"addr" env:"FRONTEND_HTTP_ADDR" desc:"The address of the http service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"FRONTEND_HTTP_PROTOCOL" desc:"The transport protocol of the http service."` + Prefix string `yaml:"prefix" env:"FRONTEND_HTTP_PREFIX"` } // Middleware configures reva middlewares. @@ -81,51 +87,40 @@ type Auth struct { } type Archiver struct { - MaxNumFiles int64 `yaml:"max_num_files"` - MaxSize int64 `yaml:"max_size"` + MaxNumFiles int64 `yaml:"max_num_files" env:"FRONTEND_ARCHIVER_MAX_NUM_FILES"` + MaxSize int64 `yaml:"max_size" env:"FRONTEND_ARCHIVER_MAX_SIZE"` Prefix string `yaml:"-"` Insecure bool `yaml:"insecure" env:"OCIS_INSECURE;FRONTEND_ARCHIVER_INSECURE"` } -type AppProvider struct { - ExternalAddr string `yaml:"external_addr"` - Driver string `yaml:"driver"` - // WopiDriver WopiDriver `yaml:"wopi_driver"` - AppsURL string `yaml:"-"` - OpenURL string `yaml:"-"` - NewURL string `yaml:"-"` - Prefix string `yaml:"-"` - Insecure bool `yaml:"insecure" env:"OCIS_INSECURE;FRONTEND_APPPROVIDER_INSECURE"` -} - type DataGateway struct { - Prefix string + Prefix string `yaml:"prefix" env:"FRONTEND_DATA_GATEWAY_PREFIX"` } type OCS struct { - Prefix string `yaml:"prefix"` - SharePrefix string `yaml:"share_prefix"` - HomeNamespace string `yaml:"home_namespace"` - AdditionalInfoAttribute string `yaml:"additional_info_attribute"` - ResourceInfoCacheTTL int `yaml:"resource_info_cache_ttl"` - CacheWarmupDriver string `yaml:"cache_warmup_driver"` - CacheWarmupDrivers CacheWarmupDrivers + Prefix string `yaml:"prefix" env:"FRONTEND_OCS_PREFIX"` + SharePrefix string `yaml:"share_prefix" env:"FRONTEND_OCS_SHARE_PREFIX"` + HomeNamespace string `yaml:"home_namespace" env:"FRONTEND_OCS_HOME_NAMESPACE"` + AdditionalInfoAttribute string `yaml:"additional_info_attribute" env:"FRONTEND_OCS_ADDITIONAL_INFO_ATTRIBUTE"` + ResourceInfoCacheTTL int `yaml:"resource_info_cache_ttl" env:"FRONTEND_OCS_RESOURCE_INFO_CACHE_TTL"` + CacheWarmupDriver string `yaml:"cache_warmup_driver,omitempty"` // not supported by the oCIS product, therefore not part of docs + CacheWarmupDrivers CacheWarmupDrivers `yaml:"cache_warmup_drivers,omitempty"` // not supported by the oCIS product, therefore not part of docs } type CacheWarmupDrivers struct { - CBOX CBOXDriver + CBOX CBOXDriver `yaml:"cbox,omitempty"` } type CBOXDriver struct { - DBUsername string - DBPassword string - DBHost string - DBPort int - DBName string - Namespace string + DBUsername string `yaml:"db_username,omitempty"` + DBPassword string `yaml:"db_password,omitempty"` + DBHost string `yaml:"db_host,omitempty"` + DBPort int `yaml:"db_port,omitempty"` + DBName string `yaml:"db_name,omitempty"` + Namespace string `yaml:"namespace,omitempty"` } type Checksums struct { - SupportedTypes []string `yaml:"supported_types"` - PreferredUploadType string `yaml:"preferred_upload_type"` + SupportedTypes []string `yaml:"supported_types" env:"FRONTEND_CHECKSUMS_SUPPORTED_TYPES"` + PreferredUploadType string `yaml:"preferred_upload_type" env:"FRONTEND_CHECKSUMS_PREFERRED_UPLOAD_TYPES"` } diff --git a/extensions/frontend/pkg/config/defaults/defaultconfig.go b/extensions/frontend/pkg/config/defaults/defaultconfig.go index a1067f1eb5..eec7998649 100644 --- a/extensions/frontend/pkg/config/defaults/defaultconfig.go +++ b/extensions/frontend/pkg/config/defaults/defaultconfig.go @@ -20,9 +20,10 @@ func DefaultConfig() *config.Config { Zpages: false, }, HTTP: config.HTTPConfig{ - Addr: "127.0.0.1:9140", - Protocol: "tcp", - Prefix: "", + Addr: "127.0.0.1:9140", + Namespace: "com.owncloud.web", + Protocol: "tcp", + Prefix: "", }, Service: config.Service{ Name: "frontend", @@ -41,10 +42,6 @@ func DefaultConfig() *config.Config { SupportedTypes: []string{"sha1", "md5", "adler32"}, PreferredUploadType: "", }, - AppProvider: config.AppProvider{ - Prefix: "", - Insecure: false, - }, Archiver: config.Archiver{ Insecure: false, Prefix: "archiver", @@ -58,7 +55,6 @@ func DefaultConfig() *config.Config { Prefix: "ocs", SharePrefix: "/Shares", HomeNamespace: "/users/{{.Id.OpaqueId}}", - CacheWarmupDriver: "", AdditionalInfoAttribute: "{{.Mail}}", ResourceInfoCacheTTL: 0, }, @@ -72,15 +68,15 @@ func DefaultConfig() *config.Config { func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/frontend/pkg/logging/logging.go b/extensions/frontend/pkg/logging/logging.go new file mode 100644 index 0000000000..19c43464d9 --- /dev/null +++ b/extensions/frontend/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/frontend/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/frontend/pkg/command/command.go b/extensions/frontend/pkg/revaconfig/config.go similarity index 54% rename from extensions/frontend/pkg/command/command.go rename to extensions/frontend/pkg/revaconfig/config.go index d14917d1c6..5fbb40c2ef 100644 --- a/extensions/frontend/pkg/command/command.go +++ b/extensions/frontend/pkg/revaconfig/config.go @@ -1,152 +1,62 @@ -package command +package revaconfig import ( - "context" - "flag" - "fmt" - "os" "path" "strconv" - "github.com/cs3org/reva/v2/cmd/revad/runtime" - "github.com/gofrs/uuid" - "github.com/oklog/run" "github.com/owncloud/ocis/extensions/frontend/pkg/config" - "github.com/owncloud/ocis/extensions/frontend/pkg/config/parser" - "github.com/owncloud/ocis/extensions/storage/pkg/server/debug" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" ) -// Frontend is the entrypoint for the frontend command. -func Frontend(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "frontend", - Usage: "start frontend service", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - //metrics = metrics.New() - - defer cancel() - - uuid := uuid.Must(uuid.NewV4()) - pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+uuid.String()+".pid") - - archivers := []map[string]interface{}{ - { - "enabled": true, - "version": "2.0.0", - "formats": []string{"tar", "zip"}, - "archiver_url": path.Join("/", cfg.Archiver.Prefix), - "max_num_files": strconv.FormatInt(cfg.Archiver.MaxNumFiles, 10), - "max_size": strconv.FormatInt(cfg.Archiver.MaxSize, 10), - }, - } - - appProviders := []map[string]interface{}{ - { - "enabled": true, - "version": "1.0.0", - "apps_url": cfg.AppProvider.AppsURL, - "open_url": cfg.AppProvider.OpenURL, - "new_url": cfg.AppProvider.NewURL, - }, - } - - filesCfg := map[string]interface{}{ - "private_links": false, - "bigfilechunking": false, - "blacklisted_files": []string{}, - "undelete": true, - "versioning": true, - "archivers": archivers, - "app_providers": appProviders, - "favorites": cfg.EnableFavorites, - } - - if cfg.DefaultUploadProtocol == "tus" { - filesCfg["tus_support"] = map[string]interface{}{ - "version": "1.0.0", - "resumable": "1.0.0", - "extension": "creation,creation-with-upload", - "http_method_override": cfg.UploadHTTPMethodOverride, - "max_chunk_size": cfg.UploadMaxChunkSize, - } - } - - revaCfg := frontendConfigFromStruct(c, cfg, filesCfg) - - gr.Add(func() error { - runtime.RunWithOptions(revaCfg, pidFile, runtime.WithLogger(&logger.Logger)) - return nil - }, func(_ error) { - logger.Info().Str("server", c.Command.Name).Msg("Shutting down server") - cancel() - }) - - { - server, err := debug.Server( - debug.Name(c.Command.Name+"-debug"), - debug.Addr(cfg.Debug.Addr), - debug.Logger(logger), - debug.Context(ctx), - debug.Pprof(cfg.Debug.Pprof), - debug.Zpages(cfg.Debug.Zpages), - debug.Token(cfg.Debug.Token), - ) - - if err != nil { - logger.Info(). - Err(err). - Str("server", "debug"). - Msg("Failed to initialize server") - - return err - } - - gr.Add(server.ListenAndServe, func(_ error) { - cancel() - }) - } - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() +// FrontendConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func FrontendConfigFromStruct(cfg *config.Config) map[string]interface{} { + archivers := []map[string]interface{}{ + { + "enabled": true, + "version": "2.0.0", + "formats": []string{"tar", "zip"}, + "archiver_url": path.Join("/", cfg.Archiver.Prefix), + "max_num_files": strconv.FormatInt(cfg.Archiver.MaxNumFiles, 10), + "max_size": strconv.FormatInt(cfg.Archiver.MaxSize, 10), }, } -} -// frontendConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func frontendConfigFromStruct(c *cli.Context, cfg *config.Config, filesCfg map[string]interface{}) map[string]interface{} { + appProviders := []map[string]interface{}{ + { + "enabled": true, + "version": "1.0.0", + "apps_url": "/app/list", + "open_url": "/app/open", + "new_url": "/app/new", + }, + } + + filesCfg := map[string]interface{}{ + "private_links": false, + "bigfilechunking": false, + "blacklisted_files": []string{}, + "undelete": true, + "versioning": true, + "archivers": archivers, + "app_providers": appProviders, + "favorites": cfg.EnableFavorites, + } + + if cfg.DefaultUploadProtocol == "tus" { + filesCfg["tus_support"] = map[string]interface{}{ + "version": "1.0.0", + "resumable": "1.0.0", + "extension": "creation,creation-with-upload", + "http_method_override": cfg.UploadHTTPMethodOverride, + "max_chunk_size": cfg.UploadMaxChunkSize, + } + } + return map[string]interface{}{ "core": map[string]interface{}{ "tracing_enabled": cfg.Tracing.Enabled, "tracing_endpoint": cfg.Tracing.Endpoint, "tracing_collector": cfg.Tracing.Collector, - "tracing_service_name": c.Command.Name, + "tracing_service_name": cfg.Service.Name, }, "shared": map[string]interface{}{ "jwt_secret": cfg.TokenManager.JWTSecret, @@ -167,12 +77,6 @@ func frontendConfigFromStruct(c *cli.Context, cfg *config.Config, filesCfg map[s }, // TODO build services dynamically "services": map[string]interface{}{ - "appprovider": map[string]interface{}{ - "prefix": cfg.AppProvider.Prefix, - "transfer_shared_secret": cfg.TransferSecret, - "timeout": 86400, - "insecure": cfg.AppProvider.Insecure, - }, "archiver": map[string]interface{}{ "prefix": cfg.Archiver.Prefix, "timeout": 86400, @@ -307,39 +211,3 @@ func frontendConfigFromStruct(c *cli.Context, cfg *config.Config, filesCfg map[s }, } } - -// FrontendSutureService allows for the storage-frontend command to be embedded and supervised by a suture supervisor tree. -type FrontendSutureService struct { - cfg *config.Config -} - -// NewFrontend creates a new frontend.FrontendSutureService -func NewFrontend(cfg *ociscfg.Config) suture.Service { - cfg.Frontend.Commons = cfg.Commons - return FrontendSutureService{ - cfg: cfg.Frontend, - } -} - -func (s FrontendSutureService) Serve(ctx context.Context) error { - // s.cfg.Reva.Frontend.Context = ctx - cmd := Frontend(s.cfg) - f := &flag.FlagSet{} - cmdFlags := cmd.Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if cmd.Before != nil { - if err := cmd.Before(cliCtx); err != nil { - return err - } - } - if err := cmd.Action(cliCtx); err != nil { - return err - } - - return nil -} diff --git a/extensions/frontend/pkg/server/debug/option.go b/extensions/frontend/pkg/server/debug/option.go new file mode 100644 index 0000000000..a46ead603a --- /dev/null +++ b/extensions/frontend/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/extensions/frontend/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Logger log.Logger + Context context.Context + Config *config.Config +} + +// newOptions initializes the available default options. +func newOptions(opts ...Option) Options { + opt := Options{} + + for _, o := range opts { + o(&opt) + } + + return opt +} + +// Logger provides a function to set the logger option. +func Logger(val log.Logger) Option { + return func(o *Options) { + o.Logger = val + } +} + +// Context provides a function to set the context option. +func Context(val context.Context) Option { + return func(o *Options) { + o.Context = val + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} diff --git a/extensions/frontend/pkg/server/debug/server.go b/extensions/frontend/pkg/server/debug/server.go new file mode 100644 index 0000000000..202c9c3058 --- /dev/null +++ b/extensions/frontend/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/extensions/frontend/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/service/debug" + "github.com/owncloud/ocis/ocis-pkg/version" +) + +// Server initializes the debug service and server. +func Server(opts ...Option) (*http.Server, error) { + options := newOptions(opts...) + + return debug.NewService( + debug.Logger(options.Logger), + debug.Name(options.Config.Service.Name), + debug.Version(version.String), + debug.Address(options.Config.Debug.Addr), + debug.Token(options.Config.Debug.Token), + debug.Pprof(options.Config.Debug.Pprof), + debug.Zpages(options.Config.Debug.Zpages), + debug.Health(health(options.Config)), + debug.Ready(ready(options.Config)), + //debug.CorsAllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins), + //debug.CorsAllowedMethods(options.Config.HTTP.CORS.AllowedMethods), + //debug.CorsAllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders), + //debug.CorsAllowCredentials(options.Config.HTTP.CORS.AllowCredentials), + ), nil +} + +// health implements the health check. +func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} + +// ready implements the ready check. +func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} diff --git a/extensions/frontend/pkg/tracing/tracing.go b/extensions/frontend/pkg/tracing/tracing.go new file mode 100644 index 0000000000..8ce5814dfc --- /dev/null +++ b/extensions/frontend/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/frontend/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/gateway/Makefile b/extensions/gateway/Makefile new file mode 100644 index 0000000000..f1130a590f --- /dev/null +++ b/extensions/gateway/Makefile @@ -0,0 +1,37 @@ +SHELL := bash +NAME := gateway + +include ../../.make/recursion.mk + +############ tooling ############ +ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI +include ../../.bingo/Variables.mk +endif + +############ go tooling ############ +include ../../.make/go.mk + +############ release ############ +include ../../.make/release.mk + +############ docs generate ############ +include ../../.make/docs.mk + +.PHONY: docs-generate +docs-generate: config-docs-generate + +############ generate ############ +include ../../.make/generate.mk + +.PHONY: ci-go-generate +ci-go-generate: # CI runs ci-node-generate automatically before this target + +.PHONY: ci-node-generate +ci-node-generate: + +############ licenses ############ +.PHONY: ci-node-check-licenses +ci-node-check-licenses: + +.PHONY: ci-node-save-licenses +ci-node-save-licenses: diff --git a/extensions/storage/cmd/storage/main.go b/extensions/gateway/cmd/gateway/main.go similarity index 52% rename from extensions/storage/cmd/storage/main.go rename to extensions/gateway/cmd/gateway/main.go index 2941f76e77..adcc697111 100644 --- a/extensions/storage/cmd/storage/main.go +++ b/extensions/gateway/cmd/gateway/main.go @@ -3,8 +3,8 @@ package main import ( "os" - "github.com/owncloud/ocis/extensions/storage/pkg/command" - "github.com/owncloud/ocis/extensions/storage/pkg/config/defaults" + "github.com/owncloud/ocis/extensions/gateway/pkg/command" + "github.com/owncloud/ocis/extensions/gateway/pkg/config/defaults" ) func main() { diff --git a/extensions/gateway/pkg/command/command.go b/extensions/gateway/pkg/command/command.go deleted file mode 100644 index 60440c2279..0000000000 --- a/extensions/gateway/pkg/command/command.go +++ /dev/null @@ -1,422 +0,0 @@ -package command - -import ( - "context" - "encoding/json" - "flag" - "fmt" - "io/ioutil" - "os" - "path" - "strings" - - "github.com/cs3org/reva/v2/cmd/revad/runtime" - "github.com/cs3org/reva/v2/pkg/utils" - "github.com/gofrs/uuid" - "github.com/mitchellh/mapstructure" - "github.com/oklog/run" - "github.com/owncloud/ocis/extensions/gateway/pkg/config" - "github.com/owncloud/ocis/extensions/gateway/pkg/config/parser" - "github.com/owncloud/ocis/extensions/storage/pkg/server/debug" - "github.com/owncloud/ocis/extensions/storage/pkg/service/external" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - "github.com/owncloud/ocis/ocis-pkg/version" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" -) - -// Gateway is the entrypoint for the gateway command. -func Gateway(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "gateway", - Usage: "start gateway", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - uuid := uuid.Must(uuid.NewV4()) - pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+uuid.String()+".pid") - rcfg := gatewayConfigFromStruct(c, cfg, logger) - logger.Debug(). - Str("server", "gateway"). - Interface("reva-config", rcfg). - Msg("config") - - defer cancel() - - gr.Add(func() error { - err := external.RegisterGRPCEndpoint( - ctx, - "com.owncloud.storage", - uuid.String(), - cfg.GRPC.Addr, - version.String, - logger, - ) - - if err != nil { - return err - } - - runtime.RunWithOptions( - rcfg, - pidFile, - runtime.WithLogger(&logger.Logger), - ) - return nil - }, func(_ error) { - logger.Info(). - Str("server", c.Command.Name). - Msg("Shutting down server") - - cancel() - }) - - debugServer, err := debug.Server( - debug.Name(c.Command.Name+"-debug"), - debug.Addr(cfg.Debug.Addr), - debug.Logger(logger), - debug.Context(ctx), - debug.Pprof(cfg.Debug.Pprof), - debug.Zpages(cfg.Debug.Zpages), - debug.Token(cfg.Debug.Token), - ) - - if err != nil { - logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") - return err - } - - gr.Add(debugServer.ListenAndServe, func(_ error) { - cancel() - }) - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() - }, - } -} - -// gatewayConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func gatewayConfigFromStruct(c *cli.Context, cfg *config.Config, logger log.Logger) map[string]interface{} { - rcfg := map[string]interface{}{ - "core": map[string]interface{}{ - "tracing_enabled": cfg.Tracing.Enabled, - "tracing_endpoint": cfg.Tracing.Endpoint, - "tracing_collector": cfg.Tracing.Collector, - "tracing_service_name": c.Command.Name, - }, - "shared": map[string]interface{}{ - "jwt_secret": cfg.TokenManager.JWTSecret, - "gatewaysvc": cfg.Reva.Address, - "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, - }, - "grpc": map[string]interface{}{ - "network": cfg.GRPC.Protocol, - "address": cfg.GRPC.Addr, - // TODO build services dynamically - "services": map[string]interface{}{ - "gateway": map[string]interface{}{ - // registries is located on the gateway - "authregistrysvc": cfg.Reva.Address, - "storageregistrysvc": cfg.Reva.Address, - "appregistrysvc": cfg.Reva.Address, - // user metadata is located on the users services - "preferencessvc": cfg.UsersEndpoint, - "userprovidersvc": cfg.UsersEndpoint, - "groupprovidersvc": cfg.GroupsEndpoint, - "permissionssvc": cfg.PermissionsEndpoint, - // sharing is located on the sharing service - "usershareprovidersvc": cfg.SharingEndpoint, - "publicshareprovidersvc": cfg.SharingEndpoint, - "ocmshareprovidersvc": cfg.SharingEndpoint, - "commit_share_to_storage_grant": cfg.CommitShareToStorageGrant, - "commit_share_to_storage_ref": cfg.CommitShareToStorageRef, - "share_folder": cfg.ShareFolder, // ShareFolder is the location where to create shares in the recipient's storage provider. - // other - "disable_home_creation_on_login": cfg.DisableHomeCreationOnLogin, - "datagateway": strings.TrimRight(cfg.FrontendPublicURL, "/") + "/data", - "transfer_shared_secret": cfg.TransferSecret, - "transfer_expires": cfg.TransferExpires, - "home_mapping": cfg.HomeMapping, - "etag_cache_ttl": cfg.EtagCacheTTL, - }, - "authregistry": map[string]interface{}{ - "driver": "static", - "drivers": map[string]interface{}{ - "static": map[string]interface{}{ - "rules": map[string]interface{}{ - "basic": cfg.AuthBasicEndpoint, - "bearer": cfg.AuthBearerEndpoint, - "machine": cfg.AuthMachineEndpoint, - "publicshares": cfg.StoragePublicLinkEndpoint, - }, - }, - }, - }, - "appregistry": map[string]interface{}{ - "driver": "static", - "drivers": map[string]interface{}{ - "static": map[string]interface{}{ - "mime_types": mimetypes(cfg, logger), - }, - }, - }, - "storageregistry": map[string]interface{}{ - "driver": cfg.StorageRegistry.Driver, - "drivers": map[string]interface{}{ - "spaces": map[string]interface{}{ - "providers": spacesProviders(cfg, logger), - }, - }, - }, - }, - }, - } - return rcfg -} - -func spacesProviders(cfg *config.Config, logger log.Logger) map[string]map[string]interface{} { - - // if a list of rules is given it overrides the generated rules from below - if len(cfg.StorageRegistry.Rules) > 0 { - rules := map[string]map[string]interface{}{} - for i := range cfg.StorageRegistry.Rules { - parts := strings.SplitN(cfg.StorageRegistry.Rules[i], "=", 2) - rules[parts[0]] = map[string]interface{}{"address": parts[1]} - } - return rules - } - - // check if the rules have to be read from a json file - if cfg.StorageRegistry.JSON != "" { - data, err := ioutil.ReadFile(cfg.StorageRegistry.JSON) - if err != nil { - logger.Error().Err(err).Msg("Failed to read storage registry rules from JSON file: " + cfg.StorageRegistry.JSON) - return nil - } - var rules map[string]map[string]interface{} - if err = json.Unmarshal(data, &rules); err != nil { - logger.Error().Err(err).Msg("Failed to unmarshal storage registry rules") - return nil - } - return rules - } - - // generate rules based on default config - return map[string]map[string]interface{}{ - cfg.StorageUsersEndpoint: { - "providerid": "1284d238-aa92-42ce-bdc4-0b0000009157", - "spaces": map[string]interface{}{ - "personal": map[string]interface{}{ - "mount_point": "/users", - "path_template": "/users/{{.Space.Owner.Id.OpaqueId}}", - }, - "project": map[string]interface{}{ - "mount_point": "/projects", - "path_template": "/projects/{{.Space.Name}}", - }, - }, - }, - cfg.StorageSharesEndpoint: { - "providerid": utils.ShareStorageProviderID, - "spaces": map[string]interface{}{ - "virtual": map[string]interface{}{ - // The root of the share jail is mounted here - "mount_point": "/users/{{.CurrentUser.Id.OpaqueId}}/Shares", - }, - "grant": map[string]interface{}{ - // Grants are relative to a space root that the gateway will determine with a stat - "mount_point": ".", - }, - "mountpoint": map[string]interface{}{ - // The jail needs to be filled with mount points - // .Space.Name is a path relative to the mount point - "mount_point": "/users/{{.CurrentUser.Id.OpaqueId}}/Shares", - "path_template": "/users/{{.CurrentUser.Id.OpaqueId}}/Shares/{{.Space.Name}}", - }, - }, - }, - // public link storage returns the mount id of the actual storage - cfg.StoragePublicLinkEndpoint: { - "providerid": utils.PublicStorageProviderID, - "spaces": map[string]interface{}{ - "grant": map[string]interface{}{ - "mount_point": ".", - }, - "mountpoint": map[string]interface{}{ - "mount_point": "/public", - "path_template": "/public/{{.Space.Root.OpaqueId}}", - }, - }, - }, - // medatada storage not part of the global namespace - } -} - -func mimetypes(cfg *config.Config, logger log.Logger) []map[string]interface{} { - - type mimeTypeConfig struct { - MimeType string `json:"mime_type" mapstructure:"mime_type"` - Extension string `json:"extension" mapstructure:"extension"` - Name string `json:"name" mapstructure:"name"` - Description string `json:"description" mapstructure:"description"` - Icon string `json:"icon" mapstructure:"icon"` - DefaultApp string `json:"default_app" mapstructure:"default_app"` - AllowCreation bool `json:"allow_creation" mapstructure:"allow_creation"` - } - var mimetypes []mimeTypeConfig - var m []map[string]interface{} - - // load default app mimetypes from a json file - if cfg.AppRegistry.MimetypesJSON != "" { - data, err := ioutil.ReadFile(cfg.AppRegistry.MimetypesJSON) - if err != nil { - logger.Error().Err(err).Msg("Failed to read app registry mimetypes from JSON file: " + cfg.AppRegistry.MimetypesJSON) - return nil - } - if err = json.Unmarshal(data, &mimetypes); err != nil { - logger.Error().Err(err).Msg("Failed to unmarshal storage registry rules") - return nil - } - if err := mapstructure.Decode(mimetypes, &m); err != nil { - logger.Error().Err(err).Msg("Failed to decode defaultapp registry mimetypes to mapstructure") - return nil - } - return m - } - - logger.Info().Msg("No app registry mimetypes JSON file provided, loading default configuration") - - mimetypes = []mimeTypeConfig{ - { - MimeType: "application/pdf", - Extension: "pdf", - Name: "PDF", - Description: "PDF document", - }, - { - MimeType: "application/vnd.oasis.opendocument.text", - Extension: "odt", - Name: "OpenDocument", - Description: "OpenDocument text document", - AllowCreation: true, - }, - { - MimeType: "application/vnd.oasis.opendocument.spreadsheet", - Extension: "ods", - Name: "OpenSpreadsheet", - Description: "OpenDocument spreadsheet document", - AllowCreation: true, - }, - { - MimeType: "application/vnd.oasis.opendocument.presentation", - Extension: "odp", - Name: "OpenPresentation", - Description: "OpenDocument presentation document", - AllowCreation: true, - }, - { - MimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - Extension: "docx", - Name: "Microsoft Word", - Description: "Microsoft Word document", - AllowCreation: true, - }, - { - MimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - Extension: "xlsx", - Name: "Microsoft Excel", - Description: "Microsoft Excel document", - AllowCreation: true, - }, - { - MimeType: "application/vnd.openxmlformats-officedocument.presentationml.presentation", - Extension: "pptx", - Name: "Microsoft PowerPoint", - Description: "Microsoft PowerPoint document", - AllowCreation: true, - }, - { - MimeType: "application/vnd.jupyter", - Extension: "ipynb", - Name: "Jupyter Notebook", - Description: "Jupyter Notebook", - }, - { - MimeType: "text/markdown", - Extension: "md", - Name: "Markdown file", - Description: "Markdown file", - AllowCreation: true, - }, - { - MimeType: "application/compressed-markdown", - Extension: "zmd", - Name: "Compressed markdown file", - Description: "Compressed markdown file", - }, - } - - if err := mapstructure.Decode(mimetypes, &m); err != nil { - logger.Error().Err(err).Msg("Failed to decode defaultapp registry mimetypes to mapstructure") - return nil - } - return m - -} - -// GatewaySutureService allows for the storage-gateway command to be embedded and supervised by a suture supervisor tree. -type GatewaySutureService struct { - cfg *config.Config -} - -// NewGatewaySutureService creates a new gateway.GatewaySutureService -func NewGateway(cfg *ociscfg.Config) suture.Service { - cfg.Gateway.Commons = cfg.Commons - return GatewaySutureService{ - cfg: cfg.Gateway, - } -} - -func (s GatewaySutureService) Serve(ctx context.Context) error { - cmd := Gateway(s.cfg) - f := &flag.FlagSet{} - cmdFlags := cmd.Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if cmd.Before != nil { - if err := cmd.Before(cliCtx); err != nil { - return err - } - } - if err := cmd.Action(cliCtx); err != nil { - return err - } - - return nil -} diff --git a/extensions/gateway/pkg/command/health.go b/extensions/gateway/pkg/command/health.go new file mode 100644 index 0000000000..b9bda87a55 --- /dev/null +++ b/extensions/gateway/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/gateway/pkg/config" + "github.com/owncloud/ocis/extensions/gateway/pkg/config/parser" + "github.com/owncloud/ocis/extensions/gateway/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/gateway/pkg/command/root.go b/extensions/gateway/pkg/command/root.go new file mode 100644 index 0000000000..a162c6d3d4 --- /dev/null +++ b/extensions/gateway/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/gateway/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-gateway command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "gateway", + Usage: "Provide a CS3api gateway for oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the gateway command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new gateway.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.Gateway.Commons = cfg.Commons + return SutureService{ + cfg: cfg.Gateway, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/gateway/pkg/command/server.go b/extensions/gateway/pkg/command/server.go new file mode 100644 index 0000000000..7caccfbb3a --- /dev/null +++ b/extensions/gateway/pkg/command/server.go @@ -0,0 +1,102 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/gateway/pkg/config" + "github.com/owncloud/ocis/extensions/gateway/pkg/config/parser" + "github.com/owncloud/ocis/extensions/gateway/pkg/logging" + "github.com/owncloud/ocis/extensions/gateway/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/gateway/pkg/server/debug" + "github.com/owncloud/ocis/extensions/gateway/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.GatewayConfigFromStruct(cfg, logger) + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if err := external.RegisterGRPCEndpoint( + ctx, + cfg.GRPC.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.GRPC.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/gateway/pkg/command/version.go b/extensions/gateway/pkg/command/version.go new file mode 100644 index 0000000000..efe2adaf7d --- /dev/null +++ b/extensions/gateway/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/gateway/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/gateway/pkg/config/config.go b/extensions/gateway/pkg/config/config.go index ca3555e721..8cd5229aa9 100644 --- a/extensions/gateway/pkg/config/config.go +++ b/extensions/gateway/pkg/config/config.go @@ -1,46 +1,53 @@ package config -import "github.com/owncloud/ocis/ocis-pkg/shared" +import ( + "context" + + "github.com/owncloud/ocis/ocis-pkg/shared" +) type Config struct { *shared.Commons `yaml:"-"` - Service Service `yaml:"-"` - Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` - Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` + Service Service `yaml:"-"` + Tracing *Tracing `yaml:"tracing"` + Log *Log `yaml:"log"` + Debug Debug `yaml:"debug"` GRPC GRPCConfig `yaml:"grpc"` TokenManager *TokenManager `yaml:"token_manager"` Reva *Reva `yaml:"reva"` - SkipUserGroupsInToken bool + SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"GATEWAY_SKIP_USER_GROUPS_IN_TOKEN"` - CommitShareToStorageGrant bool `yaml:"commit_share_to_storage_grant"` - CommitShareToStorageRef bool `yaml:"commit_share_to_storage_ref"` - ShareFolder string `yaml:"share_folder"` - DisableHomeCreationOnLogin bool `yaml:"disable_home_creation_on_login"` - TransferSecret string `yaml:"transfer_secret" env:"STORAGE_TRANSFER_SECRET"` - TransferExpires int `yaml:"transfer_expires"` - HomeMapping string `yaml:"home_mapping"` - EtagCacheTTL int `yaml:"etag_cache_ttl"` + CommitShareToStorageGrant bool `yaml:"commit_share_to_storage_grant" env:"GATEWAY_COMMIT_SHARE_TO_STORAGE_GRANT"` + CommitShareToStorageRef bool `yaml:"commit_share_to_storage_ref" env:"GATEWAY_COMMIT_SHARE_TO_STORAGE_REF"` + ShareFolder string `yaml:"share_folder_name" env:"GATEWAY_SHARE_FOLDER_NAME"` + DisableHomeCreationOnLogin bool `yaml:"disable_home_creation_on_login" env:"GATEWAY_DISABLE_HOME_CREATION_ON_LOGIN"` + TransferSecret string `yaml:"transfer_secret" env:"STORAGE_TRANSFER_SECRET"` // TODO: how to name the env + TransferExpires int `yaml:"transfer_expires" env:"GATEWAY_TRANSFER_EXPIRES"` + HomeMapping string `yaml:"home_mapping" env:"GATEWAY_HOME_MAPPING"` + EtagCacheTTL int `yaml:"etag_cache_ttl" env:"GATEWAY_ETAG_CACHE_TTL"` - UsersEndpoint string `yaml:"users_endpoint"` - GroupsEndpoint string `yaml:"groups_endpoint"` - PermissionsEndpoint string `yaml:"permissions_endpoint"` - SharingEndpoint string `yaml:"sharing_endpoint"` - FrontendPublicURL string `yaml:"frontend_public_url" env:"OCIS_URL;GATEWAY_FRONTEND_PUBLIC_URL"` - AuthBasicEndpoint string `yaml:"auth_basic_endpoint"` - AuthBearerEndpoint string `yaml:"auth_bearer_endpoint"` - AuthMachineEndpoint string `yaml:"auth_machine_endpoint"` - StoragePublicLinkEndpoint string `yaml:"storage_public_link_endpoint"` - StorageUsersEndpoint string `yaml:"storage_users_endpoint"` - StorageSharesEndpoint string `yaml:"storage_shares_endpoint"` + FrontendPublicURL string `yaml:"frontend_public_url" env:"OCIS_URL;GATEWAY_FRONTEND_PUBLIC_URL"` - StorageRegistry StorageRegistry `yaml:"storage_registry"` - AppRegistry AppRegistry `yaml:"app_registry"` + UsersEndpoint string `yaml:"users_endpoint" env:"GATEWAY_USERS_ENDPOINT"` + GroupsEndpoint string `yaml:"groups_endpoint" env:"GATEWAY_GROUPS_ENDPOINT"` + PermissionsEndpoint string `yaml:"permissions_endpoint" env:"GATEWAY_PERMISSIONS_ENDPOINT"` + SharingEndpoint string `yaml:"sharing_endpoint" env:"GATEWAY_SHARING_ENDPOINT"` + AuthBasicEndpoint string `yaml:"auth_basic_endpoint" env:"GATEWAY_AUTH_BASIC_ENDPOINT"` + AuthBearerEndpoint string `yaml:"auth_bearer_endpoint" env:"GATEWAY_AUTH_BEARER_ENDPOINT"` + AuthMachineEndpoint string `yaml:"auth_machine_endpoint" env:"GATEWAY_AUTH_MACHINE_ENDPOINT"` + StoragePublicLinkEndpoint string `yaml:"storage_public_link_endpoint" env:"GATEWAY_STORAGE_PUBLIC_LINK_ENDPOINT"` + StorageUsersEndpoint string `yaml:"storage_users_endpoint" env:"GATEWAY_STORAGE_USERS_ENDPOINT"` + StorageSharesEndpoint string `yaml:"storage_shares_endpoint" env:"GATEWAY_STORAGE_SHARES_ENDPOINT"` + AppRegistryEndpoint string `yaml:"app_registry_endpoint" env:"GATEWAY_APP_REGISTRY_ENDPOINT"` + + StorageRegistry StorageRegistry `yaml:"storage_registry"` //TODO: should we even support switching this? + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` } type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;GATEWAY_TRACING_ENABLED" desc:"Activates tracing."` @@ -49,7 +56,7 @@ type Tracing struct { Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;GATEWAY_TRACING_COLLECTOR"` } -type Logging struct { +type Log struct { Level string `yaml:"level" env:"OCIS_LOG_LEVEL;GATEWAY_LOG_LEVEL" desc:"The log level."` Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;GATEWAY_LOG_PRETTY" desc:"Activates pretty log output."` Color bool `yaml:"color" env:"OCIS_LOG_COLOR;GATEWAY_LOG_COLOR" desc:"Activates colorized log output."` @@ -68,16 +75,13 @@ type Debug struct { } type GRPCConfig struct { - Addr string `yaml:"addr" env:"GATEWAY_GRPC_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"GATEWAY_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` + Addr string `yaml:"addr" env:"GATEWAY_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"GATEWAY_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` } type StorageRegistry struct { - Driver string - Rules []string - JSON string -} - -type AppRegistry struct { - MimetypesJSON string + Driver string `yaml:"driver"` //TODO: configure via env? + Rules []string `yaml:"rules"` //TODO: configure via env? + JSON string `yaml:"json"` //TODO: configure via env? } diff --git a/extensions/gateway/pkg/config/defaults/defaultconfig.go b/extensions/gateway/pkg/config/defaults/defaultconfig.go index 21e3cc1862..3cc5b42116 100644 --- a/extensions/gateway/pkg/config/defaults/defaultconfig.go +++ b/extensions/gateway/pkg/config/defaults/defaultconfig.go @@ -20,8 +20,9 @@ func DefaultConfig() *config.Config { Zpages: false, }, GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9142", - Protocol: "tcp", + Addr: "127.0.0.1:9142", + Namespace: "com.owncloud.api", + Protocol: "tcp", }, Service: config.Service{ Name: "gateway", @@ -38,39 +39,38 @@ func DefaultConfig() *config.Config { HomeMapping: "", EtagCacheTTL: 0, - UsersEndpoint: "localhost:9144", - GroupsEndpoint: "localhost:9160", - PermissionsEndpoint: "localhost:9191", - SharingEndpoint: "localhost:9150", - FrontendPublicURL: "https://localhost:9200", + FrontendPublicURL: "https://localhost:9200", + + AppRegistryEndpoint: "localhost:9242", AuthBasicEndpoint: "localhost:9146", AuthBearerEndpoint: "localhost:9148", AuthMachineEndpoint: "localhost:9166", + GroupsEndpoint: "localhost:9160", + PermissionsEndpoint: "localhost:9191", + SharingEndpoint: "localhost:9150", StoragePublicLinkEndpoint: "localhost:9178", - StorageUsersEndpoint: "localhost:9157", StorageSharesEndpoint: "localhost:9154", + StorageUsersEndpoint: "localhost:9157", + UsersEndpoint: "localhost:9144", StorageRegistry: config.StorageRegistry{ Driver: "spaces", JSON: "", }, - AppRegistry: config.AppRegistry{ - MimetypesJSON: "", - }, } } func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/gateway/pkg/logging/logging.go b/extensions/gateway/pkg/logging/logging.go new file mode 100644 index 0000000000..4623c743c5 --- /dev/null +++ b/extensions/gateway/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/gateway/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/gateway/pkg/revaconfig/config.go b/extensions/gateway/pkg/revaconfig/config.go new file mode 100644 index 0000000000..eccd39f0a3 --- /dev/null +++ b/extensions/gateway/pkg/revaconfig/config.go @@ -0,0 +1,161 @@ +package revaconfig + +import ( + "encoding/json" + "io/ioutil" + "strings" + + "github.com/owncloud/ocis/ocis-pkg/log" + + "github.com/cs3org/reva/v2/pkg/utils" + "github.com/owncloud/ocis/extensions/gateway/pkg/config" +) + +// GatewayConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func GatewayConfigFromStruct(cfg *config.Config, logger log.Logger) map[string]interface{} { + rcfg := map[string]interface{}{ + "core": map[string]interface{}{ + "tracing_enabled": cfg.Tracing.Enabled, + "tracing_endpoint": cfg.Tracing.Endpoint, + "tracing_collector": cfg.Tracing.Collector, + "tracing_service_name": cfg.Service.Name, + }, + "shared": map[string]interface{}{ + "jwt_secret": cfg.TokenManager.JWTSecret, + "gatewaysvc": cfg.Reva.Address, + "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, + }, + "grpc": map[string]interface{}{ + "network": cfg.GRPC.Protocol, + "address": cfg.GRPC.Addr, + // TODO build services dynamically + "services": map[string]interface{}{ + "gateway": map[string]interface{}{ + // registries is located on the gateway + "authregistrysvc": cfg.Reva.Address, + "storageregistrysvc": cfg.Reva.Address, + "appregistrysvc": cfg.AppRegistryEndpoint, + // user metadata is located on the users services + "preferencessvc": cfg.UsersEndpoint, + "userprovidersvc": cfg.UsersEndpoint, + "groupprovidersvc": cfg.GroupsEndpoint, + "permissionssvc": cfg.PermissionsEndpoint, + // sharing is located on the sharing service + "usershareprovidersvc": cfg.SharingEndpoint, + "publicshareprovidersvc": cfg.SharingEndpoint, + "ocmshareprovidersvc": cfg.SharingEndpoint, + "commit_share_to_storage_grant": cfg.CommitShareToStorageGrant, + "commit_share_to_storage_ref": cfg.CommitShareToStorageRef, + "share_folder": cfg.ShareFolder, // ShareFolder is the location where to create shares in the recipient's storage provider. + // other + "disable_home_creation_on_login": cfg.DisableHomeCreationOnLogin, + "datagateway": strings.TrimRight(cfg.FrontendPublicURL, "/") + "/data", + "transfer_shared_secret": cfg.TransferSecret, + "transfer_expires": cfg.TransferExpires, + "home_mapping": cfg.HomeMapping, + "etag_cache_ttl": cfg.EtagCacheTTL, + }, + "authregistry": map[string]interface{}{ + "driver": "static", + "drivers": map[string]interface{}{ + "static": map[string]interface{}{ + "rules": map[string]interface{}{ + "basic": cfg.AuthBasicEndpoint, + "bearer": cfg.AuthBearerEndpoint, + "machine": cfg.AuthMachineEndpoint, + "publicshares": cfg.StoragePublicLinkEndpoint, + }, + }, + }, + }, + "storageregistry": map[string]interface{}{ + "driver": cfg.StorageRegistry.Driver, + "drivers": map[string]interface{}{ + "spaces": map[string]interface{}{ + "providers": spacesProviders(cfg, logger), + }, + }, + }, + }, + }, + } + return rcfg +} + +func spacesProviders(cfg *config.Config, logger log.Logger) map[string]map[string]interface{} { + + // if a list of rules is given it overrides the generated rules from below + if len(cfg.StorageRegistry.Rules) > 0 { + rules := map[string]map[string]interface{}{} + for i := range cfg.StorageRegistry.Rules { + parts := strings.SplitN(cfg.StorageRegistry.Rules[i], "=", 2) + rules[parts[0]] = map[string]interface{}{"address": parts[1]} + } + return rules + } + + // check if the rules have to be read from a json file + if cfg.StorageRegistry.JSON != "" { + data, err := ioutil.ReadFile(cfg.StorageRegistry.JSON) + if err != nil { + logger.Error().Err(err).Msg("Failed to read storage registry rules from JSON file: " + cfg.StorageRegistry.JSON) + return nil + } + var rules map[string]map[string]interface{} + if err = json.Unmarshal(data, &rules); err != nil { + logger.Error().Err(err).Msg("Failed to unmarshal storage registry rules") + return nil + } + return rules + } + + // generate rules based on default config + return map[string]map[string]interface{}{ + cfg.StorageUsersEndpoint: { + "providerid": "1284d238-aa92-42ce-bdc4-0b0000009157", + "spaces": map[string]interface{}{ + "personal": map[string]interface{}{ + "mount_point": "/users", + "path_template": "/users/{{.Space.Owner.Id.OpaqueId}}", + }, + "project": map[string]interface{}{ + "mount_point": "/projects", + "path_template": "/projects/{{.Space.Name}}", + }, + }, + }, + cfg.StorageSharesEndpoint: { + "providerid": utils.ShareStorageProviderID, + "spaces": map[string]interface{}{ + "virtual": map[string]interface{}{ + // The root of the share jail is mounted here + "mount_point": "/users/{{.CurrentUser.Id.OpaqueId}}/Shares", + }, + "grant": map[string]interface{}{ + // Grants are relative to a space root that the gateway will determine with a stat + "mount_point": ".", + }, + "mountpoint": map[string]interface{}{ + // The jail needs to be filled with mount points + // .Space.Name is a path relative to the mount point + "mount_point": "/users/{{.CurrentUser.Id.OpaqueId}}/Shares", + "path_template": "/users/{{.CurrentUser.Id.OpaqueId}}/Shares/{{.Space.Name}}", + }, + }, + }, + // public link storage returns the mount id of the actual storage + cfg.StoragePublicLinkEndpoint: { + "providerid": utils.PublicStorageProviderID, + "spaces": map[string]interface{}{ + "grant": map[string]interface{}{ + "mount_point": ".", + }, + "mountpoint": map[string]interface{}{ + "mount_point": "/public", + "path_template": "/public/{{.Space.Root.OpaqueId}}", + }, + }, + }, + // medatada storage not part of the global namespace + } +} diff --git a/extensions/gateway/pkg/server/debug/option.go b/extensions/gateway/pkg/server/debug/option.go new file mode 100644 index 0000000000..9bdf55de12 --- /dev/null +++ b/extensions/gateway/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/extensions/gateway/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Logger log.Logger + Context context.Context + Config *config.Config +} + +// newOptions initializes the available default options. +func newOptions(opts ...Option) Options { + opt := Options{} + + for _, o := range opts { + o(&opt) + } + + return opt +} + +// Logger provides a function to set the logger option. +func Logger(val log.Logger) Option { + return func(o *Options) { + o.Logger = val + } +} + +// Context provides a function to set the context option. +func Context(val context.Context) Option { + return func(o *Options) { + o.Context = val + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} diff --git a/extensions/gateway/pkg/server/debug/server.go b/extensions/gateway/pkg/server/debug/server.go new file mode 100644 index 0000000000..a1470d904c --- /dev/null +++ b/extensions/gateway/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/extensions/gateway/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/service/debug" + "github.com/owncloud/ocis/ocis-pkg/version" +) + +// Server initializes the debug service and server. +func Server(opts ...Option) (*http.Server, error) { + options := newOptions(opts...) + + return debug.NewService( + debug.Logger(options.Logger), + debug.Name(options.Config.Service.Name), + debug.Version(version.String), + debug.Address(options.Config.Debug.Addr), + debug.Token(options.Config.Debug.Token), + debug.Pprof(options.Config.Debug.Pprof), + debug.Zpages(options.Config.Debug.Zpages), + debug.Health(health(options.Config)), + debug.Ready(ready(options.Config)), + //debug.CorsAllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins), + //debug.CorsAllowedMethods(options.Config.HTTP.CORS.AllowedMethods), + //debug.CorsAllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders), + //debug.CorsAllowCredentials(options.Config.HTTP.CORS.AllowCredentials), + ), nil +} + +// health implements the health check. +func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} + +// ready implements the ready check. +func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} diff --git a/extensions/gateway/pkg/tracing/tracing.go b/extensions/gateway/pkg/tracing/tracing.go new file mode 100644 index 0000000000..cdc355b20d --- /dev/null +++ b/extensions/gateway/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/gateway/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/glauth/pkg/command/root.go b/extensions/glauth/pkg/command/root.go index 5fbfbf5760..27d48cc8ca 100644 --- a/extensions/glauth/pkg/command/root.go +++ b/extensions/glauth/pkg/command/root.go @@ -28,7 +28,7 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the ocis-glauth command. func Execute(cfg *config.Config) error { app := clihelper.DefaultApp(&cli.App{ - Name: "ocis-glauth", + Name: "glauth", Usage: "Serve GLAuth API for oCIS", Commands: GetCommands(cfg), }) diff --git a/extensions/graph/pkg/command/root.go b/extensions/graph/pkg/command/root.go index ecfeb2d15d..be426b0c79 100644 --- a/extensions/graph/pkg/command/root.go +++ b/extensions/graph/pkg/command/root.go @@ -29,7 +29,7 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the ocis-graph command. func Execute(cfg *config.Config) error { app := clihelper.DefaultApp(&cli.App{ - Name: "ocis-graph", + Name: "graph", Usage: "Serve Graph API for oCIS", Commands: GetCommands(cfg), }) diff --git a/extensions/graph/pkg/config/config.go b/extensions/graph/pkg/config/config.go index d147eaa30c..c109a402c7 100644 --- a/extensions/graph/pkg/config/config.go +++ b/extensions/graph/pkg/config/config.go @@ -68,6 +68,6 @@ type Identity struct { // Events combines the configuration options for the event bus. type Events struct { - Endpoint string `yaml:"events_endpoint" env:"GRAPH_EVENTS_ENDPOINT" desc:"the address of the streaming service"` - Cluster string `yaml:"events_cluster" env:"GRAPH_EVENTS_CLUSTER" desc:"the clusterID of the streaming service. Mandatory when using nats"` + Endpoint string `yaml:"endpoint" env:"GRAPH_EVENTS_ENDPOINT" desc:"the address of the streaming service"` + Cluster string `yaml:"cluster" env:"GRAPH_EVENTS_CLUSTER" desc:"the clusterID of the streaming service. Mandatory when using nats"` } diff --git a/extensions/graph/pkg/service/v0/driveitems.go b/extensions/graph/pkg/service/v0/driveitems.go index 7b3e1beb0a..38504ea992 100644 --- a/extensions/graph/pkg/service/v0/driveitems.go +++ b/extensions/graph/pkg/service/v0/driveitems.go @@ -12,7 +12,7 @@ import ( cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/cs3org/reva/v2/pkg/utils/resourceid" + "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/go-chi/render" libregraph "github.com/owncloud/libre-graph-api-go" "github.com/owncloud/ocis/extensions/graph/pkg/service/v0/errorcode" @@ -113,7 +113,7 @@ func (g Graph) getRemoteItem(ctx context.Context, root *storageprovider.Resource return nil, err } - item.WebDavUrl = libregraph.PtrString(baseURL.String() + resourceid.OwnCloudResourceIDWrap(root)) + item.WebDavUrl = libregraph.PtrString(baseURL.String() + storagespace.FormatResourceID(*root)) return item, nil } @@ -139,7 +139,7 @@ func cs3ResourceToDriveItem(res *storageprovider.ResourceInfo) (*libregraph.Driv *size = int64(res.Size) // TODO lurking overflow: make size of libregraph drive item use uint64 driveItem := &libregraph.DriveItem{ - Id: libregraph.PtrString(resourceid.OwnCloudResourceIDWrap(res.Id)), + Id: libregraph.PtrString(storagespace.FormatResourceID(*res.Id)), Size: size, } @@ -170,7 +170,7 @@ func cs3ResourceToRemoteItem(res *storageprovider.ResourceInfo) (*libregraph.Rem *size = int64(res.Size) // TODO lurking overflow: make size of libregraph drive item use uint64 remoteItem := &libregraph.RemoteItem{ - Id: libregraph.PtrString(resourceid.OwnCloudResourceIDWrap(res.Id)), + Id: libregraph.PtrString(storagespace.FormatResourceID(*res.Id)), Size: size, } @@ -219,7 +219,8 @@ func (g Graph) GetExtendedSpaceProperties(ctx context.Context, baseURL *url.URL, for _, itemName := range names { if itemID, ok := metadata[itemName]; ok { - spaceItem := g.getSpecialDriveItem(ctx, *resourceid.OwnCloudResourceIDUnwrap(string(itemID.Value)), itemName, baseURL, space) + rid, _ := storagespace.ParseID(string(itemID.Value)) + spaceItem := g.getSpecialDriveItem(ctx, rid, itemName, baseURL, space) if spaceItem != nil { spaceItems = append(spaceItems, *spaceItem) } diff --git a/extensions/graph/pkg/service/v0/drives.go b/extensions/graph/pkg/service/v0/drives.go index 821e664898..e37302e862 100644 --- a/extensions/graph/pkg/service/v0/drives.go +++ b/extensions/graph/pkg/service/v0/drives.go @@ -18,8 +18,8 @@ import ( storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" - "github.com/cs3org/reva/v2/pkg/utils/resourceid" "github.com/go-chi/chi/v5" "github.com/go-chi/render" libregraph "github.com/owncloud/libre-graph-api-go" @@ -287,7 +287,7 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) { identifierParts := strings.Split(driveID, "!") switch len(identifierParts) { case 1: - sID, _ := resourceid.StorageIDUnwrap(identifierParts[0]) + _, sID := storagespace.SplitStorageID(identifierParts[0]) root.StorageId, root.OpaqueId = identifierParts[0], sID case 2: root.StorageId, root.OpaqueId = identifierParts[0], identifierParts[1] @@ -453,7 +453,7 @@ func generateSpaceId(id *storageprovider.ResourceId) (spaceID string) { // 2nd ID to compare is the opaque ID of the Space Root spaceID2 := id.GetOpaqueId() if strings.Contains(spaceID, "$") { - spaceID2, _ = resourceid.StorageIDUnwrap(spaceID) + _, spaceID2 = storagespace.SplitStorageID(spaceID) } // Append opaqueID only if it is different from the spaceID2 if id.OpaqueId != spaceID2 { @@ -531,7 +531,7 @@ func (g Graph) cs3StorageSpaceToDrive(ctx context.Context, baseURL *url.URL, spa //"description": "string", // TODO read from StorageSpace ... needs Opaque for now DriveType: &space.SpaceType, Root: &libregraph.DriveItem{ - Id: libregraph.PtrString(resourceid.OwnCloudResourceIDWrap(space.Root)), + Id: libregraph.PtrString(storagespace.FormatResourceID(*space.Root)), Permissions: permissions, }, } @@ -764,7 +764,7 @@ func (g Graph) DeleteDrive(w http.ResponseWriter, r *http.Request) { root := &storageprovider.ResourceId{} identifierParts := strings.Split(driveID, "!") - sID, _ := resourceid.StorageIDUnwrap(identifierParts[0]) + _, sID := storagespace.SplitStorageID(identifierParts[0]) switch len(identifierParts) { case 1: root.StorageId, root.OpaqueId = identifierParts[0], sID diff --git a/extensions/group/Makefile b/extensions/group/Makefile new file mode 100644 index 0000000000..4f446944ec --- /dev/null +++ b/extensions/group/Makefile @@ -0,0 +1,37 @@ +SHELL := bash +NAME := group + +include ../../.make/recursion.mk + +############ tooling ############ +ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI +include ../../.bingo/Variables.mk +endif + +############ go tooling ############ +include ../../.make/go.mk + +############ release ############ +include ../../.make/release.mk + +############ docs generate ############ +include ../../.make/docs.mk + +.PHONY: docs-generate +docs-generate: config-docs-generate + +############ generate ############ +include ../../.make/generate.mk + +.PHONY: ci-go-generate +ci-go-generate: # CI runs ci-node-generate automatically before this target + +.PHONY: ci-node-generate +ci-node-generate: + +############ licenses ############ +.PHONY: ci-node-check-licenses +ci-node-check-licenses: + +.PHONY: ci-node-save-licenses +ci-node-save-licenses: diff --git a/extensions/group/cmd/group/main.go b/extensions/group/cmd/group/main.go new file mode 100644 index 0000000000..d459fa3291 --- /dev/null +++ b/extensions/group/cmd/group/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/group/pkg/command" + "github.com/owncloud/ocis/extensions/group/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/group/pkg/command/command.go b/extensions/group/pkg/command/command.go deleted file mode 100644 index 9f5d45dfe6..0000000000 --- a/extensions/group/pkg/command/command.go +++ /dev/null @@ -1,226 +0,0 @@ -package command - -import ( - "context" - "flag" - "fmt" - "os" - "path" - "path/filepath" - - "github.com/cs3org/reva/v2/cmd/revad/runtime" - "github.com/gofrs/uuid" - "github.com/oklog/run" - "github.com/owncloud/ocis/extensions/group/pkg/config" - "github.com/owncloud/ocis/extensions/group/pkg/config/parser" - "github.com/owncloud/ocis/extensions/storage/pkg/server/debug" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/ldap" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" -) - -// Groups is the entrypoint for the sharing command. -func Groups(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "groups", - Usage: "start groups service", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // pre-create folders - if cfg.Driver == "json" && cfg.Drivers.JSON.File != "" { - if err := os.MkdirAll(filepath.Dir(cfg.Drivers.JSON.File), os.FileMode(0700)); err != nil { - return err - } - } - - cuuid := uuid.Must(uuid.NewV4()) - pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+cuuid.String()+".pid") - - rcfg := groupsConfigFromStruct(c, cfg) - - if cfg.Driver == "ldap" { - if err := ldap.WaitForCA(logger, cfg.Drivers.LDAP.Insecure, cfg.Drivers.LDAP.CACert); err != nil { - logger.Error().Err(err).Msg("The configured LDAP CA cert does not exist") - return err - } - } - - gr.Add(func() error { - runtime.RunWithOptions( - rcfg, - pidFile, - runtime.WithLogger(&logger.Logger), - ) - return nil - }, func(_ error) { - logger.Info(). - Str("server", c.Command.Name). - Msg("Shutting down server") - - cancel() - }) - - debugServer, err := debug.Server( - debug.Name(c.Command.Name+"-debug"), - debug.Addr(cfg.Debug.Addr), - debug.Logger(logger), - debug.Context(ctx), - debug.Pprof(cfg.Debug.Pprof), - debug.Zpages(cfg.Debug.Zpages), - debug.Token(cfg.Debug.Token), - ) - - if err != nil { - logger.Info().Err(err).Str("server", c.Command.Name+"-debug").Msg("Failed to initialize server") - return err - } - - gr.Add(debugServer.ListenAndServe, func(_ error) { - cancel() - }) - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() - }, - } -} - -// groupsConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func groupsConfigFromStruct(c *cli.Context, cfg *config.Config) map[string]interface{} { - return map[string]interface{}{ - "core": map[string]interface{}{ - "tracing_enabled": cfg.Tracing.Enabled, - "tracing_endpoint": cfg.Tracing.Endpoint, - "tracing_collector": cfg.Tracing.Collector, - "tracing_service_name": c.Command.Name, - }, - "shared": map[string]interface{}{ - "jwt_secret": cfg.TokenManager.JWTSecret, - "gatewaysvc": cfg.Reva.Address, - "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, - }, - "grpc": map[string]interface{}{ - "network": cfg.GRPC.Protocol, - "address": cfg.GRPC.Addr, - // TODO build services dynamically - "services": map[string]interface{}{ - "groupprovider": map[string]interface{}{ - "driver": cfg.Driver, - "drivers": map[string]interface{}{ - "json": map[string]interface{}{ - "groups": cfg.Drivers.JSON.File, - }, - "ldap": ldapConfigFromString(cfg.Drivers.LDAP), - "rest": map[string]interface{}{ - "client_id": cfg.Drivers.REST.ClientID, - "client_secret": cfg.Drivers.REST.ClientSecret, - "redis_address": cfg.Drivers.REST.RedisAddr, - "redis_username": cfg.Drivers.REST.RedisUsername, - "redis_password": cfg.Drivers.REST.RedisPassword, - "group_members_cache_expiration": cfg.GroupMembersCacheExpiration, - "id_provider": cfg.Drivers.REST.IDProvider, - "api_base_url": cfg.Drivers.REST.APIBaseURL, - "oidc_token_endpoint": cfg.Drivers.REST.OIDCTokenEndpoint, - "target_api": cfg.Drivers.REST.TargetAPI, - }, - }, - }, - }, - }, - } -} - -// GroupSutureService allows for the storage-groupprovider command to be embedded and supervised by a suture supervisor tree. -type GroupSutureService struct { - cfg *config.Config -} - -// NewGroupProviderSutureService creates a new storage.GroupProvider -func NewGroupProvider(cfg *ociscfg.Config) suture.Service { - cfg.Group.Commons = cfg.Commons - return GroupSutureService{ - cfg: cfg.Group, - } -} - -func (s GroupSutureService) Serve(ctx context.Context) error { - // s.cfg.Reva.Groups.Context = ctx - f := &flag.FlagSet{} - cmdFlags := Groups(s.cfg).Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if Groups(s.cfg).Before != nil { - if err := Groups(s.cfg).Before(cliCtx); err != nil { - return err - } - } - if err := Groups(s.cfg).Action(cliCtx); err != nil { - return err - } - - return nil -} - -func ldapConfigFromString(cfg config.LDAPDriver) map[string]interface{} { - return map[string]interface{}{ - "uri": cfg.URI, - "cacert": cfg.CACert, - "insecure": cfg.Insecure, - "bind_username": cfg.BindDN, - "bind_password": cfg.BindPassword, - "user_base_dn": cfg.UserBaseDN, - "group_base_dn": cfg.GroupBaseDN, - "user_scope": cfg.UserScope, - "group_scope": cfg.GroupScope, - "user_filter": cfg.UserFilter, - "group_filter": cfg.GroupFilter, - "user_objectclass": cfg.UserObjectClass, - "group_objectclass": cfg.GroupObjectClass, - "login_attributes": cfg.LoginAttributes, - "idp": cfg.IDP, - "user_schema": map[string]interface{}{ - "id": cfg.UserSchema.ID, - "idIsOctetString": cfg.UserSchema.IDIsOctetString, - "mail": cfg.UserSchema.Mail, - "displayName": cfg.UserSchema.DisplayName, - "userName": cfg.UserSchema.Username, - }, - "group_schema": map[string]interface{}{ - "id": cfg.GroupSchema.ID, - "idIsOctetString": cfg.GroupSchema.IDIsOctetString, - "mail": cfg.GroupSchema.Mail, - "displayName": cfg.GroupSchema.DisplayName, - "groupName": cfg.GroupSchema.Groupname, - "member": cfg.GroupSchema.Member, - }, - } -} diff --git a/extensions/group/pkg/command/health.go b/extensions/group/pkg/command/health.go new file mode 100644 index 0000000000..6e6a01e7eb --- /dev/null +++ b/extensions/group/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/group/pkg/config" + "github.com/owncloud/ocis/extensions/group/pkg/config/parser" + "github.com/owncloud/ocis/extensions/group/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/group/pkg/command/root.go b/extensions/group/pkg/command/root.go new file mode 100644 index 0000000000..7a392363a4 --- /dev/null +++ b/extensions/group/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/group/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-group command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "group", + Usage: "Provide groups for oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the group command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new group.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.Group.Commons = cfg.Commons + return SutureService{ + cfg: cfg.Group, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/group/pkg/command/server.go b/extensions/group/pkg/command/server.go new file mode 100644 index 0000000000..2bf18e4748 --- /dev/null +++ b/extensions/group/pkg/command/server.go @@ -0,0 +1,120 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/group/pkg/config" + "github.com/owncloud/ocis/extensions/group/pkg/config/parser" + "github.com/owncloud/ocis/extensions/group/pkg/logging" + "github.com/owncloud/ocis/extensions/group/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/group/pkg/server/debug" + "github.com/owncloud/ocis/extensions/group/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/ldap" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/sync" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.GroupsConfigFromStruct(cfg) + + // the reva runtime calls os.Exit in the case of a failure and there is no way for the oCIS + // runtime to catch it and restart a reva service. Therefore we need to ensure the service has + // everything it needs, before starting the service. + // In this case: CA certificates + if cfg.Driver == "ldap" { + ldapCfg := cfg.Drivers.LDAP + if err := ldap.WaitForCA(logger, ldapCfg.Insecure, ldapCfg.CACert); err != nil { + logger.Error().Err(err).Msg("The configured LDAP CA cert does not exist") + return err + } + } + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + if err := external.RegisterGRPCEndpoint( + ctx, + cfg.GRPC.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.GRPC.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/group/pkg/command/version.go b/extensions/group/pkg/command/version.go new file mode 100644 index 0000000000..e3f5303faa --- /dev/null +++ b/extensions/group/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/group/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/group/pkg/config/config.go b/extensions/group/pkg/config/config.go index 415db0255e..a8861c962d 100644 --- a/extensions/group/pkg/config/config.go +++ b/extensions/group/pkg/config/config.go @@ -1,24 +1,31 @@ package config -import "github.com/owncloud/ocis/ocis-pkg/shared" +import ( + "context" + + "github.com/owncloud/ocis/ocis-pkg/shared" +) type Config struct { *shared.Commons `yaml:"-"` Service Service `yaml:"-"` Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` + Log *Log `yaml:"log"` Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` GRPC GRPCConfig `yaml:"grpc"` TokenManager *TokenManager `yaml:"token_manager"` Reva *Reva `yaml:"reva"` - SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token"` + SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"GROUPS_SKIP_USER_GROUPS_IN_TOKEN"` + GroupMembersCacheExpiration int `yaml:"group_members_cache_expiration"` Driver string `yaml:"driver"` Drivers Drivers `yaml:"drivers"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` } type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;GROUPS_TRACING_ENABLED" desc:"Activates tracing."` @@ -27,7 +34,7 @@ type Tracing struct { Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;GROUPS_TRACING_COLLECTOR"` } -type Logging struct { +type Log struct { Level string `yaml:"level" env:"OCIS_LOG_LEVEL;GROUPS_LOG_LEVEL" desc:"The log level."` Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;GROUPS_LOG_PRETTY" desc:"Activates pretty log output."` Color bool `yaml:"color" env:"OCIS_LOG_COLOR;GROUPS_LOG_COLOR" desc:"Activates colorized log output."` @@ -46,70 +53,71 @@ type Debug struct { } type GRPCConfig struct { - Addr string `yaml:"addr" env:"GROUPS_GRPC_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"GROUPS_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` + Addr string `yaml:"addr" env:"GROUPS_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"GROUPS_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` } type Drivers struct { - JSON JSONDriver - LDAP LDAPDriver - OwnCloudSQL OwnCloudSQLDriver - REST RESTProvider + LDAP LDAPDriver `yaml:"ldap"` + OwnCloudSQL OwnCloudSQLDriver `yaml:"owncloudsql"` + + JSON JSONDriver `yaml:"json,omitempty"` // not supported by the oCIS product, therefore not part of docs + REST RESTProvider `yaml:"rest,omitempty"` // not supported by the oCIS product, therefore not part of docs +} +type LDAPDriver struct { + URI string `yaml:"uri" env:"LDAP_URI;GROUPS_LDAP_URI"` + CACert string `yaml:"ca_cert" env:"LDAP_CACERT;GROUPS_LDAP_CACERT"` + Insecure bool `yaml:"insecure" env:"LDAP_INSECURE;GROUPS_LDAP_INSECURE"` + BindDN string `yaml:"bind_dn" env:"LDAP_BIND_DN;GROUPS_LDAP_BIND_DN"` + BindPassword string `yaml:"bind_password" env:"LDAP_BIND_PASSWORD;GROUPS_LDAP_BIND_PASSWORD"` + UserBaseDN string `yaml:"user_base_dn" env:"LDAP_USER_BASE_DN;GROUPS_LDAP_USER_BASE_DN"` + GroupBaseDN string `yaml:"group_base_dn" env:"LDAP_GROUP_BASE_DN;GROUPS_LDAP_GROUP_BASE_DN"` + UserScope string `yaml:"user_scope" env:"LDAP_USER_SCOPE;GROUPS_LDAP_USER_SCOPE"` + GroupScope string `yaml:"group_scope" env:"LDAP_GROUP_SCOPE;GROUPS_LDAP_GROUP_SCOPE"` + UserFilter string `yaml:"user_filter" env:"LDAP_USERFILTER;GROUPS_LDAP_USERFILTER"` + GroupFilter string `yaml:"group_filter" env:"LDAP_GROUPFILTER;GROUPS_LDAP_USERFILTER"` + UserObjectClass string `yaml:"user_object_class" env:"LDAP_USER_OBJECTCLASS;GROUPS_LDAP_USER_OBJECTCLASS"` + GroupObjectClass string `yaml:"group_object_class" env:"LDAP_GROUP_OBJECTCLASS;GROUPS_LDAP_GROUP_OBJECTCLASS"` + LoginAttributes []string `yaml:"login_attributes" env:"LDAP_LOGIN_ATTRIBUTES;GROUPS_LDAP_LOGIN_ATTRIBUTES"` + IDP string `yaml:"idp" env:"OCIS_URL;GROUPS_IDP_URL"` + UserSchema LDAPUserSchema `yaml:"user_schema"` + GroupSchema LDAPGroupSchema `yaml:"group_schema"` +} + +type LDAPUserSchema struct { + ID string `yaml:"id" env:"LDAP_USER_SCHEMA_ID;GROUPS_LDAP_USER_SCHEMA_ID"` + IDIsOctetString bool `yaml:"id_is_octet_string" env:"LDAP_USER_SCHEMA_ID_IS_OCTETSTRING;GROUPS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING"` + Mail string `yaml:"mail" env:"LDAP_USER_SCHEMA_MAIL;GROUPS_LDAP_USER_SCHEMA_MAIL"` + DisplayName string `yaml:"display_name" env:"LDAP_USER_SCHEMA_DISPLAYNAME;GROUPS_LDAP_USER_SCHEMA_DISPLAYNAME"` + Username string `yaml:"user_name" env:"LDAP_USER_SCHEMA_USERNAME;GROUPS_LDAP_USER_SCHEMA_USERNAME"` +} + +type LDAPGroupSchema struct { + ID string `yaml:"id" env:"LDAP_GROUP_SCHEMA_ID;GROUPS_LDAP_GROUP_SCHEMA_ID"` + IDIsOctetString bool `yaml:"id_is_octet_string" env:"LDAP_GROUP_SCHEMA_ID_IS_OCTETSTRING;GROUPS_LDAP_GROUP_SCHEMA_ID_IS_OCTETSTRING"` + Mail string `yaml:"mail" env:"LDAP_GROUP_SCHEMA_MAIL;GROUPS_LDAP_GROUP_SCHEMA_MAIL"` + DisplayName string `yaml:"display_name" env:"LDAP_GROUP_SCHEMA_DISPLAYNAME;GROUPS_LDAP_GROUP_SCHEMA_DISPLAYNAME"` + Groupname string `yaml:"group_name" env:"LDAP_GROUP_SCHEMA_GROUPNAME;GROUPS_LDAP_GROUP_SCHEMA_GROUPNAME"` + Member string `yaml:"member" env:"LDAP_GROUP_SCHEMA_MEMBER;GROUPS_LDAP_GROUP_SCHEMA_MEMBER"` +} + +type OwnCloudSQLDriver struct { + DBUsername string `yaml:"db_username" env:"GROUPS_OWNCLOUDSQL_DB_USERNAME"` + DBPassword string `yaml:"db_password" env:"GROUPS_OWNCLOUDSQL_DB_PASSWORD"` + DBHost string `yaml:"db_host" env:"GROUPS_OWNCLOUDSQL_DB_HOST"` + DBPort int `yaml:"db_port" env:"GROUPS_OWNCLOUDSQL_DB_PORT"` + DBName string `yaml:"db_name" env:"GROUPS_OWNCLOUDSQL_DB_NAME"` + IDP string `yaml:"idp" env:"GROUPS_OWNCLOUDSQL_IDP"` + Nobody int64 `yaml:"nobody" env:"GROUPS_OWNCLOUDSQL_NOBODY"` // TODO what is this? + JoinUsername bool `yaml:"join_username" env:"GROUPS_OWNCLOUDSQL_JOIN_USERNAME"` + JoinOwnCloudUUID bool `yaml:"join_owncloud_uuid" env:"GROUPS_OWNCLOUDSQL_JOIN_OWNCLOUD_UUID"` + EnableMedialSearch bool `yaml:"enable_medial_search" env:"GROUPS_OWNCLOUDSQL_ENABLE_MEDIAL_SEARCH"` } type JSONDriver struct { File string } -type LDAPDriver struct { - URI string `env:"LDAP_URI;GROUPS_LDAP_URI"` - CACert string `env:"LDAP_CACERT;GROUPS_LDAP_CACERT"` - Insecure bool `env:"LDAP_INSECURE;GROUPS_LDAP_INSECURE"` - BindDN string `env:"LDAP_BIND_DN;GROUPS_LDAP_BIND_DN"` - BindPassword string `yaml:"bind_password" env:"LDAP_BIND_PASSWORD;GROUPS_LDAP_BIND_PASSWORD"` - UserBaseDN string `env:"LDAP_USER_BASE_DN;GROUPS_LDAP_USER_BASE_DN"` - GroupBaseDN string `env:"LDAP_GROUP_BASE_DN;GROUPS_LDAP_GROUP_BASE_DN"` - UserScope string `env:"LDAP_USER_SCOPE;GROUPS_LDAP_USER_SCOPE"` - GroupScope string `env:"LDAP_GROUP_SCOPE;GROUPS_LDAP_GROUP_SCOPE"` - UserFilter string `env:"LDAP_USERFILTER;GROUPS_LDAP_USERFILTER"` - GroupFilter string `env:"LDAP_GROUPFILTER;GROUPS_LDAP_USERFILTER"` - UserObjectClass string `env:"LDAP_USER_OBJECTCLASS;GROUPS_LDAP_USER_OBJECTCLASS"` - GroupObjectClass string `env:"LDAP_GROUP_OBJECTCLASS;GROUPS_LDAP_GROUP_OBJECTCLASS"` - LoginAttributes []string `env:"LDAP_LOGIN_ATTRIBUTES;GROUPS_LDAP_LOGIN_ATTRIBUTES"` - IDP string `env:"OCIS_URL;GROUPS_IDP_URL"` // TODO what is this for? - GatewayEndpoint string // TODO do we need this here? - UserSchema LDAPUserSchema - GroupSchema LDAPGroupSchema -} - -type LDAPUserSchema struct { - ID string `env:"LDAP_USER_SCHEMA_ID;GROUPS_LDAP_USER_SCHEMA_ID"` - IDIsOctetString bool `env:"LDAP_USER_SCHEMA_ID_IS_OCTETSTRING;GROUPS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING"` - Mail string `env:"LDAP_USER_SCHEMA_MAIL;GROUPS_LDAP_USER_SCHEMA_MAIL"` - DisplayName string `env:"LDAP_USER_SCHEMA_DISPLAYNAME;GROUPS_LDAP_USER_SCHEMA_DISPLAYNAME"` - Username string `env:"LDAP_USER_SCHEMA_USERNAME;GROUPS_LDAP_USER_SCHEMA_USERNAME"` -} - -type LDAPGroupSchema struct { - ID string `env:"LDAP_GROUP_SCHEMA_ID;GROUPS_LDAP_GROUP_SCHEMA_ID"` - IDIsOctetString bool `env:"LDAP_GROUP_SCHEMA_ID_IS_OCTETSTRING;GROUPS_LDAP_GROUP_SCHEMA_ID_IS_OCTETSTRING"` - Mail string `env:"LDAP_GROUP_SCHEMA_MAIL;GROUPS_LDAP_GROUP_SCHEMA_MAIL"` - DisplayName string `env:"LDAP_GROUP_SCHEMA_DISPLAYNAME;GROUPS_LDAP_GROUP_SCHEMA_DISPLAYNAME"` - Groupname string `env:"LDAP_GROUP_SCHEMA_GROUPNAME;GROUPS_LDAP_GROUP_SCHEMA_GROUPNAME"` - Member string `env:"LDAP_GROUP_SCHEMA_MEMBER;GROUPS_LDAP_GROUP_SCHEMA_MEMBER"` -} - -type OwnCloudSQLDriver struct { - DBUsername string - DBPassword string - DBHost string - DBPort int - DBName string - IDP string // TODO do we need this? - Nobody int64 // TODO what is this? - JoinUsername bool - JoinOwnCloudUUID bool - EnableMedialSearch bool -} type RESTProvider struct { ClientID string diff --git a/extensions/group/pkg/config/defaults/defaultconfig.go b/extensions/group/pkg/config/defaults/defaultconfig.go index 47c10b9f79..5743008290 100644 --- a/extensions/group/pkg/config/defaults/defaultconfig.go +++ b/extensions/group/pkg/config/defaults/defaultconfig.go @@ -23,8 +23,9 @@ func DefaultConfig() *config.Config { Zpages: false, }, GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9160", - Protocol: "tcp", + Addr: "127.0.0.1:9160", + Namespace: "com.owncloud.api", + Protocol: "tcp", }, Service: config.Service{ Name: "group", @@ -64,10 +65,9 @@ func DefaultConfig() *config.Config { Member: "member", }, }, - JSON: config.JSONDriver{}, OwnCloudSQL: config.OwnCloudSQLDriver{ DBUsername: "owncloud", - DBPassword: "secret", + DBPassword: "", DBHost: "mysql", DBPort: 3306, DBName: "owncloud", @@ -77,24 +77,21 @@ func DefaultConfig() *config.Config { JoinOwnCloudUUID: false, EnableMedialSearch: false, }, - REST: config.RESTProvider{ - RedisAddr: "localhost:6379", - }, }, } } func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/group/pkg/logging/logging.go b/extensions/group/pkg/logging/logging.go new file mode 100644 index 0000000000..9aa3b08150 --- /dev/null +++ b/extensions/group/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/group/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/group/pkg/revaconfig/config.go b/extensions/group/pkg/revaconfig/config.go new file mode 100644 index 0000000000..736956060e --- /dev/null +++ b/extensions/group/pkg/revaconfig/config.go @@ -0,0 +1,85 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/extensions/group/pkg/config" +) + +// GroupsConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func GroupsConfigFromStruct(cfg *config.Config) map[string]interface{} { + return map[string]interface{}{ + "core": map[string]interface{}{ + "tracing_enabled": cfg.Tracing.Enabled, + "tracing_endpoint": cfg.Tracing.Endpoint, + "tracing_collector": cfg.Tracing.Collector, + "tracing_service_name": cfg.Service.Name, + }, + "shared": map[string]interface{}{ + "jwt_secret": cfg.TokenManager.JWTSecret, + "gatewaysvc": cfg.Reva.Address, + "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, + }, + "grpc": map[string]interface{}{ + "network": cfg.GRPC.Protocol, + "address": cfg.GRPC.Addr, + // TODO build services dynamically + "services": map[string]interface{}{ + "groupprovider": map[string]interface{}{ + "driver": cfg.Driver, + "drivers": map[string]interface{}{ + "json": map[string]interface{}{ + "groups": cfg.Drivers.JSON.File, + }, + "ldap": ldapConfigFromString(cfg.Drivers.LDAP), + "rest": map[string]interface{}{ + "client_id": cfg.Drivers.REST.ClientID, + "client_secret": cfg.Drivers.REST.ClientSecret, + "redis_address": cfg.Drivers.REST.RedisAddr, + "redis_username": cfg.Drivers.REST.RedisUsername, + "redis_password": cfg.Drivers.REST.RedisPassword, + "group_members_cache_expiration": cfg.GroupMembersCacheExpiration, + "id_provider": cfg.Drivers.REST.IDProvider, + "api_base_url": cfg.Drivers.REST.APIBaseURL, + "oidc_token_endpoint": cfg.Drivers.REST.OIDCTokenEndpoint, + "target_api": cfg.Drivers.REST.TargetAPI, + }, + }, + }, + }, + }, + } +} + +func ldapConfigFromString(cfg config.LDAPDriver) map[string]interface{} { + return map[string]interface{}{ + "uri": cfg.URI, + "cacert": cfg.CACert, + "insecure": cfg.Insecure, + "bind_username": cfg.BindDN, + "bind_password": cfg.BindPassword, + "user_base_dn": cfg.UserBaseDN, + "group_base_dn": cfg.GroupBaseDN, + "user_scope": cfg.UserScope, + "group_scope": cfg.GroupScope, + "user_filter": cfg.UserFilter, + "group_filter": cfg.GroupFilter, + "user_objectclass": cfg.UserObjectClass, + "group_objectclass": cfg.GroupObjectClass, + "login_attributes": cfg.LoginAttributes, + "idp": cfg.IDP, + "user_schema": map[string]interface{}{ + "id": cfg.UserSchema.ID, + "idIsOctetString": cfg.UserSchema.IDIsOctetString, + "mail": cfg.UserSchema.Mail, + "displayName": cfg.UserSchema.DisplayName, + "userName": cfg.UserSchema.Username, + }, + "group_schema": map[string]interface{}{ + "id": cfg.GroupSchema.ID, + "idIsOctetString": cfg.GroupSchema.IDIsOctetString, + "mail": cfg.GroupSchema.Mail, + "displayName": cfg.GroupSchema.DisplayName, + "groupName": cfg.GroupSchema.Groupname, + "member": cfg.GroupSchema.Member, + }, + } +} diff --git a/extensions/group/pkg/server/debug/option.go b/extensions/group/pkg/server/debug/option.go new file mode 100644 index 0000000000..6b1c347dd4 --- /dev/null +++ b/extensions/group/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/extensions/group/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Logger log.Logger + Context context.Context + Config *config.Config +} + +// newOptions initializes the available default options. +func newOptions(opts ...Option) Options { + opt := Options{} + + for _, o := range opts { + o(&opt) + } + + return opt +} + +// Logger provides a function to set the logger option. +func Logger(val log.Logger) Option { + return func(o *Options) { + o.Logger = val + } +} + +// Context provides a function to set the context option. +func Context(val context.Context) Option { + return func(o *Options) { + o.Context = val + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} diff --git a/extensions/group/pkg/server/debug/server.go b/extensions/group/pkg/server/debug/server.go new file mode 100644 index 0000000000..e323840cae --- /dev/null +++ b/extensions/group/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/extensions/group/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/service/debug" + "github.com/owncloud/ocis/ocis-pkg/version" +) + +// Server initializes the debug service and server. +func Server(opts ...Option) (*http.Server, error) { + options := newOptions(opts...) + + return debug.NewService( + debug.Logger(options.Logger), + debug.Name(options.Config.Service.Name), + debug.Version(version.String), + debug.Address(options.Config.Debug.Addr), + debug.Token(options.Config.Debug.Token), + debug.Pprof(options.Config.Debug.Pprof), + debug.Zpages(options.Config.Debug.Zpages), + debug.Health(health(options.Config)), + debug.Ready(ready(options.Config)), + //debug.CorsAllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins), + //debug.CorsAllowedMethods(options.Config.HTTP.CORS.AllowedMethods), + //debug.CorsAllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders), + //debug.CorsAllowCredentials(options.Config.HTTP.CORS.AllowCredentials), + ), nil +} + +// health implements the health check. +func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} + +// ready implements the ready check. +func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} diff --git a/extensions/group/pkg/tracing/tracing.go b/extensions/group/pkg/tracing/tracing.go new file mode 100644 index 0000000000..af58cbf4f9 --- /dev/null +++ b/extensions/group/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/group/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/idm/pkg/command/root.go b/extensions/idm/pkg/command/root.go index 50f6444346..74866c9ecc 100644 --- a/extensions/idm/pkg/command/root.go +++ b/extensions/idm/pkg/command/root.go @@ -28,7 +28,7 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the ocis-idm command. func Execute(cfg *config.Config) error { app := clihelper.DefaultApp(&cli.App{ - Name: "ocis-idm", + Name: "idm", Usage: "Embedded LDAP service for oCIS", Commands: GetCommands(cfg), }) diff --git a/extensions/idp/pkg/command/root.go b/extensions/idp/pkg/command/root.go index 120e847139..8e8394ac1c 100644 --- a/extensions/idp/pkg/command/root.go +++ b/extensions/idp/pkg/command/root.go @@ -28,7 +28,7 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the ocis-idp command. func Execute(cfg *config.Config) error { app := clihelper.DefaultApp(&cli.App{ - Name: "ocis-idp", + Name: "idp", Usage: "Serve IDP API for oCIS", Commands: GetCommands(cfg), }) diff --git a/extensions/storage/Makefile b/extensions/nats/Makefile similarity index 94% rename from extensions/storage/Makefile rename to extensions/nats/Makefile index bb9471219d..5da8c05591 100644 --- a/extensions/storage/Makefile +++ b/extensions/nats/Makefile @@ -1,5 +1,5 @@ SHELL := bash -NAME := storage +NAME := nats include ../../.make/recursion.mk @@ -11,7 +11,7 @@ endif ############ go tooling ############ include ../../.make/go.mk -############ release ######### +############ release ############ include ../../.make/release.mk ############ docs generate ############ diff --git a/extensions/nats/pkg/command/root.go b/extensions/nats/pkg/command/root.go index 44e27d1673..2a2bdffd6e 100644 --- a/extensions/nats/pkg/command/root.go +++ b/extensions/nats/pkg/command/root.go @@ -48,7 +48,7 @@ type SutureService struct { // NewSutureService creates a new nats.SutureService func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Settings.Commons = cfg.Commons + cfg.Nats.Commons = cfg.Commons return SutureService{ cfg: cfg.Nats, } diff --git a/extensions/notifications/Makefile b/extensions/notifications/Makefile new file mode 100644 index 0000000000..749bf8057b --- /dev/null +++ b/extensions/notifications/Makefile @@ -0,0 +1,37 @@ +SHELL := bash +NAME := notifications + +include ../../.make/recursion.mk + +############ tooling ############ +ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI +include ../../.bingo/Variables.mk +endif + +############ go tooling ############ +include ../../.make/go.mk + +############ release ############ +include ../../.make/release.mk + +############ docs generate ############ +include ../../.make/docs.mk + +.PHONY: docs-generate +docs-generate: config-docs-generate + +############ generate ############ +include ../../.make/generate.mk + +.PHONY: ci-go-generate +ci-go-generate: # CI runs ci-node-generate automatically before this target + +.PHONY: ci-node-generate +ci-node-generate: + +############ licenses ############ +.PHONY: ci-node-check-licenses +ci-node-check-licenses: + +.PHONY: ci-node-save-licenses +ci-node-save-licenses: diff --git a/extensions/notifications/pkg/config/config.go b/extensions/notifications/pkg/config/config.go index 36ff2e6c8c..dc49e46cc8 100644 --- a/extensions/notifications/pkg/config/config.go +++ b/extensions/notifications/pkg/config/config.go @@ -39,7 +39,7 @@ type SMTP struct { // Events combines the configuration options for the event bus. type Events struct { - Endpoint string `yaml:"events_endpoint" env:"NOTIFICATIONS_EVENTS_ENDPOINT"` - Cluster string `yaml:"events_cluster" env:"NOTIFICATIONS_EVENTS_CLUSTER"` - ConsumerGroup string `yaml:"events_group" env:"NOTIFICATIONS_EVENTS_GROUP"` + Endpoint string `yaml:"endpoint" env:"NOTIFICATIONS_EVENTS_ENDPOINT"` + Cluster string `yaml:"cluster" env:"NOTIFICATIONS_EVENTS_CLUSTER"` + ConsumerGroup string `yaml:"group" env:"NOTIFICATIONS_EVENTS_GROUP"` } diff --git a/extensions/ocdav/Makefile b/extensions/ocdav/Makefile new file mode 100644 index 0000000000..7fd2d03c35 --- /dev/null +++ b/extensions/ocdav/Makefile @@ -0,0 +1,37 @@ +SHELL := bash +NAME := ocdav + +include ../../.make/recursion.mk + +############ tooling ############ +ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI +include ../../.bingo/Variables.mk +endif + +############ go tooling ############ +include ../../.make/go.mk + +############ release ############ +include ../../.make/release.mk + +############ docs generate ############ +include ../../.make/docs.mk + +.PHONY: docs-generate +docs-generate: config-docs-generate + +############ generate ############ +include ../../.make/generate.mk + +.PHONY: ci-go-generate +ci-go-generate: # CI runs ci-node-generate automatically before this target + +.PHONY: ci-node-generate +ci-node-generate: + +############ licenses ############ +.PHONY: ci-node-check-licenses +ci-node-check-licenses: + +.PHONY: ci-node-save-licenses +ci-node-save-licenses: diff --git a/extensions/ocdav/cmd/ocdav/main.go b/extensions/ocdav/cmd/ocdav/main.go new file mode 100644 index 0000000000..0b1035acc6 --- /dev/null +++ b/extensions/ocdav/cmd/ocdav/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/ocdav/pkg/command" + "github.com/owncloud/ocis/extensions/ocdav/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/ocdav/pkg/command/health.go b/extensions/ocdav/pkg/command/health.go new file mode 100644 index 0000000000..ec3391a168 --- /dev/null +++ b/extensions/ocdav/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/ocdav/pkg/config" + "github.com/owncloud/ocis/extensions/ocdav/pkg/config/parser" + "github.com/owncloud/ocis/extensions/ocdav/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/ocdav/pkg/command/ocdav.go b/extensions/ocdav/pkg/command/ocdav.go deleted file mode 100644 index 20bb8a29b6..0000000000 --- a/extensions/ocdav/pkg/command/ocdav.go +++ /dev/null @@ -1,146 +0,0 @@ -package command - -import ( - "context" - "flag" - "fmt" - - "github.com/cs3org/reva/v2/pkg/micro/ocdav" - "github.com/oklog/run" - "github.com/owncloud/ocis/extensions/ocdav/pkg/config" - "github.com/owncloud/ocis/extensions/ocdav/pkg/config/parser" - "github.com/owncloud/ocis/extensions/storage/pkg/server/debug" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" -) - -// OCDav is the entrypoint for the ocdav command. -// TODO move ocdav cmd to a separate service -func OCDav(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "ocdav", - Usage: "start ocdav service", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - //metrics = metrics.New() - - defer cancel() - - gr.Add(func() error { - s, err := ocdav.Service( - ocdav.Context(ctx), - ocdav.Logger(logger.Logger), - ocdav.Address(cfg.HTTP.Addr), - ocdav.FilesNamespace(cfg.FilesNamespace), - ocdav.WebdavNamespace(cfg.WebdavNamespace), - ocdav.SharesNamespace(cfg.SharesNamespace), - ocdav.Timeout(cfg.Timeout), - ocdav.Insecure(cfg.Insecure), - ocdav.PublicURL(cfg.PublicURL), - ocdav.Prefix(cfg.HTTP.Prefix), - ocdav.GatewaySvc(cfg.Reva.Address), - ocdav.JWTSecret(cfg.TokenManager.JWTSecret), - // ocdav.FavoriteManager() // FIXME needs a proper persistence implementation - // ocdav.LockSystem(), // will default to the CS3 lock system - // ocdav.TLSConfig() // tls config for the http server - ) - if err != nil { - return err - } - - return s.Run() - }, func(err error) { - logger.Info().Err(err).Str("server", c.Command.Name).Msg("Shutting down server") - cancel() - }) - - { - server, err := debug.Server( - debug.Name(c.Command.Name+"-debug"), - debug.Addr(cfg.Debug.Addr), - debug.Logger(logger), - debug.Context(ctx), - debug.Pprof(cfg.Debug.Pprof), - debug.Zpages(cfg.Debug.Zpages), - debug.Token(cfg.Debug.Token), - ) - - if err != nil { - logger.Info(). - Err(err). - Str("server", "debug"). - Msg("Failed to initialize server") - - return err - } - - gr.Add(server.ListenAndServe, func(_ error) { - cancel() - }) - } - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() - }, - } -} - -// OCDavSutureService allows for the ocdav command to be embedded and supervised by a suture supervisor tree. -type OCDavSutureService struct { - cfg *config.Config -} - -// NewOCDav creates a new ocdav.OCDavSutureService -func NewOCDav(cfg *ociscfg.Config) suture.Service { - cfg.OCDav.Commons = cfg.Commons - return OCDavSutureService{ - cfg: cfg.OCDav, - } -} - -func (s OCDavSutureService) Serve(ctx context.Context) error { - // s.cfg.Reva.Frontend.Context = ctx - cmd := OCDav(s.cfg) - f := &flag.FlagSet{} - cmdFlags := cmd.Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if cmd.Before != nil { - if err := cmd.Before(cliCtx); err != nil { - return err - } - } - if err := cmd.Action(cliCtx); err != nil { - return err - } - - return nil -} diff --git a/extensions/ocdav/pkg/command/root.go b/extensions/ocdav/pkg/command/root.go new file mode 100644 index 0000000000..c37fdd6852 --- /dev/null +++ b/extensions/ocdav/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/ocdav/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-ocdav command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "ocdav", + Usage: "Provide a WebDav API for oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the ocdav command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new ocdav.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.OCDav.Commons = cfg.Commons + return SutureService{ + cfg: cfg.OCDav, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/ocdav/pkg/command/server.go b/extensions/ocdav/pkg/command/server.go new file mode 100644 index 0000000000..55f8a48e48 --- /dev/null +++ b/extensions/ocdav/pkg/command/server.go @@ -0,0 +1,98 @@ +package command + +import ( + "context" + "fmt" + + "github.com/cs3org/reva/v2/pkg/micro/ocdav" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/ocdav/pkg/config" + "github.com/owncloud/ocis/extensions/ocdav/pkg/config/parser" + "github.com/owncloud/ocis/extensions/ocdav/pkg/logging" + "github.com/owncloud/ocis/extensions/ocdav/pkg/server/debug" + "github.com/owncloud/ocis/extensions/ocdav/pkg/tracing" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + gr.Add(func() error { + s, err := ocdav.Service( + ocdav.Context(ctx), + ocdav.Logger(logger.Logger), + ocdav.Address(cfg.HTTP.Addr), + ocdav.FilesNamespace(cfg.FilesNamespace), + ocdav.WebdavNamespace(cfg.WebdavNamespace), + ocdav.SharesNamespace(cfg.SharesNamespace), + ocdav.Timeout(cfg.Timeout), + ocdav.Insecure(cfg.Insecure), + ocdav.PublicURL(cfg.PublicURL), + ocdav.Prefix(cfg.HTTP.Prefix), + ocdav.GatewaySvc(cfg.Reva.Address), + ocdav.JWTSecret(cfg.TokenManager.JWTSecret), + // ocdav.FavoriteManager() // FIXME needs a proper persistence implementation + // ocdav.LockSystem(), // will default to the CS3 lock system + // ocdav.TLSConfig() // tls config for the http server + ) + if err != nil { + return err + } + + return s.Run() + }, func(err error) { + logger.Info().Err(err).Str("server", c.Command.Name).Msg("Shutting down server") + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/ocdav/pkg/command/version.go b/extensions/ocdav/pkg/command/version.go new file mode 100644 index 0000000000..05bf397226 --- /dev/null +++ b/extensions/ocdav/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/ocdav/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.HTTP.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/ocdav/pkg/config/config.go b/extensions/ocdav/pkg/config/config.go index da510a3eff..3b52046d65 100644 --- a/extensions/ocdav/pkg/config/config.go +++ b/extensions/ocdav/pkg/config/config.go @@ -1,33 +1,38 @@ package config -import "github.com/owncloud/ocis/ocis-pkg/shared" +import ( + "context" + + "github.com/owncloud/ocis/ocis-pkg/shared" +) type Config struct { *shared.Commons `yaml:"-"` Service Service `yaml:"-"` Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` + Log *Log `yaml:"log"` Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` HTTP HTTPConfig `yaml:"http"` TokenManager *TokenManager `yaml:"token_manager"` Reva *Reva `yaml:"reva"` - SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token"` + SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"OCDAV_SKIP_USER_GROUPS_IN_TOKEN"` - WebdavNamespace string `yaml:"webdav_namespace"` - FilesNamespace string `yaml:"files_namespace"` - SharesNamespace string `yaml:"shares_namespace"` + WebdavNamespace string `yaml:"webdav_namespace" env:"OCDAV_WEBDAV_NAMESPACE"` + FilesNamespace string `yaml:"files_namespace" env:"OCDAV_FILES_NAMESPACE"` + SharesNamespace string `yaml:"shares_namespace" env:"OCDAV_SHARES_NAMESPACE"` // PublicURL used to redirect /s/{token} URLs to PublicURL string `yaml:"public_url" env:"OCIS_URL;OCDAV_PUBLIC_URL"` // Insecure certificates allowed when making requests to the gateway Insecure bool `yaml:"insecure" env:"OCIS_INSECURE;OCDAV_INSECURE"` // Timeout in seconds when making requests to the gateway - Timeout int64 `yaml:"timeout"` + Timeout int64 `yaml:"gateway_request_timeout" env:"OCDAV_GATEWAY_REQUEST_TIMEOUT"` Middleware Middleware `yaml:"middleware"` + + Context context.Context `yaml:"-"` } type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;OCDAV_TRACING_ENABLED" desc:"Activates tracing."` @@ -36,7 +41,7 @@ type Tracing struct { Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;OCDAV_TRACING_COLLECTOR"` } -type Logging struct { +type Log struct { Level string `yaml:"level" env:"OCIS_LOG_LEVEL;OCDAV_LOG_LEVEL" desc:"The log level."` Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;OCDAV_LOG_PRETTY" desc:"Activates pretty log output."` Color bool `yaml:"color" env:"OCIS_LOG_COLOR;OCDAV_LOG_COLOR" desc:"Activates colorized log output."` @@ -55,9 +60,10 @@ type Debug struct { } type HTTPConfig struct { - Addr string `yaml:"addr" env:"OCDAV_HTTP_ADDR" desc:"The address of the http service."` - Protocol string `yaml:"protocol" env:"OCDAV_HTTP_PROTOCOL" desc:"The transport protocol of the http service."` - Prefix string `yaml:"prefix"` + Addr string `yaml:"addr" env:"OCDAV_HTTP_ADDR" desc:"The address of the http service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"OCDAV_HTTP_PROTOCOL" desc:"The transport protocol of the http service."` + Prefix string `yaml:"prefix" env:"OCDAV_HTTP_PREFIX"` } // Middleware configures reva middlewares. @@ -67,5 +73,5 @@ type Middleware struct { // Auth configures reva http auth middleware. type Auth struct { - CredentialsByUserAgent map[string]string `yaml:"credentials_by_user_agenr"` + CredentialsByUserAgent map[string]string `yaml:"credentials_by_user_agent"` } diff --git a/extensions/ocdav/pkg/config/defaults/defaultconfig.go b/extensions/ocdav/pkg/config/defaults/defaultconfig.go index b55f9e6513..8567bbdb71 100644 --- a/extensions/ocdav/pkg/config/defaults/defaultconfig.go +++ b/extensions/ocdav/pkg/config/defaults/defaultconfig.go @@ -20,9 +20,10 @@ func DefaultConfig() *config.Config { Zpages: false, }, HTTP: config.HTTPConfig{ - Addr: "127.0.0.1:0", // :0 to pick any free local port - Protocol: "tcp", - Prefix: "", + Addr: "127.0.0.1:0", // :0 to pick any free local port + Namespace: "", //TODO: make this configurable for the reva micro service + Protocol: "tcp", + Prefix: "", }, Service: config.Service{ Name: "ocdav", @@ -46,15 +47,15 @@ func DefaultConfig() *config.Config { func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/ocdav/pkg/logging/logging.go b/extensions/ocdav/pkg/logging/logging.go new file mode 100644 index 0000000000..37a48c877e --- /dev/null +++ b/extensions/ocdav/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/ocdav/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/ocdav/pkg/server/debug/option.go b/extensions/ocdav/pkg/server/debug/option.go new file mode 100644 index 0000000000..01bda4d86c --- /dev/null +++ b/extensions/ocdav/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/extensions/ocdav/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Logger log.Logger + Context context.Context + Config *config.Config +} + +// newOptions initializes the available default options. +func newOptions(opts ...Option) Options { + opt := Options{} + + for _, o := range opts { + o(&opt) + } + + return opt +} + +// Logger provides a function to set the logger option. +func Logger(val log.Logger) Option { + return func(o *Options) { + o.Logger = val + } +} + +// Context provides a function to set the context option. +func Context(val context.Context) Option { + return func(o *Options) { + o.Context = val + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} diff --git a/extensions/ocdav/pkg/server/debug/server.go b/extensions/ocdav/pkg/server/debug/server.go new file mode 100644 index 0000000000..6a7838accd --- /dev/null +++ b/extensions/ocdav/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/extensions/ocdav/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/service/debug" + "github.com/owncloud/ocis/ocis-pkg/version" +) + +// Server initializes the debug service and server. +func Server(opts ...Option) (*http.Server, error) { + options := newOptions(opts...) + + return debug.NewService( + debug.Logger(options.Logger), + debug.Name(options.Config.Service.Name), + debug.Version(version.String), + debug.Address(options.Config.Debug.Addr), + debug.Token(options.Config.Debug.Token), + debug.Pprof(options.Config.Debug.Pprof), + debug.Zpages(options.Config.Debug.Zpages), + debug.Health(health(options.Config)), + debug.Ready(ready(options.Config)), + //debug.CorsAllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins), + //debug.CorsAllowedMethods(options.Config.HTTP.CORS.AllowedMethods), + //debug.CorsAllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders), + //debug.CorsAllowCredentials(options.Config.HTTP.CORS.AllowCredentials), + ), nil +} + +// health implements the health check. +func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} + +// ready implements the ready check. +func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} diff --git a/extensions/ocdav/pkg/tracing/tracing.go b/extensions/ocdav/pkg/tracing/tracing.go new file mode 100644 index 0000000000..adcb593161 --- /dev/null +++ b/extensions/ocdav/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/ocdav/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/ocs/pkg/command/root.go b/extensions/ocs/pkg/command/root.go index 61b65c7486..446db14b92 100644 --- a/extensions/ocs/pkg/command/root.go +++ b/extensions/ocs/pkg/command/root.go @@ -28,7 +28,7 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the ocis-ocs command. func Execute(cfg *config.Config) error { app := clihelper.DefaultApp(&cli.App{ - Name: "ocis-ocs", + Name: "ocs", Usage: "Serve OCS API for oCIS", Commands: GetCommands(cfg), }) diff --git a/extensions/proxy/pkg/command/root.go b/extensions/proxy/pkg/command/root.go index bcd4fcaec0..98a47d7571 100644 --- a/extensions/proxy/pkg/command/root.go +++ b/extensions/proxy/pkg/command/root.go @@ -28,7 +28,7 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the ocis-proxy command. func Execute(cfg *config.Config) error { app := clihelper.DefaultApp(&cli.App{ - Name: "ocis-proxy", + Name: "proxy", Usage: "proxy for oCIS", Commands: GetCommands(cfg), }) diff --git a/extensions/search/pkg/command/root.go b/extensions/search/pkg/command/root.go index a43261631f..41dc43f558 100644 --- a/extensions/search/pkg/command/root.go +++ b/extensions/search/pkg/command/root.go @@ -30,7 +30,7 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the ocis-search command. func Execute(cfg *config.Config) error { app := clihelper.DefaultApp(&cli.App{ - Name: "ocis-search", + Name: "search", Usage: "Serve search API for oCIS", Commands: GetCommands(cfg), }) diff --git a/extensions/search/pkg/config/config.go b/extensions/search/pkg/config/config.go index 7c7ac33ff6..b1396872ee 100644 --- a/extensions/search/pkg/config/config.go +++ b/extensions/search/pkg/config/config.go @@ -29,7 +29,7 @@ type Config struct { // Events combines the configuration options for the event bus. type Events struct { - Endpoint string `yaml:"events_endpoint" env:"SEARCH_EVENTS_ENDPOINT" desc:"the address of the streaming service"` - Cluster string `yaml:"events_cluster" env:"SEARCH_EVENTS_CLUSTER" desc:"the clusterID of the streaming service. Mandatory when using nats"` - ConsumerGroup string `yaml:"events_group" env:"SEARCH_EVENTS_GROUP" desc:"the customergroup of the service. One group will only get one copy of an event"` + Endpoint string `yaml:"endpoint" env:"SEARCH_EVENTS_ENDPOINT" desc:"the address of the streaming service"` + Cluster string `yaml:"cluster" env:"SEARCH_EVENTS_CLUSTER" desc:"the clusterID of the streaming service. Mandatory when using nats"` + ConsumerGroup string `yaml:"group" env:"SEARCH_EVENTS_GROUP" desc:"the customergroup of the service. One group will only get one copy of an event"` } diff --git a/extensions/settings/pkg/command/root.go b/extensions/settings/pkg/command/root.go index 5ad6a4bff4..81a01cc32d 100644 --- a/extensions/settings/pkg/command/root.go +++ b/extensions/settings/pkg/command/root.go @@ -28,7 +28,7 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the ocis-settings command. func Execute(cfg *config.Config) error { app := clihelper.DefaultApp(&cli.App{ - Name: "ocis-settings", + Name: "settings", Usage: "Provide settings and permissions for oCIS", Commands: GetCommands(cfg), }) diff --git a/extensions/sharing/Makefile b/extensions/sharing/Makefile new file mode 100644 index 0000000000..074fbff5df --- /dev/null +++ b/extensions/sharing/Makefile @@ -0,0 +1,37 @@ +SHELL := bash +NAME := sharing + +include ../../.make/recursion.mk + +############ tooling ############ +ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI +include ../../.bingo/Variables.mk +endif + +############ go tooling ############ +include ../../.make/go.mk + +############ release ############ +include ../../.make/release.mk + +############ docs generate ############ +include ../../.make/docs.mk + +.PHONY: docs-generate +docs-generate: config-docs-generate + +############ generate ############ +include ../../.make/generate.mk + +.PHONY: ci-go-generate +ci-go-generate: # CI runs ci-node-generate automatically before this target + +.PHONY: ci-node-generate +ci-node-generate: + +############ licenses ############ +.PHONY: ci-node-check-licenses +ci-node-check-licenses: + +.PHONY: ci-node-save-licenses +ci-node-save-licenses: diff --git a/extensions/sharing/cmd/sharing/main.go b/extensions/sharing/cmd/sharing/main.go new file mode 100644 index 0000000000..8f1936f293 --- /dev/null +++ b/extensions/sharing/cmd/sharing/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/sharing/pkg/command" + "github.com/owncloud/ocis/extensions/sharing/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/sharing/pkg/command/command.go b/extensions/sharing/pkg/command/command.go deleted file mode 100644 index 29cde19357..0000000000 --- a/extensions/sharing/pkg/command/command.go +++ /dev/null @@ -1,247 +0,0 @@ -package command - -import ( - "context" - "flag" - "fmt" - "os" - "path" - "path/filepath" - - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - - "github.com/cs3org/reva/v2/cmd/revad/runtime" - "github.com/gofrs/uuid" - "github.com/oklog/run" - "github.com/owncloud/ocis/extensions/sharing/pkg/config" - "github.com/owncloud/ocis/extensions/sharing/pkg/config/parser" - "github.com/owncloud/ocis/extensions/storage/pkg/server/debug" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" -) - -// Sharing is the entrypoint for the sharing command. -func Sharing(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "sharing", - Usage: "start sharing service", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // precreate folders - if cfg.UserSharingDriver == "json" && cfg.UserSharingDrivers.JSON.File != "" { - if err := os.MkdirAll(filepath.Dir(cfg.UserSharingDrivers.JSON.File), os.FileMode(0700)); err != nil { - return err - } - } - if cfg.PublicSharingDriver == "json" && cfg.PublicSharingDrivers.JSON.File != "" { - if err := os.MkdirAll(filepath.Dir(cfg.PublicSharingDrivers.JSON.File), os.FileMode(0700)); err != nil { - return err - } - } - - uuid := uuid.Must(uuid.NewV4()) - pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+uuid.String()+".pid") - - rcfg := sharingConfigFromStruct(c, cfg) - - gr.Add(func() error { - runtime.RunWithOptions( - rcfg, - pidFile, - runtime.WithLogger(&logger.Logger), - ) - return nil - }, func(_ error) { - logger.Info(). - Str("server", c.Command.Name). - Msg("Shutting down server") - - cancel() - }) - - debug, err := debug.Server( - debug.Name(c.Command.Name+"-debug"), - debug.Addr(cfg.Debug.Addr), - debug.Logger(logger), - debug.Context(ctx), - debug.Pprof(cfg.Debug.Pprof), - debug.Zpages(cfg.Debug.Zpages), - debug.Token(cfg.Debug.Token), - ) - - if err != nil { - logger.Info().Err(err).Str("server", c.Command.Name+"-debug").Msg("Failed to initialize server") - return err - } - - gr.Add(debug.ListenAndServe, func(_ error) { - cancel() - }) - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() - }, - } -} - -// sharingConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func sharingConfigFromStruct(c *cli.Context, cfg *config.Config) map[string]interface{} { - rcfg := map[string]interface{}{ - "core": map[string]interface{}{ - "tracing_enabled": cfg.Tracing.Enabled, - "tracing_endpoint": cfg.Tracing.Endpoint, - "tracing_collector": cfg.Tracing.Collector, - "tracing_service_name": c.Command.Name, - }, - "shared": map[string]interface{}{ - "jwt_secret": cfg.TokenManager.JWTSecret, - "gatewaysvc": cfg.Reva.Address, - "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, - }, - "grpc": map[string]interface{}{ - "network": cfg.GRPC.Protocol, - "address": cfg.GRPC.Addr, - // TODO build services dynamically - "services": map[string]interface{}{ - "usershareprovider": map[string]interface{}{ - "driver": cfg.UserSharingDriver, - "drivers": map[string]interface{}{ - "json": map[string]interface{}{ - "file": cfg.UserSharingDrivers.JSON.File, - "gateway_addr": cfg.Reva.Address, - }, - "sql": map[string]interface{}{ // cernbox sql - "db_username": cfg.UserSharingDrivers.SQL.DBUsername, - "db_password": cfg.UserSharingDrivers.SQL.DBPassword, - "db_host": cfg.UserSharingDrivers.SQL.DBHost, - "db_port": cfg.UserSharingDrivers.SQL.DBPort, - "db_name": cfg.UserSharingDrivers.SQL.DBName, - "password_hash_cost": cfg.UserSharingDrivers.SQL.PasswordHashCost, - "enable_expired_shares_cleanup": cfg.UserSharingDrivers.SQL.EnableExpiredSharesCleanup, - "janitor_run_interval": cfg.UserSharingDrivers.SQL.JanitorRunInterval, - }, - "oc10-sql": map[string]interface{}{ - "storage_mount_id": cfg.UserSharingDrivers.SQL.UserStorageMountID, - "db_username": cfg.UserSharingDrivers.SQL.DBUsername, - "db_password": cfg.UserSharingDrivers.SQL.DBPassword, - "db_host": cfg.UserSharingDrivers.SQL.DBHost, - "db_port": cfg.UserSharingDrivers.SQL.DBPort, - "db_name": cfg.UserSharingDrivers.SQL.DBName, - }, - "cs3": map[string]interface{}{ - "provider_addr": cfg.UserSharingDrivers.CS3.ProviderAddr, - "service_user_id": cfg.UserSharingDrivers.CS3.ServiceUserID, - "service_user_idp": cfg.UserSharingDrivers.CS3.ServiceUserIDP, - "machine_auth_apikey": cfg.UserSharingDrivers.CS3.MachineAuthAPIKey, - }, - }, - }, - "publicshareprovider": map[string]interface{}{ - "driver": cfg.PublicSharingDriver, - "drivers": map[string]interface{}{ - "json": map[string]interface{}{ - "file": cfg.PublicSharingDrivers.JSON.File, - "gateway_addr": cfg.Reva.Address, - }, - "sql": map[string]interface{}{ - "db_username": cfg.PublicSharingDrivers.SQL.DBUsername, - "db_password": cfg.PublicSharingDrivers.SQL.DBPassword, - "db_host": cfg.PublicSharingDrivers.SQL.DBHost, - "db_port": cfg.PublicSharingDrivers.SQL.DBPort, - "db_name": cfg.PublicSharingDrivers.SQL.DBName, - "password_hash_cost": cfg.PublicSharingDrivers.SQL.PasswordHashCost, - "enable_expired_shares_cleanup": cfg.PublicSharingDrivers.SQL.EnableExpiredSharesCleanup, - "janitor_run_interval": cfg.PublicSharingDrivers.SQL.JanitorRunInterval, - }, - "oc10-sql": map[string]interface{}{ - "storage_mount_id": cfg.PublicSharingDrivers.SQL.UserStorageMountID, - "db_username": cfg.PublicSharingDrivers.SQL.DBUsername, - "db_password": cfg.PublicSharingDrivers.SQL.DBPassword, - "db_host": cfg.PublicSharingDrivers.SQL.DBHost, - "db_port": cfg.PublicSharingDrivers.SQL.DBPort, - "db_name": cfg.PublicSharingDrivers.SQL.DBName, - "password_hash_cost": cfg.PublicSharingDrivers.SQL.PasswordHashCost, - "enable_expired_shares_cleanup": cfg.PublicSharingDrivers.SQL.EnableExpiredSharesCleanup, - "janitor_run_interval": cfg.PublicSharingDrivers.SQL.JanitorRunInterval, - }, - "cs3": map[string]interface{}{ - "provider_addr": cfg.PublicSharingDrivers.CS3.ProviderAddr, - "service_user_id": cfg.PublicSharingDrivers.CS3.ServiceUserID, - "service_user_idp": cfg.PublicSharingDrivers.CS3.ServiceUserIDP, - "machine_auth_apikey": cfg.PublicSharingDrivers.CS3.MachineAuthAPIKey, - }, - }, - }, - }, - "interceptors": map[string]interface{}{ - "eventsmiddleware": map[string]interface{}{ - "group": "sharing", - "type": "nats", - "address": cfg.Events.Addr, - "clusterID": cfg.Events.ClusterID, - }, - }, - }, - } - return rcfg -} - -// SharingSutureService allows for the storage-sharing command to be embedded and supervised by a suture supervisor tree. -type SharingSutureService struct { - cfg *config.Config -} - -// NewSharingSutureService creates a new store.SharingSutureService -func NewSharing(cfg *ociscfg.Config) suture.Service { - cfg.Sharing.Commons = cfg.Commons - return SharingSutureService{ - cfg: cfg.Sharing, - } -} - -func (s SharingSutureService) Serve(ctx context.Context) error { - // s.cfg.Reva.Sharing.Context = ctx - cmd := Sharing(s.cfg) - f := &flag.FlagSet{} - cmdFlags := cmd.Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if cmd.Before != nil { - if err := cmd.Before(cliCtx); err != nil { - return err - } - } - if err := cmd.Action(cliCtx); err != nil { - return err - } - - return nil -} diff --git a/extensions/sharing/pkg/command/health.go b/extensions/sharing/pkg/command/health.go new file mode 100644 index 0000000000..a159314c37 --- /dev/null +++ b/extensions/sharing/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/sharing/pkg/config" + "github.com/owncloud/ocis/extensions/sharing/pkg/config/parser" + "github.com/owncloud/ocis/extensions/sharing/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/sharing/pkg/command/root.go b/extensions/sharing/pkg/command/root.go new file mode 100644 index 0000000000..c2ce475e8d --- /dev/null +++ b/extensions/sharing/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/sharing/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the sharing command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "sharing", + Usage: "Provide sharing for oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the sharing command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new sharing.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.Sharing.Commons = cfg.Commons + return SutureService{ + cfg: cfg.Sharing, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/sharing/pkg/command/server.go b/extensions/sharing/pkg/command/server.go new file mode 100644 index 0000000000..fe5ad30b6f --- /dev/null +++ b/extensions/sharing/pkg/command/server.go @@ -0,0 +1,120 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + "path/filepath" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/sharing/pkg/config" + "github.com/owncloud/ocis/extensions/sharing/pkg/config/parser" + "github.com/owncloud/ocis/extensions/sharing/pkg/logging" + "github.com/owncloud/ocis/extensions/sharing/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/sharing/pkg/server/debug" + "github.com/owncloud/ocis/extensions/sharing/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/sync" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + // precreate folders + if cfg.UserSharingDriver == "json" && cfg.UserSharingDrivers.JSON.File != "" { + if err := os.MkdirAll(filepath.Dir(cfg.UserSharingDrivers.JSON.File), os.FileMode(0700)); err != nil { + return err + } + } + if cfg.PublicSharingDriver == "json" && cfg.PublicSharingDrivers.JSON.File != "" { + if err := os.MkdirAll(filepath.Dir(cfg.PublicSharingDrivers.JSON.File), os.FileMode(0700)); err != nil { + return err + } + } + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.SharingConfigFromStruct(cfg) + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + if err := external.RegisterGRPCEndpoint( + ctx, + cfg.GRPC.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.GRPC.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/sharing/pkg/command/version.go b/extensions/sharing/pkg/command/version.go new file mode 100644 index 0000000000..e54734d0ff --- /dev/null +++ b/extensions/sharing/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/sharing/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/sharing/pkg/config/config.go b/extensions/sharing/pkg/config/config.go index f81d37faa1..553ba05ec7 100644 --- a/extensions/sharing/pkg/config/config.go +++ b/extensions/sharing/pkg/config/config.go @@ -1,26 +1,33 @@ package config -import "github.com/owncloud/ocis/ocis-pkg/shared" +import ( + "context" + + "github.com/owncloud/ocis/ocis-pkg/shared" +) type Config struct { *shared.Commons `yaml:"-"` Service Service `yaml:"-"` Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` + Log *Log `yaml:"log"` Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` GRPC GRPCConfig `yaml:"grpc"` TokenManager *TokenManager `yaml:"token_manager"` Reva *Reva `yaml:"reva"` + Events Events `yaml:"events"` - SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token"` - UserSharingDriver string `yaml:"user_sharing_driver"` - UserSharingDrivers UserSharingDrivers `yaml:"user_sharin_drivers"` - PublicSharingDriver string `yaml:"public_sharing_driver"` - PublicSharingDrivers PublicSharingDrivers `yaml:"public_sharing_drivers"` - Events Events `yaml:"events"` + SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"SHARING_SKIP_USER_GROUPS_IN_TOKEN"` + + UserSharingDriver string `yaml:"user_sharing_driver" env:"SHARING_USER_DRIVER"` + UserSharingDrivers UserSharingDrivers `yaml:"user_sharing_drivers"` + PublicSharingDriver string `yaml:"public_sharing_driver" env:"SHARING_PUBLIC_DRIVER"` + PublicSharingDrivers PublicSharingDrivers `yaml:"public_sharing_drivers"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` } type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;SHARING_TRACING_ENABLED" desc:"Activates tracing."` @@ -29,7 +36,7 @@ type Tracing struct { Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;SHARING_TRACING_COLLECTOR"` } -type Logging struct { +type Log struct { Level string `yaml:"level" env:"OCIS_LOG_LEVEL;SHARING_LOG_LEVEL" desc:"The log level."` Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;SHARING_LOG_PRETTY" desc:"Activates pretty log output."` Color bool `yaml:"color" env:"OCIS_LOG_COLOR;SHARING_LOG_COLOR" desc:"Activates colorized log output."` @@ -48,69 +55,82 @@ type Debug struct { } type GRPCConfig struct { - Addr string `yaml:"addr" env:"SHARING_GRPC_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"SHARING_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` + Addr string `yaml:"addr" env:"SHARING_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"SHARING_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` } type UserSharingDrivers struct { - JSON UserSharingJSONDriver - SQL UserSharingSQLDriver - CS3 UserSharingCS3Driver + JSON UserSharingJSONDriver `yaml:"json"` + CS3 UserSharingCS3Driver `yaml:"cs3"` + OwnCloudSQL UserSharingOwnCloudSQLDriver `yaml:"owncloudsql"` + + SQL UserSharingSQLDriver `yaml:"sql,omitempty"` // not supported by the oCIS product, therefore not part of docs } type UserSharingJSONDriver struct { - File string `env:"SHARING_USER_JSON_FILE"` + File string `yaml:"file" env:"SHARING_USER_JSON_FILE"` } type UserSharingSQLDriver struct { - DBUsername string `env:"SHARING_USER_SQL_USERNAME"` - DBPassword string `env:"SHARING_USER_SQL_PASSWORD"` - DBHost string `env:"SHARING_USER_SQL_HOST"` - DBPort int `env:"SHARING_USER_SQL_PORT"` - DBName string `env:"SHARING_USER_SQL_NAME"` - PasswordHashCost int - EnableExpiredSharesCleanup bool - JanitorRunInterval int - UserStorageMountID string + DBUsername string `yaml:"db_username"` + DBPassword string `yaml:"db_password"` + DBHost string `yaml:"db_host"` + DBPort int `yaml:"db_port"` + DBName string `yaml:"db_name"` + PasswordHashCost int `yaml:"password_hash_cost"` + EnableExpiredSharesCleanup bool `yaml:"enable_expired_shares_cleanup"` + JanitorRunInterval int `yaml:"janitor_run_interval"` + UserStorageMountID string `yaml:"user_storage_mount_id"` +} + +type UserSharingOwnCloudSQLDriver struct { + DBUsername string `yaml:"db_username" env:"SHARING_USER_OWNCLOUDSQL_DB_USERNAME"` + DBPassword string `yaml:"db_password" env:"SHARING_USER_OWNCLOUDSQL_DB_PASSWORD"` + DBHost string `yaml:"db_host" env:"SHARING_USER_OWNCLOUDSQL_DB_HOST"` + DBPort int `yaml:"db_port" env:"SHARING_USER_OWNCLOUDSQL_DB_PORT"` + DBName string `yaml:"db_name" env:"SHARING_USER_OWNCLOUDSQL_DB_NAME"` + UserStorageMountID string `yaml:"user_storage_mount_id" env:"SHARING_USER_OWNCLOUDSQL_USER_STORAGE_MOUNT_ID"` } type UserSharingCS3Driver struct { - ProviderAddr string - ServiceUserID string - ServiceUserIDP string `env:"OCIS_URL;SHARING_CS3_SERVICE_USER_IDP"` - MachineAuthAPIKey string `env:"OCIS_MACHINE_AUTH_API_KEY"` + ProviderAddr string `yaml:"provider_addr" env:"SHARING_USER_CS3_PROVIDER_ADDR"` + ServiceUserID string `yaml:"service_user_id" env:"SHARING_USER_CS3_SERVICE_USER_ID"` + ServiceUserIDP string `yaml:"service_user_idp" env:"OCIS_URL;SHARING_USER_CS3_SERVICE_USER_IDP"` + MachineAuthAPIKey string `yaml:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY;SHARING_USER_CS3_MACHINE_AUTH_API_KEY"` } type PublicSharingDrivers struct { - JSON PublicSharingJSONDriver - SQL PublicSharingSQLDriver - CS3 PublicSharingCS3Driver + JSON PublicSharingJSONDriver `yaml:"json"` + CS3 PublicSharingCS3Driver `yaml:"cs3"` + + SQL PublicSharingSQLDriver `yaml:"sql,omitempty"` // not supported by the oCIS product, therefore not part of docs } type PublicSharingJSONDriver struct { - File string + File string `yaml:"file" env:"SHARING_PUBLIC_JSON_FILE"` } type PublicSharingSQLDriver struct { - DBUsername string - DBPassword string - DBHost string - DBPort int - DBName string - PasswordHashCost int - EnableExpiredSharesCleanup bool - JanitorRunInterval int - UserStorageMountID string + DBUsername string `yaml:"db_username"` + DBPassword string `yaml:"db_password"` + DBHost string `yaml:"db_host"` + DBPort int `yaml:"db_port"` + DBName string `yaml:"db_name"` + PasswordHashCost int `yaml:"password_hash_cost"` + EnableExpiredSharesCleanup bool `yaml:"enable_expired_shares_cleanup"` + JanitorRunInterval int `yaml:"janitor_run_interval"` + UserStorageMountID string `yaml:"user_storage_mount_id"` } type PublicSharingCS3Driver struct { - ProviderAddr string - ServiceUserID string - ServiceUserIDP string - MachineAuthAPIKey string `env:"OCIS_MACHINE_AUTH_API_KEY"` + ProviderAddr string `yaml:"provider_addr" env:"SHARING_PUBLIC_CS3_PROVIDER_ADDR"` + ServiceUserID string `yaml:"service_user_id" env:"SHARING_PUBLIC_CS3_SERVICE_USER_ID"` + ServiceUserIDP string `yaml:"service_user_idp" env:"OCIS_URL;SHARING_PUBLIC_CS3_SERVICE_USER_IDP"` + MachineAuthAPIKey string `yaml:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY;SHARING_PUBLIC_CS3_MACHINE_AUTH_API_KEY"` } type Events struct { - Addr string - ClusterID string + Addr string `yaml:"endpoint" env:"SHARING_EVENTS_ENDPOINT" desc:"the address of the streaming service"` + ClusterID string `yaml:"cluster" env:"SHARING_EVENTS_CLUSTER" desc:"the clusterID of the streaming service. Mandatory when using nats"` } diff --git a/extensions/sharing/pkg/config/defaults/defaultconfig.go b/extensions/sharing/pkg/config/defaults/defaultconfig.go index 924e432288..d4538c4204 100644 --- a/extensions/sharing/pkg/config/defaults/defaultconfig.go +++ b/extensions/sharing/pkg/config/defaults/defaultconfig.go @@ -23,8 +23,9 @@ func DefaultConfig() *config.Config { Zpages: false, }, GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9150", - Protocol: "tcp", + Addr: "127.0.0.1:9150", + Namespace: "com.owncloud.api", + Protocol: "tcp", }, Service: config.Service{ Name: "sharing", @@ -32,41 +33,21 @@ func DefaultConfig() *config.Config { Reva: &config.Reva{ Address: "127.0.0.1:9142", }, - UserSharingDriver: "json", + UserSharingDriver: "json", //"cs3", UserSharingDrivers: config.UserSharingDrivers{ JSON: config.UserSharingJSONDriver{ File: filepath.Join(defaults.BaseDataPath(), "storage", "shares.json"), }, - SQL: config.UserSharingSQLDriver{ - DBUsername: "", - DBPassword: "", - DBHost: "", - DBPort: 1433, - DBName: "", - PasswordHashCost: 11, - EnableExpiredSharesCleanup: true, - JanitorRunInterval: 60, - }, CS3: config.UserSharingCS3Driver{ ProviderAddr: "127.0.0.1:9215", // metadata storage ServiceUserIDP: "internal", }, }, - PublicSharingDriver: "json", + PublicSharingDriver: "json", // "cs3", PublicSharingDrivers: config.PublicSharingDrivers{ JSON: config.PublicSharingJSONDriver{ File: filepath.Join(defaults.BaseDataPath(), "storage", "publicshares.json"), }, - SQL: config.PublicSharingSQLDriver{ - DBUsername: "", - DBPassword: "", - DBHost: "", - DBPort: 1433, - DBName: "", - PasswordHashCost: 11, - EnableExpiredSharesCleanup: true, - JanitorRunInterval: 60, - }, CS3: config.PublicSharingCS3Driver{ ProviderAddr: "127.0.0.1:9215", // metadata storage ServiceUserIDP: "internal", @@ -81,15 +62,15 @@ func DefaultConfig() *config.Config { func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/sharing/pkg/logging/logging.go b/extensions/sharing/pkg/logging/logging.go new file mode 100644 index 0000000000..364f2a76de --- /dev/null +++ b/extensions/sharing/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/sharing/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/sharing/pkg/revaconfig/config.go b/extensions/sharing/pkg/revaconfig/config.go new file mode 100644 index 0000000000..00a8d3a251 --- /dev/null +++ b/extensions/sharing/pkg/revaconfig/config.go @@ -0,0 +1,96 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/extensions/sharing/pkg/config" +) + +// SharingConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func SharingConfigFromStruct(cfg *config.Config) map[string]interface{} { + rcfg := map[string]interface{}{ + "core": map[string]interface{}{ + "tracing_enabled": cfg.Tracing.Enabled, + "tracing_endpoint": cfg.Tracing.Endpoint, + "tracing_collector": cfg.Tracing.Collector, + "tracing_service_name": cfg.Service.Name, + }, + "shared": map[string]interface{}{ + "jwt_secret": cfg.TokenManager.JWTSecret, + "gatewaysvc": cfg.Reva.Address, + "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, + }, + "grpc": map[string]interface{}{ + "network": cfg.GRPC.Protocol, + "address": cfg.GRPC.Addr, + // TODO build services dynamically + "services": map[string]interface{}{ + "usershareprovider": map[string]interface{}{ + "driver": cfg.UserSharingDriver, + "drivers": map[string]interface{}{ + "json": map[string]interface{}{ + "file": cfg.UserSharingDrivers.JSON.File, + "gateway_addr": cfg.Reva.Address, + }, + "sql": map[string]interface{}{ // cernbox sql + "db_username": cfg.UserSharingDrivers.SQL.DBUsername, + "db_password": cfg.UserSharingDrivers.SQL.DBPassword, + "db_host": cfg.UserSharingDrivers.SQL.DBHost, + "db_port": cfg.UserSharingDrivers.SQL.DBPort, + "db_name": cfg.UserSharingDrivers.SQL.DBName, + "password_hash_cost": cfg.UserSharingDrivers.SQL.PasswordHashCost, + "enable_expired_shares_cleanup": cfg.UserSharingDrivers.SQL.EnableExpiredSharesCleanup, + "janitor_run_interval": cfg.UserSharingDrivers.SQL.JanitorRunInterval, + }, + "owncloudsql": map[string]interface{}{ + "storage_mount_id": cfg.UserSharingDrivers.OwnCloudSQL.UserStorageMountID, + "db_username": cfg.UserSharingDrivers.OwnCloudSQL.DBUsername, + "db_password": cfg.UserSharingDrivers.OwnCloudSQL.DBPassword, + "db_host": cfg.UserSharingDrivers.OwnCloudSQL.DBHost, + "db_port": cfg.UserSharingDrivers.OwnCloudSQL.DBPort, + "db_name": cfg.UserSharingDrivers.OwnCloudSQL.DBName, + }, + "cs3": map[string]interface{}{ + "provider_addr": cfg.UserSharingDrivers.CS3.ProviderAddr, + "service_user_id": cfg.UserSharingDrivers.CS3.ServiceUserID, + "service_user_idp": cfg.UserSharingDrivers.CS3.ServiceUserIDP, + "machine_auth_apikey": cfg.UserSharingDrivers.CS3.MachineAuthAPIKey, + }, + }, + }, + "publicshareprovider": map[string]interface{}{ + "driver": cfg.PublicSharingDriver, + "drivers": map[string]interface{}{ + "json": map[string]interface{}{ + "file": cfg.PublicSharingDrivers.JSON.File, + "gateway_addr": cfg.Reva.Address, + }, + "sql": map[string]interface{}{ + "db_username": cfg.PublicSharingDrivers.SQL.DBUsername, + "db_password": cfg.PublicSharingDrivers.SQL.DBPassword, + "db_host": cfg.PublicSharingDrivers.SQL.DBHost, + "db_port": cfg.PublicSharingDrivers.SQL.DBPort, + "db_name": cfg.PublicSharingDrivers.SQL.DBName, + "password_hash_cost": cfg.PublicSharingDrivers.SQL.PasswordHashCost, + "enable_expired_shares_cleanup": cfg.PublicSharingDrivers.SQL.EnableExpiredSharesCleanup, + "janitor_run_interval": cfg.PublicSharingDrivers.SQL.JanitorRunInterval, + }, + "cs3": map[string]interface{}{ + "provider_addr": cfg.PublicSharingDrivers.CS3.ProviderAddr, + "service_user_id": cfg.PublicSharingDrivers.CS3.ServiceUserID, + "service_user_idp": cfg.PublicSharingDrivers.CS3.ServiceUserIDP, + "machine_auth_apikey": cfg.PublicSharingDrivers.CS3.MachineAuthAPIKey, + }, + }, + }, + }, + "interceptors": map[string]interface{}{ + "eventsmiddleware": map[string]interface{}{ + "group": "sharing", + "type": "nats", + "address": cfg.Events.Addr, + "clusterID": cfg.Events.ClusterID, + }, + }, + }, + } + return rcfg +} diff --git a/extensions/sharing/pkg/server/debug/option.go b/extensions/sharing/pkg/server/debug/option.go new file mode 100644 index 0000000000..9667bca9e4 --- /dev/null +++ b/extensions/sharing/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/extensions/sharing/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Logger log.Logger + Context context.Context + Config *config.Config +} + +// newOptions initializes the available default options. +func newOptions(opts ...Option) Options { + opt := Options{} + + for _, o := range opts { + o(&opt) + } + + return opt +} + +// Logger provides a function to set the logger option. +func Logger(val log.Logger) Option { + return func(o *Options) { + o.Logger = val + } +} + +// Context provides a function to set the context option. +func Context(val context.Context) Option { + return func(o *Options) { + o.Context = val + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} diff --git a/extensions/sharing/pkg/server/debug/server.go b/extensions/sharing/pkg/server/debug/server.go new file mode 100644 index 0000000000..d7ce79028f --- /dev/null +++ b/extensions/sharing/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/extensions/sharing/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/service/debug" + "github.com/owncloud/ocis/ocis-pkg/version" +) + +// Server initializes the debug service and server. +func Server(opts ...Option) (*http.Server, error) { + options := newOptions(opts...) + + return debug.NewService( + debug.Logger(options.Logger), + debug.Name(options.Config.Service.Name), + debug.Version(version.String), + debug.Address(options.Config.Debug.Addr), + debug.Token(options.Config.Debug.Token), + debug.Pprof(options.Config.Debug.Pprof), + debug.Zpages(options.Config.Debug.Zpages), + debug.Health(health(options.Config)), + debug.Ready(ready(options.Config)), + //debug.CorsAllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins), + //debug.CorsAllowedMethods(options.Config.HTTP.CORS.AllowedMethods), + //debug.CorsAllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders), + //debug.CorsAllowCredentials(options.Config.HTTP.CORS.AllowCredentials), + ), nil +} + +// health implements the health check. +func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} + +// ready implements the ready check. +func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} diff --git a/extensions/sharing/pkg/tracing/tracing.go b/extensions/sharing/pkg/tracing/tracing.go new file mode 100644 index 0000000000..55a39d9ffc --- /dev/null +++ b/extensions/sharing/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/sharing/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/storage-metadata/Makefile b/extensions/storage-metadata/Makefile new file mode 100644 index 0000000000..67d5ce4e3a --- /dev/null +++ b/extensions/storage-metadata/Makefile @@ -0,0 +1,37 @@ +SHELL := bash +NAME := storage-metadata + +include ../../.make/recursion.mk + +############ tooling ############ +ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI +include ../../.bingo/Variables.mk +endif + +############ go tooling ############ +include ../../.make/go.mk + +############ release ############ +include ../../.make/release.mk + +############ docs generate ############ +include ../../.make/docs.mk + +.PHONY: docs-generate +docs-generate: config-docs-generate + +############ generate ############ +include ../../.make/generate.mk + +.PHONY: ci-go-generate +ci-go-generate: # CI runs ci-node-generate automatically before this target + +.PHONY: ci-node-generate +ci-node-generate: + +############ licenses ############ +.PHONY: ci-node-check-licenses +ci-node-check-licenses: + +.PHONY: ci-node-save-licenses +ci-node-save-licenses: diff --git a/extensions/storage-metadata/cmd/storage-metadata/main.go b/extensions/storage-metadata/cmd/storage-metadata/main.go new file mode 100644 index 0000000000..e52ef1a27b --- /dev/null +++ b/extensions/storage-metadata/cmd/storage-metadata/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/command" + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/storage-metadata/pkg/command/command.go b/extensions/storage-metadata/pkg/command/command.go deleted file mode 100644 index 54eff79d45..0000000000 --- a/extensions/storage-metadata/pkg/command/command.go +++ /dev/null @@ -1,271 +0,0 @@ -package command - -import ( - "context" - "flag" - "fmt" - "os" - "path" - - userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - "github.com/cs3org/reva/v2/cmd/revad/runtime" - "github.com/gofrs/uuid" - "github.com/oklog/run" - "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config" - "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config/parser" - "github.com/owncloud/ocis/extensions/storage/pkg/server/debug" - "github.com/owncloud/ocis/extensions/storage/pkg/service/external" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - "github.com/owncloud/ocis/ocis-pkg/version" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" -) - -// StorageMetadata the entrypoint for the storage-storage-metadata command. -// -// It provides a ocis-specific storage store metadata (shares,account,settings...) -func StorageMetadata(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "storage-metadata", - Usage: "start storage-metadata service", - Category: "extensions", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - - gr := run.Group{} - ctx, cancel := func() (context.Context, context.CancelFunc) { - if cfg.Context == nil { - return context.WithCancel(context.Background()) - } - return context.WithCancel(cfg.Context) - }() - - defer cancel() - - pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") - rcfg := storageMetadataFromStruct(c, cfg) - - gr.Add(func() error { - runtime.RunWithOptions( - rcfg, - pidFile, - runtime.WithLogger(&logger.Logger), - ) - return nil - }, func(_ error) { - logger.Info(). - Str("server", c.Command.Name). - Msg("Shutting down server") - - cancel() - }) - - debugServer, err := debug.Server( - debug.Name(c.Command.Name+"-debug"), - debug.Addr(cfg.Debug.Addr), - debug.Logger(logger), - debug.Context(ctx), - debug.Pprof(cfg.Debug.Pprof), - debug.Zpages(cfg.Debug.Zpages), - debug.Token(cfg.Debug.Token), - ) - - if err != nil { - logger.Info(). - Err(err). - Str("server", c.Command.Name+"-debug"). - Msg("Failed to initialize server") - - return err - } - - gr.Add(func() error { - return debugServer.ListenAndServe() - }, func(_ error) { - _ = debugServer.Shutdown(ctx) - cancel() - }) - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - if err := external.RegisterGRPCEndpoint( - ctx, - "com.owncloud.storage.metadata", - uuid.Must(uuid.NewV4()).String(), - cfg.GRPC.Addr, - version.String, - logger, - ); err != nil { - logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") - } - - return gr.Run() - }, - } -} - -// storageMetadataFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func storageMetadataFromStruct(c *cli.Context, cfg *config.Config) map[string]interface{} { - rcfg := map[string]interface{}{ - "core": map[string]interface{}{ - "tracing_enabled": cfg.Tracing.Enabled, - "tracing_endpoint": cfg.Tracing.Endpoint, - "tracing_collector": cfg.Tracing.Collector, - "tracing_service_name": c.Command.Name, - }, - "shared": map[string]interface{}{ - "jwt_secret": cfg.TokenManager.JWTSecret, - "gatewaysvc": cfg.Reva.Address, - "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, - }, - "grpc": map[string]interface{}{ - "network": cfg.GRPC.Protocol, - "address": cfg.GRPC.Addr, - "services": map[string]interface{}{ - "gateway": map[string]interface{}{ - // registries are located on the gateway - "authregistrysvc": cfg.GRPC.Addr, - "storageregistrysvc": cfg.GRPC.Addr, - // user metadata is located on the users services - "userprovidersvc": cfg.GRPC.Addr, - "groupprovidersvc": cfg.GRPC.Addr, - "permissionssvc": cfg.GRPC.Addr, - // other - "disable_home_creation_on_login": true, // metadata manually creates a space - // metadata always uses the simple upload, so no transfer secret or datagateway needed - }, - "userprovider": map[string]interface{}{ - "driver": "memory", - "drivers": map[string]interface{}{ - "memory": map[string]interface{}{ - "users": map[string]interface{}{ - "serviceuser": map[string]interface{}{ - "id": map[string]interface{}{ - "opaqueId": cfg.MetadataUserID, - "idp": "internal", - "type": userpb.UserType_USER_TYPE_PRIMARY, - }, - "username": "serviceuser", - "display_name": "System User", - }, - }, - }, - }, - }, - "authregistry": map[string]interface{}{ - "driver": "static", - "drivers": map[string]interface{}{ - "static": map[string]interface{}{ - "rules": map[string]interface{}{ - "machine": cfg.GRPC.Addr, - }, - }, - }, - }, - "authprovider": map[string]interface{}{ - "auth_manager": "machine", - "auth_managers": map[string]interface{}{ - "machine": map[string]interface{}{ - "api_key": cfg.MachineAuthAPIKey, - "gateway_addr": cfg.GRPC.Addr, - }, - }, - }, - "permissions": map[string]interface{}{ - "driver": "demo", - "drivers": map[string]interface{}{ - "demo": map[string]interface{}{}, - }, - }, - "storageregistry": map[string]interface{}{ - "driver": "static", - "drivers": map[string]interface{}{ - "static": map[string]interface{}{ - "rules": map[string]interface{}{ - "/": map[string]interface{}{ - "address": cfg.GRPC.Addr, - }, - }, - }, - }, - }, - "storageprovider": map[string]interface{}{ - "driver": cfg.Driver, - "drivers": config.MetadataDrivers(cfg), - "data_server_url": cfg.DataServerURL, - "tmp_folder": cfg.TempFolder, - }, - }, - }, - "http": map[string]interface{}{ - "network": cfg.HTTP.Protocol, - "address": cfg.HTTP.Addr, - // no datagateway needed as the metadata clients directly talk to the dataprovider with the simple protocol - "services": map[string]interface{}{ - "dataprovider": map[string]interface{}{ - "prefix": "data", - "driver": cfg.Driver, - "drivers": config.MetadataDrivers(cfg), - "timeout": 86400, - "insecure": cfg.DataProviderInsecure, - "disable_tus": true, - }, - }, - }, - } - return rcfg -} - -// SutureService allows for the storage-metadata command to be embedded and supervised by a suture supervisor tree. -type MetadataSutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new storagemetadata.SutureService -func NewStorageMetadata(cfg *ociscfg.Config) suture.Service { - cfg.StorageMetadata.Commons = cfg.Commons - return MetadataSutureService{ - cfg: cfg.StorageMetadata, - } -} - -func (s MetadataSutureService) Serve(ctx context.Context) error { - s.cfg.Context = ctx - f := &flag.FlagSet{} - cmdFlags := StorageMetadata(s.cfg).Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if StorageMetadata(s.cfg).Before != nil { - if err := StorageMetadata(s.cfg).Before(cliCtx); err != nil { - return err - } - } - if err := StorageMetadata(s.cfg).Action(cliCtx); err != nil { - return err - } - - return nil -} diff --git a/extensions/storage-metadata/pkg/command/health.go b/extensions/storage-metadata/pkg/command/health.go new file mode 100644 index 0000000000..66274f9e72 --- /dev/null +++ b/extensions/storage-metadata/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config" + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config/parser" + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/storage-metadata/pkg/command/root.go b/extensions/storage-metadata/pkg/command/root.go new file mode 100644 index 0000000000..c1949566f6 --- /dev/null +++ b/extensions/storage-metadata/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the storage-metadata command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "storage-metadata", + Usage: "Provide metadata storage for oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the storage-metadata command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new storage-metadata.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.StorageMetadata.Commons = cfg.Commons + return SutureService{ + cfg: cfg.StorageMetadata, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/storage-metadata/pkg/command/server.go b/extensions/storage-metadata/pkg/command/server.go new file mode 100644 index 0000000000..43b720eb29 --- /dev/null +++ b/extensions/storage-metadata/pkg/command/server.go @@ -0,0 +1,118 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config" + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config/parser" + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/logging" + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/server/debug" + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/sync" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.StorageMetadataFromStruct(cfg) + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + if err := external.RegisterGRPCEndpoint( + ctx, + cfg.GRPC.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.GRPC.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + } + + if err := external.RegisterHTTPEndpoint( + ctx, + cfg.HTTP.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.HTTP.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the http endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/storage-metadata/pkg/command/version.go b/extensions/storage-metadata/pkg/command/version.go new file mode 100644 index 0000000000..0f58c54990 --- /dev/null +++ b/extensions/storage-metadata/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/storage-metadata/pkg/config/config.go b/extensions/storage-metadata/pkg/config/config.go index 8c4475600f..b6a285c41f 100644 --- a/extensions/storage-metadata/pkg/config/config.go +++ b/extensions/storage-metadata/pkg/config/config.go @@ -10,26 +10,27 @@ type Config struct { *shared.Commons `yaml:"-"` Service Service `yaml:"-"` Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` + Log *Log `yaml:"log"` Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` GRPC GRPCConfig `yaml:"grpc"` HTTP HTTPConfig `yaml:"http"` - Context context.Context `yaml:"context"` - TokenManager *TokenManager `yaml:"token_manager"` Reva *Reva `yaml:"reva"` MachineAuthAPIKey string `yaml:"machine_auth_api_key" env:"STORAGE_METADATA_MACHINE_AUTH_API_KEY"` MetadataUserID string `yaml:"metadata_user_id"` - SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token"` - Driver string `yaml:"driver" env:"STORAGE_METADATA_DRIVER" desc:"The driver which should be used by the service"` - Drivers Drivers `yaml:"drivers"` - DataServerURL string `yaml:"data_server_url"` - TempFolder string `yaml:"temp_folder"` - DataProviderInsecure bool `yaml:"data_provider_insecure" env:"OCIS_INSECURE;STORAGE_METADATA_DATAPROVIDER_INSECURE"` + SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"STORAGE_METADATA_SKIP_USER_GROUPS_IN_TOKEN"` + + Driver string `yaml:"driver" env:"STORAGE_METADATA_DRIVER" desc:"The driver which should be used by the service"` + Drivers Drivers `yaml:"drivers"` + DataServerURL string `yaml:"data_server_url" env:"STORAGE_METADATA_DATA_SERVER_URL"` + TempFolder string `yaml:"temp_folder" env:"STORAGE_METADATA_TEMP_FOLDER"` + DataProviderInsecure bool `yaml:"data_provider_insecure" env:"OCIS_INSECURE;STORAGE_METADATA_DATAPROVIDER_INSECURE"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` } type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;STORAGE_METADATA_TRACING_ENABLED" desc:"Activates tracing."` @@ -38,7 +39,7 @@ type Tracing struct { Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;STORAGE_METADATA_TRACING_COLLECTOR"` } -type Logging struct { +type Log struct { Level string `yaml:"level" env:"OCIS_LOG_LEVEL;STORAGE_METADATA_LOG_LEVEL" desc:"The log level."` Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;STORAGE_METADATA_LOG_PRETTY" desc:"Activates pretty log output."` Color bool `yaml:"color" env:"OCIS_LOG_COLOR;STORAGE_METADATA_LOG_COLOR" desc:"Activates colorized log output."` @@ -57,95 +58,22 @@ type Debug struct { } type GRPCConfig struct { - Addr string `yaml:"addr" env:"STORAGE_METADATA_GRPC_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"STORAGE_METADATA_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` + Addr string `yaml:"addr" env:"STORAGE_METADATA_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"STORAGE_METADATA_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` } type HTTPConfig struct { - Addr string `yaml:"addr" env:"STORAGE_METADATA_HTTP_ADDR" desc:"The address of the http service."` - Protocol string `yaml:"protocol" env:"STORAGE_METADATA_HTTP_PROTOCOL" desc:"The transport protocol of the http service."` + Addr string `yaml:"addr" env:"STORAGE_METADATA_HTTP_ADDR" desc:"The address of the http service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"STORAGE_METADATA_HTTP_PROTOCOL" desc:"The transport protocol of the http service."` } type Drivers struct { - EOS EOSDriver - Local LocalDriver - OCIS OCISDriver - S3 S3Driver - S3NG S3NGDriver -} - -type EOSDriver struct { - // Root is the absolute path to the location of the data - Root string `yaml:"root"` - // ShadowNamespace for storing shadow data - ShadowNamespace string `yaml:"shadow_namespace"` - // UploadsNamespace for storing upload data - UploadsNamespace string `yaml:"uploads_namespace"` - // Location of the eos binary. - // Default is /usr/bin/eos. - EosBinary string `yaml:"eos_binary"` - // Location of the xrdcopy binary. - // Default is /usr/bin/xrdcopy. - XrdcopyBinary string `yaml:"xrd_copy_binary"` - // URL of the Master EOS MGM. - // Default is root://eos-example.org - MasterURL string `yaml:"master_url"` - // URL of the Slave EOS MGM. - // Default is root://eos-example.org - SlaveURL string `yaml:"slave_url"` - // Location on the local fs where to store reads. - // Defaults to os.TempDir() - CacheDirectory string `yaml:"cache_directory"` - // SecProtocol specifies the xrootd security protocol to use between the server and EOS. - SecProtocol string `yaml:"sec_protocol"` - // Keytab specifies the location of the keytab to use to authenticate to EOS. - Keytab string `yaml:"keytab"` - // SingleUsername is the username to use when SingleUserMode is enabled - SingleUsername string `yaml:"single_username"` - // Enables logging of the commands executed - // Defaults to false - EnableLogging bool `yaml:"enable_logging"` - // ShowHiddenSysFiles shows internal EOS files like - // .sys.v# and .sys.a# files. - ShowHiddenSysFiles bool `yaml:"shadow_hidden_files"` - // ForceSingleUserMode will force connections to EOS to use SingleUsername - ForceSingleUserMode bool `yaml:"force_single_user_mode"` - // UseKeyTabAuth changes will authenticate requests by using an EOS keytab. - UseKeytab bool `yaml:"user_keytab"` - // gateway service to use for uid lookups - GatewaySVC string `yaml:"gateway_svc"` - GRPCURI string - UserLayout string -} - -type LocalDriver struct { - // Root is the absolute path to the location of the data - Root string `yaml:"root"` + OCIS OCISDriver `yaml:"ocis"` } type OCISDriver struct { // Root is the absolute path to the location of the data - Root string `yaml:"root" env:"STORAGE_METADATA_DRIVER_OCIS_ROOT"` - UserLayout string - PermissionsEndpoint string -} - -type S3Driver struct { - Region string `yaml:"region"` - AccessKey string `yaml:"access_key"` - SecretKey string `yaml:"secret_key"` - Endpoint string `yaml:"endpoint"` - Bucket string `yaml:"bucket"` -} - -type S3NGDriver struct { - // Root is the absolute path to the location of the data - Root string `yaml:"root"` - UserLayout string - PermissionsEndpoint string - Region string `yaml:"region"` - AccessKey string `yaml:"access_key"` - SecretKey string `yaml:"secret_key"` - Endpoint string `yaml:"endpoint"` - Bucket string `yaml:"bucket"` + Root string `yaml:"root" env:"STORAGE_METADATA_OCIS_ROOT"` } diff --git a/extensions/storage-metadata/pkg/config/defaults/defaultconfig.go b/extensions/storage-metadata/pkg/config/defaults/defaultconfig.go index 4f274aa0ca..0b9c1d2a50 100644 --- a/extensions/storage-metadata/pkg/config/defaults/defaultconfig.go +++ b/extensions/storage-metadata/pkg/config/defaults/defaultconfig.go @@ -1,7 +1,6 @@ package defaults import ( - "os" "path/filepath" "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config" @@ -24,12 +23,14 @@ func DefaultConfig() *config.Config { Zpages: false, }, GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9215", - Protocol: "tcp", + Addr: "127.0.0.1:9215", + Namespace: "com.owncloud.api", + Protocol: "tcp", }, HTTP: config.HTTPConfig{ - Addr: "127.0.0.1:9216", - Protocol: "tcp", + Addr: "127.0.0.1:9216", + Namespace: "com.owncloud.web", + Protocol: "tcp", }, Service: config.Service{ Name: "storage-metadata", @@ -41,42 +42,8 @@ func DefaultConfig() *config.Config { DataServerURL: "http://localhost:9216/data", Driver: "ocis", Drivers: config.Drivers{ - EOS: config.EOSDriver{ - Root: "/eos/dockertest/reva", - UserLayout: "{{substr 0 1 .Username}}/{{.Username}}", - ShadowNamespace: "", - UploadsNamespace: "", - EosBinary: "/usr/bin/eos", - XrdcopyBinary: "/usr/bin/xrdcopy", - MasterURL: "root://eos-mgm1.eoscluster.cern.ch:1094", - GRPCURI: "", - SlaveURL: "root://eos-mgm1.eoscluster.cern.ch:1094", - CacheDirectory: os.TempDir(), - EnableLogging: false, - ShowHiddenSysFiles: false, - ForceSingleUserMode: false, - UseKeytab: false, - SecProtocol: "", - Keytab: "", - SingleUsername: "", - GatewaySVC: "127.0.0.1:9215", - }, - Local: config.LocalDriver{ - Root: filepath.Join(defaults.BaseDataPath(), "storage", "local", "metadata"), - }, - S3: config.S3Driver{ - Region: "default", - }, - S3NG: config.S3NGDriver{ - Root: filepath.Join(defaults.BaseDataPath(), "storage", "metadata"), - UserLayout: "{{.Id.OpaqueId}}", - Region: "default", - PermissionsEndpoint: "127.0.0.1:9215", - }, OCIS: config.OCISDriver{ - Root: filepath.Join(defaults.BaseDataPath(), "storage", "metadata"), - UserLayout: "{{.Id.OpaqueId}}", - PermissionsEndpoint: "127.0.0.1:9215", + Root: filepath.Join(defaults.BaseDataPath(), "storage", "metadata"), }, }, } @@ -84,15 +51,15 @@ func DefaultConfig() *config.Config { func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/storage-metadata/pkg/config/metadata.go b/extensions/storage-metadata/pkg/config/metadata.go deleted file mode 100644 index e580882378..0000000000 --- a/extensions/storage-metadata/pkg/config/metadata.go +++ /dev/null @@ -1,75 +0,0 @@ -package config - -func MetadataDrivers(cfg *Config) map[string]interface{} { - return map[string]interface{}{ - "eos": map[string]interface{}{ - "namespace": cfg.Drivers.EOS.Root, - "shadow_namespace": cfg.Drivers.EOS.ShadowNamespace, - "uploads_namespace": cfg.Drivers.EOS.UploadsNamespace, - "eos_binary": cfg.Drivers.EOS.EosBinary, - "xrdcopy_binary": cfg.Drivers.EOS.XrdcopyBinary, - "master_url": cfg.Drivers.EOS.MasterURL, - "slave_url": cfg.Drivers.EOS.SlaveURL, - "cache_directory": cfg.Drivers.EOS.CacheDirectory, - "sec_protocol": cfg.Drivers.EOS.SecProtocol, - "keytab": cfg.Drivers.EOS.Keytab, - "single_username": cfg.Drivers.EOS.SingleUsername, - "enable_logging": cfg.Drivers.EOS.EnableLogging, - "show_hidden_sys_files": cfg.Drivers.EOS.ShowHiddenSysFiles, - "force_single_user_mode": cfg.Drivers.EOS.ForceSingleUserMode, - "use_keytab": cfg.Drivers.EOS.UseKeytab, - "gatewaysvc": cfg.Drivers.EOS.GatewaySVC, - "enable_home": false, - }, - "eosgrpc": map[string]interface{}{ - "namespace": cfg.Drivers.EOS.Root, - "shadow_namespace": cfg.Drivers.EOS.ShadowNamespace, - "eos_binary": cfg.Drivers.EOS.EosBinary, - "xrdcopy_binary": cfg.Drivers.EOS.XrdcopyBinary, - "master_url": cfg.Drivers.EOS.MasterURL, - "master_grpc_uri": cfg.Drivers.EOS.GRPCURI, - "slave_url": cfg.Drivers.EOS.SlaveURL, - "cache_directory": cfg.Drivers.EOS.CacheDirectory, - "sec_protocol": cfg.Drivers.EOS.SecProtocol, - "keytab": cfg.Drivers.EOS.Keytab, - "single_username": cfg.Drivers.EOS.SingleUsername, - "user_layout": cfg.Drivers.EOS.UserLayout, - "enable_logging": cfg.Drivers.EOS.EnableLogging, - "show_hidden_sys_files": cfg.Drivers.EOS.ShowHiddenSysFiles, - "force_single_user_mode": cfg.Drivers.EOS.ForceSingleUserMode, - "use_keytab": cfg.Drivers.EOS.UseKeytab, - "enable_home": false, - "gatewaysvc": cfg.Drivers.EOS.GatewaySVC, - }, - "local": map[string]interface{}{ - "root": cfg.Drivers.Local.Root, - }, - "ocis": map[string]interface{}{ - "root": cfg.Drivers.OCIS.Root, - "user_layout": cfg.Drivers.OCIS.UserLayout, - "treetime_accounting": false, - "treesize_accounting": false, - "permissionssvc": cfg.Drivers.OCIS.PermissionsEndpoint, - }, - "s3": map[string]interface{}{ - "region": cfg.Drivers.S3.Region, - "access_key": cfg.Drivers.S3.AccessKey, - "secret_key": cfg.Drivers.S3.SecretKey, - "endpoint": cfg.Drivers.S3.Endpoint, - "bucket": cfg.Drivers.S3.Bucket, - }, - "s3ng": map[string]interface{}{ - "root": cfg.Drivers.S3NG.Root, - "enable_home": false, - "user_layout": cfg.Drivers.S3NG.UserLayout, - "treetime_accounting": false, - "treesize_accounting": false, - "permissionssvc": cfg.Drivers.S3NG.PermissionsEndpoint, - "s3.region": cfg.Drivers.S3NG.Region, - "s3.access_key": cfg.Drivers.S3NG.AccessKey, - "s3.secret_key": cfg.Drivers.S3NG.SecretKey, - "s3.endpoint": cfg.Drivers.S3NG.Endpoint, - "s3.bucket": cfg.Drivers.S3NG.Bucket, - }, - } -} diff --git a/extensions/storage-metadata/pkg/logging/logging.go b/extensions/storage-metadata/pkg/logging/logging.go new file mode 100644 index 0000000000..feef4ad399 --- /dev/null +++ b/extensions/storage-metadata/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/storage-metadata/pkg/revaconfig/config.go b/extensions/storage-metadata/pkg/revaconfig/config.go new file mode 100644 index 0000000000..70b5056dbc --- /dev/null +++ b/extensions/storage-metadata/pkg/revaconfig/config.go @@ -0,0 +1,130 @@ +package revaconfig + +import ( + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config" +) + +// StorageMetadataFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func StorageMetadataFromStruct(cfg *config.Config) map[string]interface{} { + rcfg := map[string]interface{}{ + "core": map[string]interface{}{ + "tracing_enabled": cfg.Tracing.Enabled, + "tracing_endpoint": cfg.Tracing.Endpoint, + "tracing_collector": cfg.Tracing.Collector, + "tracing_service_name": cfg.Service.Name, + }, + "shared": map[string]interface{}{ + "jwt_secret": cfg.TokenManager.JWTSecret, + "gatewaysvc": cfg.Reva.Address, + "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, + }, + "grpc": map[string]interface{}{ + "network": cfg.GRPC.Protocol, + "address": cfg.GRPC.Addr, + "services": map[string]interface{}{ + "gateway": map[string]interface{}{ + // registries are located on the gateway + "authregistrysvc": cfg.GRPC.Addr, + "storageregistrysvc": cfg.GRPC.Addr, + // user metadata is located on the users services + "userprovidersvc": cfg.GRPC.Addr, + "groupprovidersvc": cfg.GRPC.Addr, + "permissionssvc": cfg.GRPC.Addr, + // other + "disable_home_creation_on_login": true, // metadata manually creates a space + // metadata always uses the simple upload, so no transfer secret or datagateway needed + }, + "userprovider": map[string]interface{}{ + "driver": "memory", + "drivers": map[string]interface{}{ + "memory": map[string]interface{}{ + "users": map[string]interface{}{ + "serviceuser": map[string]interface{}{ + "id": map[string]interface{}{ + "opaqueId": cfg.MetadataUserID, + "idp": "internal", + "type": userpb.UserType_USER_TYPE_PRIMARY, + }, + "username": "serviceuser", + "display_name": "System User", + }, + }, + }, + }, + }, + "authregistry": map[string]interface{}{ + "driver": "static", + "drivers": map[string]interface{}{ + "static": map[string]interface{}{ + "rules": map[string]interface{}{ + "machine": cfg.GRPC.Addr, + }, + }, + }, + }, + "authprovider": map[string]interface{}{ + "auth_manager": "machine", + "auth_managers": map[string]interface{}{ + "machine": map[string]interface{}{ + "api_key": cfg.MachineAuthAPIKey, + "gateway_addr": cfg.GRPC.Addr, + }, + }, + }, + "permissions": map[string]interface{}{ + "driver": "demo", + "drivers": map[string]interface{}{ + "demo": map[string]interface{}{}, + }, + }, + "storageregistry": map[string]interface{}{ + "driver": "static", + "drivers": map[string]interface{}{ + "static": map[string]interface{}{ + "rules": map[string]interface{}{ + "/": map[string]interface{}{ + "address": cfg.GRPC.Addr, + }, + }, + }, + }, + }, + "storageprovider": map[string]interface{}{ + "driver": cfg.Driver, + "drivers": metadataDrivers(cfg), + "data_server_url": cfg.DataServerURL, + "tmp_folder": cfg.TempFolder, + }, + }, + }, + "http": map[string]interface{}{ + "network": cfg.HTTP.Protocol, + "address": cfg.HTTP.Addr, + // no datagateway needed as the metadata clients directly talk to the dataprovider with the simple protocol + "services": map[string]interface{}{ + "dataprovider": map[string]interface{}{ + "prefix": "data", + "driver": cfg.Driver, + "drivers": metadataDrivers(cfg), + "timeout": 86400, + "insecure": cfg.DataProviderInsecure, + "disable_tus": true, + }, + }, + }, + } + return rcfg +} + +func metadataDrivers(cfg *config.Config) map[string]interface{} { + return map[string]interface{}{ + "ocis": map[string]interface{}{ + "root": cfg.Drivers.OCIS.Root, + "user_layout": "{{.Id.OpaqueId}}", + "treetime_accounting": false, + "treesize_accounting": false, + "permissionssvc": cfg.GRPC.Addr, + }, + } +} diff --git a/extensions/storage-metadata/pkg/server/debug/option.go b/extensions/storage-metadata/pkg/server/debug/option.go new file mode 100644 index 0000000000..b138e2e70a --- /dev/null +++ b/extensions/storage-metadata/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Logger log.Logger + Context context.Context + Config *config.Config +} + +// newOptions initializes the available default options. +func newOptions(opts ...Option) Options { + opt := Options{} + + for _, o := range opts { + o(&opt) + } + + return opt +} + +// Logger provides a function to set the logger option. +func Logger(val log.Logger) Option { + return func(o *Options) { + o.Logger = val + } +} + +// Context provides a function to set the context option. +func Context(val context.Context) Option { + return func(o *Options) { + o.Context = val + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} diff --git a/extensions/storage-metadata/pkg/server/debug/server.go b/extensions/storage-metadata/pkg/server/debug/server.go new file mode 100644 index 0000000000..1ce0a803a8 --- /dev/null +++ b/extensions/storage-metadata/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/service/debug" + "github.com/owncloud/ocis/ocis-pkg/version" +) + +// Server initializes the debug service and server. +func Server(opts ...Option) (*http.Server, error) { + options := newOptions(opts...) + + return debug.NewService( + debug.Logger(options.Logger), + debug.Name(options.Config.Service.Name), + debug.Version(version.String), + debug.Address(options.Config.Debug.Addr), + debug.Token(options.Config.Debug.Token), + debug.Pprof(options.Config.Debug.Pprof), + debug.Zpages(options.Config.Debug.Zpages), + debug.Health(health(options.Config)), + debug.Ready(ready(options.Config)), + //debug.CorsAllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins), + //debug.CorsAllowedMethods(options.Config.HTTP.CORS.AllowedMethods), + //debug.CorsAllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders), + //debug.CorsAllowCredentials(options.Config.HTTP.CORS.AllowCredentials), + ), nil +} + +// health implements the health check. +func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} + +// ready implements the ready check. +func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} diff --git a/extensions/storage-metadata/pkg/tracing/tracing.go b/extensions/storage-metadata/pkg/tracing/tracing.go new file mode 100644 index 0000000000..540715bfb9 --- /dev/null +++ b/extensions/storage-metadata/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/storage-metadata/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/storage-publiclink/Makefile b/extensions/storage-publiclink/Makefile new file mode 100644 index 0000000000..0ca4c96683 --- /dev/null +++ b/extensions/storage-publiclink/Makefile @@ -0,0 +1,37 @@ +SHELL := bash +NAME := storage-publiclink + +include ../../.make/recursion.mk + +############ tooling ############ +ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI +include ../../.bingo/Variables.mk +endif + +############ go tooling ############ +include ../../.make/go.mk + +############ release ############ +include ../../.make/release.mk + +############ docs generate ############ +include ../../.make/docs.mk + +.PHONY: docs-generate +docs-generate: config-docs-generate + +############ generate ############ +include ../../.make/generate.mk + +.PHONY: ci-go-generate +ci-go-generate: # CI runs ci-node-generate automatically before this target + +.PHONY: ci-node-generate +ci-node-generate: + +############ licenses ############ +.PHONY: ci-node-check-licenses +ci-node-check-licenses: + +.PHONY: ci-node-save-licenses +ci-node-save-licenses: diff --git a/extensions/storage-publiclink/cmd/storage-publiclink/main.go b/extensions/storage-publiclink/cmd/storage-publiclink/main.go new file mode 100644 index 0000000000..55e9f26f95 --- /dev/null +++ b/extensions/storage-publiclink/cmd/storage-publiclink/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/command" + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/storage-publiclink/pkg/command/health.go b/extensions/storage-publiclink/pkg/command/health.go new file mode 100644 index 0000000000..f406daeb5f --- /dev/null +++ b/extensions/storage-publiclink/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/config" + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/config/parser" + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/storage-publiclink/pkg/command/root.go b/extensions/storage-publiclink/pkg/command/root.go new file mode 100644 index 0000000000..87cb0586c6 --- /dev/null +++ b/extensions/storage-publiclink/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the storage-publiclink command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "storage-publiclink", + Usage: "Provide publiclink storage for oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the accounts command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new accounts.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.StoragePublicLink.Commons = cfg.Commons + return SutureService{ + cfg: cfg.StoragePublicLink, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/storage-publiclink/pkg/command/server.go b/extensions/storage-publiclink/pkg/command/server.go new file mode 100644 index 0000000000..eb0a938e63 --- /dev/null +++ b/extensions/storage-publiclink/pkg/command/server.go @@ -0,0 +1,107 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/config" + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/config/parser" + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/logging" + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/server/debug" + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/sync" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.StoragePublicLinkConfigFromStruct(cfg) + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + if err := external.RegisterGRPCEndpoint( + ctx, + cfg.GRPC.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.GRPC.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/storage-publiclink/pkg/command/storagepubliclink.go b/extensions/storage-publiclink/pkg/command/storagepubliclink.go deleted file mode 100644 index 06fe7ada8a..0000000000 --- a/extensions/storage-publiclink/pkg/command/storagepubliclink.go +++ /dev/null @@ -1,169 +0,0 @@ -package command - -import ( - "context" - "flag" - "fmt" - "os" - "path" - - "github.com/cs3org/reva/v2/cmd/revad/runtime" - "github.com/gofrs/uuid" - "github.com/oklog/run" - "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/config" - "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/config/parser" - "github.com/owncloud/ocis/extensions/storage/pkg/server/debug" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" -) - -// StoragePublicLink is the entrypoint for the reva-storage-public-link command. -func StoragePublicLink(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "storage-public-link", - Usage: "start storage-public-link service", - Category: "extensions", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") - rcfg := storagePublicLinkConfigFromStruct(c, cfg) - - gr.Add(func() error { - runtime.RunWithOptions( - rcfg, - pidFile, - runtime.WithLogger(&logger.Logger), - ) - return nil - }, func(_ error) { - logger.Info(). - Str("server", c.Command.Name). - Msg("Shutting down server") - - cancel() - }) - - debugServer, err := debug.Server( - debug.Name(c.Command.Name+"-debug"), - debug.Addr(cfg.Debug.Addr), - debug.Logger(logger), - debug.Context(ctx), - debug.Pprof(cfg.Debug.Pprof), - debug.Zpages(cfg.Debug.Zpages), - debug.Token(cfg.Debug.Token), - ) - - if err != nil { - logger.Info().Err(err).Str("server", c.Command.Name+"-debug").Msg("Failed to initialize server") - return err - } - - gr.Add(debugServer.ListenAndServe, func(_ error) { - cancel() - }) - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() - }, - } -} - -// storagePublicLinkConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func storagePublicLinkConfigFromStruct(c *cli.Context, cfg *config.Config) map[string]interface{} { - rcfg := map[string]interface{}{ - "core": map[string]interface{}{ - "tracing_enabled": cfg.Tracing.Enabled, - "tracing_endpoint": cfg.Tracing.Endpoint, - "tracing_collector": cfg.Tracing.Collector, - "tracing_service_name": c.Command.Name, - }, - "shared": map[string]interface{}{ - "jwt_secret": cfg.TokenManager.JWTSecret, - "gatewaysvc": cfg.Reva.Address, - "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, - }, - "grpc": map[string]interface{}{ - "network": cfg.GRPC.Protocol, - "address": cfg.GRPC.Addr, - "interceptors": map[string]interface{}{ - "log": map[string]interface{}{}, - }, - "services": map[string]interface{}{ - "publicstorageprovider": map[string]interface{}{ - "mount_id": cfg.StorageProvider.MountID, - "gateway_addr": cfg.StorageProvider.GatewayEndpoint, - }, - "authprovider": map[string]interface{}{ - "auth_manager": "publicshares", - "auth_managers": map[string]interface{}{ - "publicshares": map[string]interface{}{ - "gateway_addr": cfg.AuthProvider.GatewayEndpoint, - }, - }, - }, - }, - }, - } - return rcfg -} - -// StoragePublicLinkSutureService allows for the storage-public-link command to be embedded and supervised by a suture supervisor tree. -type StoragePublicLinkSutureService struct { - cfg *config.Config -} - -// NewStoragePublicLinkSutureService creates a new storage.StoragePublicLinkSutureService -func NewStoragePublicLink(cfg *ociscfg.Config) suture.Service { - cfg.StoragePublicLink.Commons = cfg.Commons - return StoragePublicLinkSutureService{ - cfg: cfg.StoragePublicLink, - } -} - -func (s StoragePublicLinkSutureService) Serve(ctx context.Context) error { - // s.cfg.Reva.StoragePublicLink.Context = ctx - cmd := StoragePublicLink(s.cfg) - f := &flag.FlagSet{} - cmdFlags := cmd.Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if cmd.Before != nil { - if err := cmd.Before(cliCtx); err != nil { - return err - } - } - if err := cmd.Action(cliCtx); err != nil { - return err - } - - return nil -} diff --git a/extensions/storage-publiclink/pkg/command/version.go b/extensions/storage-publiclink/pkg/command/version.go new file mode 100644 index 0000000000..f16c6dca20 --- /dev/null +++ b/extensions/storage-publiclink/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/storage-publiclink/pkg/config/config.go b/extensions/storage-publiclink/pkg/config/config.go index 92d412a690..0420deb666 100644 --- a/extensions/storage-publiclink/pkg/config/config.go +++ b/extensions/storage-publiclink/pkg/config/config.go @@ -10,20 +10,20 @@ type Config struct { *shared.Commons `yaml:"-"` Service Service `yaml:"-"` Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` + Log *Log `yaml:"log"` Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` GRPC GRPCConfig `yaml:"grpc"` - Context context.Context `yaml:"context"` - TokenManager *TokenManager `yaml:"token_manager"` Reva *Reva `yaml:"reva"` - SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token"` - AuthProvider AuthProvider `yaml:"auth_provider"` - StorageProvider StorageProvider `yaml:"storage_provider"` + SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"STORAGE_PUBLICLINK_SKIP_USER_GROUPS_IN_TOKEN"` + + StorageProvider StorageProvider `yaml:"storage_provider"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` } type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;STORAGE_PUBLICLINK_TRACING_ENABLED" desc:"Activates tracing."` @@ -32,7 +32,7 @@ type Tracing struct { Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;STORAGE_PUBLICLINK_TRACING_COLLECTOR"` } -type Logging struct { +type Log struct { Level string `yaml:"level" env:"OCIS_LOG_LEVEL;STORAGE_PUBLICLINK_LOG_LEVEL" desc:"The log level."` Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;STORAGE_PUBLICLINK_LOG_PRETTY" desc:"Activates pretty log output."` Color bool `yaml:"color" env:"OCIS_LOG_COLOR;STORAGE_PUBLICLINK_LOG_COLOR" desc:"Activates colorized log output."` @@ -51,15 +51,11 @@ type Debug struct { } type GRPCConfig struct { - Addr string `yaml:"addr" env:"STORAGE_PUBLICLINK_GRPC_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"STORAGE_PUBLICLINK_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` -} - -type AuthProvider struct { - GatewayEndpoint string + Addr string `yaml:"addr" env:"STORAGE_PUBLICLINK_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"STORAGE_PUBLICLINK_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` } type StorageProvider struct { - MountID string - GatewayEndpoint string + MountID string `yaml:"mount_id" env:"STORAGE_PUBLICLINK_STORAGE_PROVIDER_MOUNT_ID"` } diff --git a/extensions/storage-publiclink/pkg/config/defaults/defaultconfig.go b/extensions/storage-publiclink/pkg/config/defaults/defaultconfig.go index 47b729c05a..d8595c935d 100644 --- a/extensions/storage-publiclink/pkg/config/defaults/defaultconfig.go +++ b/extensions/storage-publiclink/pkg/config/defaults/defaultconfig.go @@ -20,8 +20,9 @@ func DefaultConfig() *config.Config { Zpages: false, }, GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9178", - Protocol: "tcp", + Addr: "127.0.0.1:9178", + Namespace: "com.owncloud.api", + Protocol: "tcp", }, Service: config.Service{ Name: "storage-publiclink", @@ -29,27 +30,23 @@ func DefaultConfig() *config.Config { Reva: &config.Reva{ Address: "127.0.0.1:9142", }, - AuthProvider: config.AuthProvider{ - GatewayEndpoint: "127.0.0.1:9142", - }, StorageProvider: config.StorageProvider{ - MountID: "7993447f-687f-490d-875c-ac95e89a62a4", - GatewayEndpoint: "127.0.0.1:9142", + MountID: "7993447f-687f-490d-875c-ac95e89a62a4", }, } } func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/storage-publiclink/pkg/logging/logging.go b/extensions/storage-publiclink/pkg/logging/logging.go new file mode 100644 index 0000000000..a3dc14fdeb --- /dev/null +++ b/extensions/storage-publiclink/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/storage-publiclink/pkg/revaconfig/config.go b/extensions/storage-publiclink/pkg/revaconfig/config.go new file mode 100644 index 0000000000..9d1ac314f8 --- /dev/null +++ b/extensions/storage-publiclink/pkg/revaconfig/config.go @@ -0,0 +1,44 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/config" +) + +// StoragePublicLinkConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func StoragePublicLinkConfigFromStruct(cfg *config.Config) map[string]interface{} { + rcfg := map[string]interface{}{ + "core": map[string]interface{}{ + "tracing_enabled": cfg.Tracing.Enabled, + "tracing_endpoint": cfg.Tracing.Endpoint, + "tracing_collector": cfg.Tracing.Collector, + "tracing_service_name": cfg.Service.Name, + }, + "shared": map[string]interface{}{ + "jwt_secret": cfg.TokenManager.JWTSecret, + "gatewaysvc": cfg.Reva.Address, + "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, + }, + "grpc": map[string]interface{}{ + "network": cfg.GRPC.Protocol, + "address": cfg.GRPC.Addr, + "interceptors": map[string]interface{}{ + "log": map[string]interface{}{}, + }, + "services": map[string]interface{}{ + "publicstorageprovider": map[string]interface{}{ + "mount_id": cfg.StorageProvider.MountID, + "gateway_addr": cfg.Reva.Address, + }, + "authprovider": map[string]interface{}{ + "auth_manager": "publicshares", + "auth_managers": map[string]interface{}{ + "publicshares": map[string]interface{}{ + "gateway_addr": cfg.Reva.Address, + }, + }, + }, + }, + }, + } + return rcfg +} diff --git a/extensions/storage-publiclink/pkg/server/debug/option.go b/extensions/storage-publiclink/pkg/server/debug/option.go new file mode 100644 index 0000000000..d2cccd3437 --- /dev/null +++ b/extensions/storage-publiclink/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Logger log.Logger + Context context.Context + Config *config.Config +} + +// newOptions initializes the available default options. +func newOptions(opts ...Option) Options { + opt := Options{} + + for _, o := range opts { + o(&opt) + } + + return opt +} + +// Logger provides a function to set the logger option. +func Logger(val log.Logger) Option { + return func(o *Options) { + o.Logger = val + } +} + +// Context provides a function to set the context option. +func Context(val context.Context) Option { + return func(o *Options) { + o.Context = val + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} diff --git a/extensions/storage-publiclink/pkg/server/debug/server.go b/extensions/storage-publiclink/pkg/server/debug/server.go new file mode 100644 index 0000000000..fe2896014f --- /dev/null +++ b/extensions/storage-publiclink/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/service/debug" + "github.com/owncloud/ocis/ocis-pkg/version" +) + +// Server initializes the debug service and server. +func Server(opts ...Option) (*http.Server, error) { + options := newOptions(opts...) + + return debug.NewService( + debug.Logger(options.Logger), + debug.Name(options.Config.Service.Name), + debug.Version(version.String), + debug.Address(options.Config.Debug.Addr), + debug.Token(options.Config.Debug.Token), + debug.Pprof(options.Config.Debug.Pprof), + debug.Zpages(options.Config.Debug.Zpages), + debug.Health(health(options.Config)), + debug.Ready(ready(options.Config)), + //debug.CorsAllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins), + //debug.CorsAllowedMethods(options.Config.HTTP.CORS.AllowedMethods), + //debug.CorsAllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders), + //debug.CorsAllowCredentials(options.Config.HTTP.CORS.AllowCredentials), + ), nil +} + +// health implements the health check. +func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} + +// ready implements the ready check. +func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} diff --git a/extensions/storage-publiclink/pkg/tracing/tracing.go b/extensions/storage-publiclink/pkg/tracing/tracing.go new file mode 100644 index 0000000000..7c3c1bb60d --- /dev/null +++ b/extensions/storage-publiclink/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/storage-publiclink/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/storage-shares/Makefile b/extensions/storage-shares/Makefile new file mode 100644 index 0000000000..6fc69fef89 --- /dev/null +++ b/extensions/storage-shares/Makefile @@ -0,0 +1,37 @@ +SHELL := bash +NAME := storage-shares + +include ../../.make/recursion.mk + +############ tooling ############ +ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI +include ../../.bingo/Variables.mk +endif + +############ go tooling ############ +include ../../.make/go.mk + +############ release ############ +include ../../.make/release.mk + +############ docs generate ############ +include ../../.make/docs.mk + +.PHONY: docs-generate +docs-generate: config-docs-generate + +############ generate ############ +include ../../.make/generate.mk + +.PHONY: ci-go-generate +ci-go-generate: # CI runs ci-node-generate automatically before this target + +.PHONY: ci-node-generate +ci-node-generate: + +############ licenses ############ +.PHONY: ci-node-check-licenses +ci-node-check-licenses: + +.PHONY: ci-node-save-licenses +ci-node-save-licenses: diff --git a/extensions/storage-shares/cmd/storage-shares/main.go b/extensions/storage-shares/cmd/storage-shares/main.go new file mode 100644 index 0000000000..6fea0b0df3 --- /dev/null +++ b/extensions/storage-shares/cmd/storage-shares/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/storage-shares/pkg/command" + "github.com/owncloud/ocis/extensions/storage-shares/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/storage-shares/pkg/command/command.go b/extensions/storage-shares/pkg/command/command.go deleted file mode 100644 index 6964706456..0000000000 --- a/extensions/storage-shares/pkg/command/command.go +++ /dev/null @@ -1,165 +0,0 @@ -package command - -import ( - "context" - "flag" - "fmt" - "os" - "path" - - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - - "github.com/cs3org/reva/v2/cmd/revad/runtime" - "github.com/gofrs/uuid" - "github.com/oklog/run" - "github.com/owncloud/ocis/extensions/storage-shares/pkg/config" - "github.com/owncloud/ocis/extensions/storage-shares/pkg/config/parser" - "github.com/owncloud/ocis/extensions/storage/pkg/server/debug" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" -) - -// StorageShares is the entrypoint for the storage-shares command. -func StorageShares(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "storage-shares", - Usage: "start storage-shares service", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - uuid := uuid.Must(uuid.NewV4()) - pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+uuid.String()+".pid") - - rcfg := storageSharesConfigFromStruct(c, cfg) - - gr.Add(func() error { - runtime.RunWithOptions( - rcfg, - pidFile, - runtime.WithLogger(&logger.Logger), - ) - return nil - }, func(_ error) { - logger.Info(). - Str("server", c.Command.Name). - Msg("Shutting down server") - - cancel() - }) - - debugServer, err := debug.Server( - debug.Name(c.Command.Name+"-debug"), - debug.Addr(cfg.Debug.Addr), - debug.Logger(logger), - debug.Context(ctx), - debug.Pprof(cfg.Debug.Pprof), - debug.Zpages(cfg.Debug.Zpages), - debug.Token(cfg.Debug.Token), - ) - - if err != nil { - logger.Info().Err(err).Str("server", c.Command.Name+"-debug").Msg("Failed to initialize server") - return err - } - - gr.Add(debugServer.ListenAndServe, func(_ error) { - cancel() - }) - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() - }, - } -} - -// storageSharesConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func storageSharesConfigFromStruct(c *cli.Context, cfg *config.Config) map[string]interface{} { - rcfg := map[string]interface{}{ - "core": map[string]interface{}{ - "tracing_enabled": cfg.Tracing.Enabled, - "tracing_endpoint": cfg.Tracing.Endpoint, - "tracing_collector": cfg.Tracing.Collector, - "tracing_service_name": c.Command.Name, - }, - "shared": map[string]interface{}{ - "jwt_secret": cfg.TokenManager.JWTSecret, - "gatewaysvc": cfg.Reva.Address, - "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, - }, - "grpc": map[string]interface{}{ - "network": cfg.GRPC.Protocol, - "address": cfg.GRPC.Addr, - "services": map[string]interface{}{ - "sharesstorageprovider": map[string]interface{}{ - "usershareprovidersvc": cfg.SharesProviderEndpoint, - }, - }, - }, - } - if cfg.ReadOnly { - gcfg := rcfg["grpc"].(map[string]interface{}) - gcfg["interceptors"] = map[string]interface{}{ - "readonly": map[string]interface{}{}, - } - } - return rcfg -} - -// StorageSharesSutureService allows for the storage-shares command to be embedded and supervised by a suture supervisor tree. -type StorageSharesSutureService struct { - cfg *config.Config -} - -// NewStorageShares creates a new storage.StorageSharesSutureService -func NewStorageShares(cfg *ociscfg.Config) suture.Service { - cfg.StorageShares.Commons = cfg.Commons - return StorageSharesSutureService{ - cfg: cfg.StorageShares, - } -} - -func (s StorageSharesSutureService) Serve(ctx context.Context) error { - // s.cfg.Reva.StorageShares.Context = ctx - cmd := StorageShares(s.cfg) - f := &flag.FlagSet{} - cmdFlags := cmd.Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if cmd.Before != nil { - if err := cmd.Before(cliCtx); err != nil { - return err - } - } - if err := cmd.Action(cliCtx); err != nil { - return err - } - - return nil -} diff --git a/extensions/storage-shares/pkg/command/health.go b/extensions/storage-shares/pkg/command/health.go new file mode 100644 index 0000000000..8b5505e738 --- /dev/null +++ b/extensions/storage-shares/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/storage-shares/pkg/config" + "github.com/owncloud/ocis/extensions/storage-shares/pkg/config/parser" + "github.com/owncloud/ocis/extensions/storage-shares/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/storage-shares/pkg/command/root.go b/extensions/storage-shares/pkg/command/root.go new file mode 100644 index 0000000000..bafe1a336b --- /dev/null +++ b/extensions/storage-shares/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/storage-shares/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the storage-shares command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "storage-shares", + Usage: "Provide a virtual storage for shares in oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the accounts command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new storage-shares.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.StorageShares.Commons = cfg.Commons + return SutureService{ + cfg: cfg.StorageShares, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/storage-shares/pkg/command/server.go b/extensions/storage-shares/pkg/command/server.go new file mode 100644 index 0000000000..16560c6e94 --- /dev/null +++ b/extensions/storage-shares/pkg/command/server.go @@ -0,0 +1,107 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/storage-shares/pkg/config" + "github.com/owncloud/ocis/extensions/storage-shares/pkg/config/parser" + "github.com/owncloud/ocis/extensions/storage-shares/pkg/logging" + "github.com/owncloud/ocis/extensions/storage-shares/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/storage-shares/pkg/server/debug" + "github.com/owncloud/ocis/extensions/storage-shares/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/sync" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.StorageSharesConfigFromStruct(cfg) + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + if err := external.RegisterGRPCEndpoint( + ctx, + cfg.GRPC.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.GRPC.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/storage-shares/pkg/command/version.go b/extensions/storage-shares/pkg/command/version.go new file mode 100644 index 0000000000..3687778691 --- /dev/null +++ b/extensions/storage-shares/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/storage-shares/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/storage-shares/pkg/config/config.go b/extensions/storage-shares/pkg/config/config.go index 4677edef2e..d555ed2d3f 100644 --- a/extensions/storage-shares/pkg/config/config.go +++ b/extensions/storage-shares/pkg/config/config.go @@ -10,20 +10,21 @@ type Config struct { *shared.Commons `yaml:"-"` Service Service `yaml:"-"` Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` + Log *Log `yaml:"log"` Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` GRPC GRPCConfig `yaml:"grpc"` - HTTP HTTPConfig `yaml:"http"` TokenManager *TokenManager `yaml:"token_manager"` Reva *Reva `yaml:"reva"` - Context context.Context `yaml:"context"` - SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token"` - ReadOnly bool `yaml:"readonly"` - SharesProviderEndpoint string `yaml:"shares_provider_endpoint"` + SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"STORAGE_SHARES_SKIP_USER_GROUPS_IN_TOKEN"` + + ReadOnly bool `yaml:"readonly" env:"STORAGE_SHARES_READ_ONLY"` + SharesProviderEndpoint string `yaml:"user_share_provider_endpoint" env:"STORAGE_SHARES_USER_SHARE_PROVIDER_ENDPOINT"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` } type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;STORAGE_SHARES_TRACING_ENABLED" desc:"Activates tracing."` @@ -32,7 +33,7 @@ type Tracing struct { Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;STORAGE_SHARES_TRACING_COLLECTOR"` } -type Logging struct { +type Log struct { Level string `yaml:"level" env:"OCIS_LOG_LEVEL;STORAGE_SHARES_LOG_LEVEL" desc:"The log level."` Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;STORAGE_SHARES_LOG_PRETTY" desc:"Activates pretty log output."` Color bool `yaml:"color" env:"OCIS_LOG_COLOR;STORAGE_SHARES_LOG_COLOR" desc:"Activates colorized log output."` @@ -51,11 +52,7 @@ type Debug struct { } type GRPCConfig struct { - Addr string `yaml:"addr" env:"STORAGE_SHARES_GRPC_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"STORAGE_SHARES_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` -} - -type HTTPConfig struct { - Addr string `yaml:"addr" env:"STORAGE_SHARES_HTTP_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"STORAGE_SHARES_HTTP_PROTOCOL" desc:"The transport protocol of the grpc service."` + Addr string `yaml:"addr" env:"STORAGE_SHARES_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"STORAGE_SHARES_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` } diff --git a/extensions/storage-shares/pkg/config/defaults/defaultconfig.go b/extensions/storage-shares/pkg/config/defaults/defaultconfig.go index 40aba54cfd..5b1ad7faa2 100644 --- a/extensions/storage-shares/pkg/config/defaults/defaultconfig.go +++ b/extensions/storage-shares/pkg/config/defaults/defaultconfig.go @@ -20,12 +20,9 @@ func DefaultConfig() *config.Config { Zpages: false, }, GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9154", - Protocol: "tcp", - }, - HTTP: config.HTTPConfig{ - Addr: "127.0.0.1:9155", - Protocol: "tcp", + Addr: "127.0.0.1:9154", + Namespace: "com.owncloud.api", + Protocol: "tcp", }, Service: config.Service{ Name: "storage-shares", @@ -40,15 +37,15 @@ func DefaultConfig() *config.Config { func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/storage-shares/pkg/logging/logging.go b/extensions/storage-shares/pkg/logging/logging.go new file mode 100644 index 0000000000..7769e10fe7 --- /dev/null +++ b/extensions/storage-shares/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/storage-shares/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/storage-shares/pkg/revaconfig/config.go b/extensions/storage-shares/pkg/revaconfig/config.go new file mode 100644 index 0000000000..be281c0553 --- /dev/null +++ b/extensions/storage-shares/pkg/revaconfig/config.go @@ -0,0 +1,38 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/extensions/storage-shares/pkg/config" +) + +// StorageSharesConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func StorageSharesConfigFromStruct(cfg *config.Config) map[string]interface{} { + rcfg := map[string]interface{}{ + "core": map[string]interface{}{ + "tracing_enabled": cfg.Tracing.Enabled, + "tracing_endpoint": cfg.Tracing.Endpoint, + "tracing_collector": cfg.Tracing.Collector, + "tracing_service_name": cfg.Service.Name, + }, + "shared": map[string]interface{}{ + "jwt_secret": cfg.TokenManager.JWTSecret, + "gatewaysvc": cfg.Reva.Address, + "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, + }, + "grpc": map[string]interface{}{ + "network": cfg.GRPC.Protocol, + "address": cfg.GRPC.Addr, + "services": map[string]interface{}{ + "sharesstorageprovider": map[string]interface{}{ + "usershareprovidersvc": cfg.SharesProviderEndpoint, + }, + }, + }, + } + if cfg.ReadOnly { + gcfg := rcfg["grpc"].(map[string]interface{}) + gcfg["interceptors"] = map[string]interface{}{ + "readonly": map[string]interface{}{}, + } + } + return rcfg +} diff --git a/extensions/storage/pkg/server/debug/option.go b/extensions/storage-shares/pkg/server/debug/option.go similarity index 53% rename from extensions/storage/pkg/server/debug/option.go rename to extensions/storage-shares/pkg/server/debug/option.go index 8e84764913..a671b3ec76 100644 --- a/extensions/storage/pkg/server/debug/option.go +++ b/extensions/storage-shares/pkg/server/debug/option.go @@ -3,7 +3,7 @@ package debug import ( "context" - "github.com/owncloud/ocis/extensions/storage/pkg/config" + "github.com/owncloud/ocis/extensions/storage-shares/pkg/config" "github.com/owncloud/ocis/ocis-pkg/log" ) @@ -12,14 +12,9 @@ type Option func(o *Options) // Options defines the available options for this package. type Options struct { - Name string - Addr string Logger log.Logger Context context.Context Config *config.Config - Pprof bool - Zpages bool - Token string } // newOptions initializes the available default options. @@ -33,20 +28,6 @@ func newOptions(opts ...Option) Options { return opt } -// Name provides a function to set the name option. -func Name(val string) Option { - return func(o *Options) { - o.Name = val - } -} - -// Addr provides a function to set the addr option. -func Addr(val string) Option { - return func(o *Options) { - o.Addr = val - } -} - // Logger provides a function to set the logger option. func Logger(val log.Logger) Option { return func(o *Options) { @@ -67,24 +48,3 @@ func Config(val *config.Config) Option { o.Config = val } } - -// Pprof provides a function to set the pprof option. -func Pprof(val bool) Option { - return func(o *Options) { - o.Pprof = val - } -} - -// Zpages provides a function to set the zpages option. -func Zpages(val bool) Option { - return func(o *Options) { - o.Zpages = val - } -} - -// Token provides a function to set the token option. -func Token(val string) Option { - return func(o *Options) { - o.Token = val - } -} diff --git a/extensions/storage-shares/pkg/server/debug/server.go b/extensions/storage-shares/pkg/server/debug/server.go new file mode 100644 index 0000000000..666c3b3b14 --- /dev/null +++ b/extensions/storage-shares/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/extensions/storage-shares/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/service/debug" + "github.com/owncloud/ocis/ocis-pkg/version" +) + +// Server initializes the debug service and server. +func Server(opts ...Option) (*http.Server, error) { + options := newOptions(opts...) + + return debug.NewService( + debug.Logger(options.Logger), + debug.Name(options.Config.Service.Name), + debug.Version(version.String), + debug.Address(options.Config.Debug.Addr), + debug.Token(options.Config.Debug.Token), + debug.Pprof(options.Config.Debug.Pprof), + debug.Zpages(options.Config.Debug.Zpages), + debug.Health(health(options.Config)), + debug.Ready(ready(options.Config)), + //debug.CorsAllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins), + //debug.CorsAllowedMethods(options.Config.HTTP.CORS.AllowedMethods), + //debug.CorsAllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders), + //debug.CorsAllowCredentials(options.Config.HTTP.CORS.AllowCredentials), + ), nil +} + +// health implements the health check. +func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} + +// ready implements the ready check. +func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} diff --git a/extensions/storage-shares/pkg/tracing/tracing.go b/extensions/storage-shares/pkg/tracing/tracing.go new file mode 100644 index 0000000000..3ba5dbbc40 --- /dev/null +++ b/extensions/storage-shares/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/storage-shares/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/storage-users/Makefile b/extensions/storage-users/Makefile new file mode 100644 index 0000000000..f1e9ac0ba8 --- /dev/null +++ b/extensions/storage-users/Makefile @@ -0,0 +1,37 @@ +SHELL := bash +NAME := storage-users + +include ../../.make/recursion.mk + +############ tooling ############ +ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI +include ../../.bingo/Variables.mk +endif + +############ go tooling ############ +include ../../.make/go.mk + +############ release ############ +include ../../.make/release.mk + +############ docs generate ############ +include ../../.make/docs.mk + +.PHONY: docs-generate +docs-generate: config-docs-generate + +############ generate ############ +include ../../.make/generate.mk + +.PHONY: ci-go-generate +ci-go-generate: # CI runs ci-node-generate automatically before this target + +.PHONY: ci-node-generate +ci-node-generate: + +############ licenses ############ +.PHONY: ci-node-check-licenses +ci-node-check-licenses: + +.PHONY: ci-node-save-licenses +ci-node-save-licenses: diff --git a/extensions/storage-users/cmd/storage-users/main.go b/extensions/storage-users/cmd/storage-users/main.go new file mode 100644 index 0000000000..4e99f59377 --- /dev/null +++ b/extensions/storage-users/cmd/storage-users/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/storage-users/pkg/command" + "github.com/owncloud/ocis/extensions/storage-users/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/storage-users/pkg/command/command.go b/extensions/storage-users/pkg/command/command.go deleted file mode 100644 index 5e48a2db03..0000000000 --- a/extensions/storage-users/pkg/command/command.go +++ /dev/null @@ -1,193 +0,0 @@ -package command - -import ( - "context" - "flag" - "fmt" - "os" - "path" - - "github.com/cs3org/reva/v2/cmd/revad/runtime" - "github.com/gofrs/uuid" - "github.com/oklog/run" - "github.com/owncloud/ocis/extensions/storage-users/pkg/config" - "github.com/owncloud/ocis/extensions/storage-users/pkg/config/parser" - "github.com/owncloud/ocis/extensions/storage/pkg/server/debug" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" -) - -// StorageUsers is the entrypoint for the storage-users command. -func StorageUsers(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "storage-users", - Usage: "start storage-users service", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - uuid := uuid.Must(uuid.NewV4()) - pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+uuid.String()+".pid") - - rcfg := storageUsersConfigFromStruct(c, cfg) - - gr.Add(func() error { - runtime.RunWithOptions( - rcfg, - pidFile, - runtime.WithLogger(&logger.Logger), - ) - return nil - }, func(_ error) { - logger.Info(). - Str("server", c.Command.Name). - Msg("Shutting down server") - - cancel() - }) - - debugServer, err := debug.Server( - debug.Name(c.Command.Name+"-debug"), - debug.Addr(cfg.Debug.Addr), - debug.Logger(logger), - debug.Context(ctx), - debug.Pprof(cfg.Debug.Pprof), - debug.Zpages(cfg.Debug.Zpages), - debug.Token(cfg.Debug.Token), - ) - - if err != nil { - logger.Info().Err(err).Str("server", c.Command.Name+"-debug").Msg("Failed to initialize server") - return err - } - - gr.Add(debugServer.ListenAndServe, func(_ error) { - cancel() - }) - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() - }, - } -} - -// storageUsersConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func storageUsersConfigFromStruct(c *cli.Context, cfg *config.Config) map[string]interface{} { - rcfg := map[string]interface{}{ - "core": map[string]interface{}{ - "tracing_enabled": cfg.Tracing.Enabled, - "tracing_endpoint": cfg.Tracing.Endpoint, - "tracing_collector": cfg.Tracing.Collector, - "tracing_service_name": c.Command.Name, - }, - "shared": map[string]interface{}{ - "jwt_secret": cfg.TokenManager.JWTSecret, - "gatewaysvc": cfg.Reva.Address, - "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, - }, - "grpc": map[string]interface{}{ - "network": cfg.GRPC.Protocol, - "address": cfg.GRPC.Addr, - // TODO build services dynamically - "services": map[string]interface{}{ - "storageprovider": map[string]interface{}{ - "driver": cfg.Driver, - "drivers": config.UserDrivers(cfg), - "mount_id": cfg.MountID, - "expose_data_server": cfg.ExposeDataServer, - "data_server_url": cfg.DataServerURL, - "tmp_folder": cfg.TempFolder, - }, - }, - "interceptors": map[string]interface{}{ - "eventsmiddleware": map[string]interface{}{ - "group": "sharing", - "type": "nats", - "address": cfg.Events.Addr, - "clusterID": cfg.Events.ClusterID, - }, - }, - }, - "http": map[string]interface{}{ - "network": cfg.HTTP.Protocol, - "address": cfg.HTTP.Addr, - // TODO build services dynamically - "services": map[string]interface{}{ - "dataprovider": map[string]interface{}{ - "prefix": cfg.HTTP.Prefix, - "driver": cfg.Driver, - "drivers": config.UserDrivers(cfg), - "timeout": 86400, - "insecure": cfg.DataProviderInsecure, - "disable_tus": false, - }, - }, - }, - } - if cfg.ReadOnly { - gcfg := rcfg["grpc"].(map[string]interface{}) - gcfg["interceptors"] = map[string]interface{}{ - "readonly": map[string]interface{}{}, - } - } - return rcfg -} - -// StorageUsersSutureService allows for the storage-home command to be embedded and supervised by a suture supervisor tree. -type StorageUsersSutureService struct { - cfg *config.Config -} - -// NewStorageUsersSutureService creates a new storage.StorageUsersSutureService -func NewStorageUsers(cfg *ociscfg.Config) suture.Service { - cfg.StorageUsers.Commons = cfg.Commons - return StorageUsersSutureService{ - cfg: cfg.StorageUsers, - } -} - -func (s StorageUsersSutureService) Serve(ctx context.Context) error { - // s.cfg.Reva.StorageUsers.Context = ctx - cmd := StorageUsers(s.cfg) - f := &flag.FlagSet{} - cmdFlags := cmd.Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if cmd.Before != nil { - if err := cmd.Before(cliCtx); err != nil { - return err - } - } - if err := cmd.Action(cliCtx); err != nil { - return err - } - - return nil -} diff --git a/extensions/storage-users/pkg/command/health.go b/extensions/storage-users/pkg/command/health.go new file mode 100644 index 0000000000..baa4d1a8de --- /dev/null +++ b/extensions/storage-users/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/extensions/storage-users/pkg/config" + "github.com/owncloud/ocis/extensions/storage-users/pkg/config/parser" + "github.com/owncloud/ocis/extensions/storage-users/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/extensions/storage-users/pkg/command/root.go b/extensions/storage-users/pkg/command/root.go new file mode 100644 index 0000000000..49304eebe2 --- /dev/null +++ b/extensions/storage-users/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/storage-users/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-storage-users command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "storage-users", + Usage: "Provide storage for users and projects in oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the accounts command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new storage-users.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.StorageUsers.Commons = cfg.Commons + return SutureService{ + cfg: cfg.StorageUsers, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/storage-users/pkg/command/server.go b/extensions/storage-users/pkg/command/server.go new file mode 100644 index 0000000000..ae83b2435f --- /dev/null +++ b/extensions/storage-users/pkg/command/server.go @@ -0,0 +1,107 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/storage-users/pkg/config" + "github.com/owncloud/ocis/extensions/storage-users/pkg/config/parser" + "github.com/owncloud/ocis/extensions/storage-users/pkg/logging" + "github.com/owncloud/ocis/extensions/storage-users/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/storage-users/pkg/server/debug" + "github.com/owncloud/ocis/extensions/storage-users/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/sync" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.StorageUsersConfigFromStruct(cfg) + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + if err := external.RegisterGRPCEndpoint( + ctx, + cfg.GRPC.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.GRPC.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/storage-users/pkg/command/version.go b/extensions/storage-users/pkg/command/version.go new file mode 100644 index 0000000000..597dcc14df --- /dev/null +++ b/extensions/storage-users/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/storage-users/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/storage-users/pkg/config/config.go b/extensions/storage-users/pkg/config/config.go index 7cb2888148..59f95849cc 100644 --- a/extensions/storage-users/pkg/config/config.go +++ b/extensions/storage-users/pkg/config/config.go @@ -10,9 +10,8 @@ type Config struct { *shared.Commons `yaml:"-"` Service Service `yaml:"-"` Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` + Log *Log `yaml:"log"` Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` GRPC GRPCConfig `yaml:"grpc"` HTTP HTTPConfig `yaml:"http"` @@ -20,18 +19,20 @@ type Config struct { TokenManager *TokenManager `yaml:"token_manager"` Reva *Reva `yaml:"reva"` - Context context.Context `yaml:"context"` + SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"STORAGE_USERS_SKIP_USER_GROUPS_IN_TOKEN"` - SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token"` - Driver string `yaml:"driver" env:"STORAGE_USERS_DRIVER" desc:"The storage driver which should be used by the service"` - Drivers Drivers `yaml:"drivers"` - DataServerURL string `yaml:"data_server_url"` - TempFolder string `yaml:"temp_folder"` - DataProviderInsecure bool `yaml:"data_provider_insecure" env:"OCIS_INSECURE;STORAGE_USERS_DATAPROVIDER_INSECURE"` - Events Events `yaml:"events"` - MountID string `yaml:"mount_id"` - ExposeDataServer bool `yaml:"expose_data_server"` - ReadOnly bool `yaml:"readonly"` + Driver string `yaml:"driver" env:"STORAGE_USERS_DRIVER" desc:"The storage driver which should be used by the service"` + Drivers Drivers `yaml:"drivers"` + DataServerURL string `yaml:"data_server_url" env:"STORAGE_USERS_DATA_SERVER_URL"` + TempFolder string `yaml:"temp_folder" env:"STORAGE_USERS_TEMP_FOLDER"` + DataProviderInsecure bool `yaml:"data_provider_insecure" env:"OCIS_INSECURE;STORAGE_USERS_DATAPROVIDER_INSECURE"` + Events Events `yaml:"events"` + MountID string `yaml:"mount_id" env:"STORAGE_USERS_MOUNT_ID"` + ExposeDataServer bool `yaml:"expose_data_server" env:"STORAGE_USERS_EXPOSE_DATA_SERVER"` + ReadOnly bool `yaml:"readonly" env:"STORAGE_USERS_READ_ONLY"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` } type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;STORAGE_USERS_TRACING_ENABLED" desc:"Activates tracing."` @@ -40,7 +41,7 @@ type Tracing struct { Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;STORAGE_USERS_TRACING_COLLECTOR"` } -type Logging struct { +type Log struct { Level string `yaml:"level" env:"OCIS_LOG_LEVEL;STORAGE_USERS_LOG_LEVEL" desc:"The log level."` Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;STORAGE_USERS_LOG_PRETTY" desc:"Activates pretty log output."` Color bool `yaml:"color" env:"OCIS_LOG_COLOR;STORAGE_USERS_LOG_COLOR" desc:"Activates colorized log output."` @@ -59,25 +60,91 @@ type Debug struct { } type GRPCConfig struct { - Addr string `yaml:"addr" env:"STORAGE_USERS_GRPC_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"STORAGE_USERS_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` + Addr string `yaml:"addr" env:"STORAGE_USERS_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"STORAGE_USERS_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` } type HTTPConfig struct { - Addr string `yaml:"addr" env:"STORAGE_USERS_GRPC_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"STORAGE_USERS_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` - Prefix string + Addr string `yaml:"addr" env:"STORAGE_USERS_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"STORAGE_USERS_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` + Prefix string } type Drivers struct { - EOS EOSDriver - Local LocalDriver - OCIS OCISDriver - S3 S3Driver - S3NG S3NGDriver - OwnCloudSQL OwnCloudSQLDriver + OCIS OCISDriver `yaml:"ocis"` + S3NG S3NGDriver `yaml:"s3ng"` + OwnCloudSQL OwnCloudSQLDriver `yaml:"owncloudsql"` + + S3 S3Driver `yaml:",omitempty"` // not supported by the oCIS product, therefore not part of docs + EOS EOSDriver `yaml:",omitempty"` // not supported by the oCIS product, therefore not part of docs + Local LocalDriver `yaml:",omitempty"` // not supported by the oCIS product, therefore not part of docs } +type OCISDriver struct { + // Root is the absolute path to the location of the data + Root string `yaml:"root" env:"STORAGE_USERS_OCIS_ROOT"` + UserLayout string `yaml:"user_layout" env:"STORAGE_USERS_OCIS_USER_LAYOUT"` + PermissionsEndpoint string `yaml:"permissions_endpoint" env:"STORAGE_USERS_OCIS_PERMISSIONS_ENDPOINT"` + // PersonalSpaceAliasTemplate contains the template used to construct + // the personal space alias, eg: `"{{.SpaceType}}/{{.User.Username | lower}}"` + PersonalSpaceAliasTemplate string `yaml:"personalspacealias_template" env:"STORAGE_USERS_OCIS_PERSONAL_SPACE_ALIAS_TEMPLATE"` + // GeneralSpaceAliasTemplate contains the template used to construct + // the general space alias, eg: `{{.SpaceType}}/{{.SpaceName | replace " " "-" | lower}}` + GeneralSpaceAliasTemplate string `yaml:"generalspacealias_template" env:"STORAGE_USERS_OCIS_GENERAL_SPACE_ALIAS_TEMPLATE"` + //ShareFolder defines the name of the folder jailing all shares + ShareFolder string `yaml:"share_folder" env:"STORAGE_USERS_OCIS_SHARE_FOLDER"` +} + +type S3NGDriver struct { + // Root is the absolute path to the location of the data + Root string `yaml:"root" env:"STORAGE_USERS_S3NG_ROOT"` + UserLayout string `yaml:"user_layout" env:"STORAGE_USERS_S3NG_USER_LAYOUT"` + PermissionsEndpoint string `yaml:"permissions_endpoint" env:"STORAGE_USERS_PERMISSION_ENDPOINT;STORAGE_USERS_S3NG_USERS_PROVIDER_ENDPOINT"` + Region string `yaml:"region" env:"STORAGE_USERS_S3NG_REGION"` + AccessKey string `yaml:"access_key" env:"STORAGE_USERS_S3NG_ACCESS_KEY"` + SecretKey string `yaml:"secret_key" env:"STORAGE_USERS_S3NG_SECRET_KEY"` + Endpoint string `yaml:"endpoint" env:"STORAGE_USERS_S3NG_ENDPOINT"` + Bucket string `yaml:"bucket" env:"STORAGE_USERS_S3NG_BUCKET"` + // PersonalSpaceAliasTemplate contains the template used to construct + // the personal space alias, eg: `"{{.SpaceType}}/{{.User.Username | lower}}"` + PersonalSpaceAliasTemplate string `yaml:"personalspacealias_template" env:"STORAGE_USERS_S3NG_PERSONAL_SPACE_ALIAS_TEMPLATE"` + // GeneralSpaceAliasTemplate contains the template used to construct + // the general space alias, eg: `{{.SpaceType}}/{{.SpaceName | replace " " "-" | lower}}` + GeneralSpaceAliasTemplate string `yaml:"generalspacealias_template" env:"STORAGE_USERS_S3NG_GENERAL_SPACE_ALIAS_TEMPLATE"` + //ShareFolder defines the name of the folder jailing all shares + ShareFolder string `yaml:"share_folder" env:"STORAGE_USERS_S3NG_SHARE_FOLDER"` +} + +type OwnCloudSQLDriver struct { + // Root is the absolute path to the location of the data + Root string `yaml:"root" env:"STORAGE_USERS_OWNCLOUDSQL_DATADIR"` + //ShareFolder defines the name of the folder jailing all shares + ShareFolder string `yaml:"share_folder" env:"STORAGE_USERS_OWNCLOUDSQL_SHARE_FOLDER"` + UserLayout string `env:"STORAGE_USERS_OWNCLOUDSQL_LAYOUT"` + UploadInfoDir string `yaml:"upload_info_dir" env:"STORAGE_USERS_UPLOADINFO_DIR"` + DBUsername string `yaml:"db_username" env:"STORAGE_USERS_OWNCLOUDSQL_DB_USERNAME"` + DBPassword string `yaml:"db_password" env:"STORAGE_USERS_OWNCLOUDSQL_DB_PASSWORD"` + DBHost string `yaml:"db_host" env:"STORAGE_USERS_OWNCLOUDSQL_DB_HOST"` + DBPort int `yaml:"db_port" env:"STORAGE_USERS_OWNCLOUDSQL_DB_PORT"` + DBName string `yaml:"db_name" env:"STORAGE_USERS_OWNCLOUDSQL_DB_NAME"` + UsersProviderEndpoint string `yaml:"users_provider_endpoint" env:"STORAGE_USERS_PERMISSION_ENDPOINT;STORAGE_USERS_OWNCLOUDSQL_USERS_PROVIDER_ENDPOINT"` +} + +type Events struct { + Addr string `yaml:"endpoint" env:"STORAGE_USERS_EVENTS_ENDPOINT" desc:"the address of the streaming service"` + ClusterID string `yaml:"cluster" env:"STORAGE_USERS_EVENTS_CLUSTER" desc:"the clusterID of the streaming service. Mandatory when using nats"` +} +type S3Driver struct { + // Root is the absolute path to the location of the data + Root string `yaml:"root"` + Region string `yaml:"region"` + AccessKey string `yaml:"access_key"` + SecretKey string `yaml:"secret_key"` + Endpoint string `yaml:"endpoint"` + Bucket string `yaml:"bucket"` +} type EOSDriver struct { // Root is the absolute path to the location of the data Root string `yaml:"root"` @@ -126,73 +193,8 @@ type EOSDriver struct { type LocalDriver struct { // Root is the absolute path to the location of the data - Root string `yaml:"root" env:"STORAGE_USERS_LOCAL_ROOT"` + Root string `yaml:"root"` //ShareFolder defines the name of the folder jailing all shares ShareFolder string `yaml:"share_folder"` - UserLayout string -} - -type OCISDriver struct { - // Root is the absolute path to the location of the data - Root string `yaml:"root" env:"STORAGE_USERS_OCIS_ROOT"` - UserLayout string - PermissionsEndpoint string - // PersonalSpaceAliasTemplate contains the template used to construct - // the personal space alias, eg: `"{{.SpaceType}}/{{.User.Username | lower}}"` - PersonalSpaceAliasTemplate string `yaml:"personalspacealias_template"` - // GeneralSpaceAliasTemplate contains the template used to construct - // the general space alias, eg: `{{.SpaceType}}/{{.SpaceName | replace " " "-" | lower}}` - GeneralSpaceAliasTemplate string `yaml:"generalspacealias_template"` - //ShareFolder defines the name of the folder jailing all shares - ShareFolder string `yaml:"share_folder"` -} - -type S3Driver struct { - // Root is the absolute path to the location of the data - Root string `yaml:"root"` - Region string `yaml:"region"` - AccessKey string `yaml:"access_key"` - SecretKey string `yaml:"secret_key"` - Endpoint string `yaml:"endpoint"` - Bucket string `yaml:"bucket"` -} - -type S3NGDriver struct { - // Root is the absolute path to the location of the data - Root string `yaml:"root"` - UserLayout string - PermissionsEndpoint string - Region string `yaml:"region"` - AccessKey string `yaml:"access_key"` - SecretKey string `yaml:"secret_key"` - Endpoint string `yaml:"endpoint"` - Bucket string `yaml:"bucket"` - // PersonalSpaceAliasTemplate contains the template used to construct - // the personal space alias, eg: `"{{.SpaceType}}/{{.User.Username | lower}}"` - PersonalSpaceAliasTemplate string `yaml:"personalspacealias_template"` - // GeneralSpaceAliasTemplate contains the template used to construct - // the general space alias, eg: `{{.SpaceType}}/{{.SpaceName | replace " " "-" | lower}}` - GeneralSpaceAliasTemplate string `yaml:"generalspacealias_template"` - //ShareFolder defines the name of the folder jailing all shares - ShareFolder string `yaml:"share_folder"` -} - -type OwnCloudSQLDriver struct { - // Root is the absolute path to the location of the data - Root string `yaml:"root" env:"STORAGE_USERS_DRIVER_OWNCLOUDSQL_DATADIR"` - //ShareFolder defines the name of the folder jailing all shares - ShareFolder string `yaml:"share_folder" env:"STORAGE_USERS_DRIVER_OWNCLOUDSQL_SHARE_FOLDER"` - UserLayout string `env:"STORAGE_USERS_DRIVER_OWNCLOUDSQL_LAYOUT"` - UploadInfoDir string `yaml:"upload_info_dir" env:"STORAGE_USERS_DRIVER_OWNCLOUDSQL_UPLOADINFO_DIR"` - DBUsername string `yaml:"db_username" env:"STORAGE_USERS_DRIVER_OWNCLOUDSQL_DBUSERNAME"` - DBPassword string `yaml:"db_password" env:"STORAGE_USERS_DRIVER_OWNCLOUDSQL_DBPASSWORD"` - DBHost string `yaml:"db_host" env:"STORAGE_USERS_DRIVER_OWNCLOUDSQL_DBHOST"` - DBPort int `yaml:"db_port" env:"STORAGE_USERS_DRIVER_OWNCLOUDSQL_DBPORT"` - DBName string `yaml:"db_name" env:"STORAGE_USERS_DRIVER_OWNCLOUDSQL_DBNAME"` - UsersProviderEndpoint string -} - -type Events struct { - Addr string - ClusterID string + UserLayout string `yaml:"user_layout"` } diff --git a/extensions/storage-users/pkg/config/defaults/defaultconfig.go b/extensions/storage-users/pkg/config/defaults/defaultconfig.go index b29e9daa98..fb36a5073d 100644 --- a/extensions/storage-users/pkg/config/defaults/defaultconfig.go +++ b/extensions/storage-users/pkg/config/defaults/defaultconfig.go @@ -1,7 +1,6 @@ package defaults import ( - "os" "path/filepath" "github.com/owncloud/ocis/extensions/storage-users/pkg/config" @@ -24,13 +23,15 @@ func DefaultConfig() *config.Config { Zpages: false, }, GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9157", - Protocol: "tcp", + Addr: "127.0.0.1:9157", + Namespace: "com.owncloud.api", + Protocol: "tcp", }, HTTP: config.HTTPConfig{ - Addr: "127.0.0.1:9158", - Protocol: "tcp", - Prefix: "data", + Addr: "127.0.0.1:9158", + Namespace: "com.owncloud.web", + Protocol: "tcp", + Prefix: "data", }, Service: config.Service{ Name: "storage-users", @@ -43,25 +44,6 @@ func DefaultConfig() *config.Config { MountID: "1284d238-aa92-42ce-bdc4-0b0000009157", Driver: "ocis", Drivers: config.Drivers{ - EOS: config.EOSDriver{ - Root: "/eos/dockertest/reva", - ShareFolder: "/Shares", - UserLayout: "{{substr 0 1 .Username}}/{{.Username}}", - ShadowNamespace: "", - UploadsNamespace: "", - EosBinary: "/usr/bin/eos", - XrdcopyBinary: "/usr/bin/xrdcopy", - MasterURL: "root://eos-mgm1.eoscluster.cern.ch:1094", - GRPCURI: "", - SlaveURL: "root://eos-mgm1.eoscluster.cern.ch:1094", - CacheDirectory: os.TempDir(), - GatewaySVC: "127.0.0.1:9142", - }, - Local: config.LocalDriver{ - Root: filepath.Join(defaults.BaseDataPath(), "storage", "local", "users"), - ShareFolder: "/Shares", - UserLayout: "{{.Username}}", - }, OwnCloudSQL: config.OwnCloudSQLDriver{ Root: filepath.Join(defaults.BaseDataPath(), "storage", "owncloud"), ShareFolder: "/Shares", @@ -73,9 +55,6 @@ func DefaultConfig() *config.Config { DBPort: 3306, DBName: "owncloud", }, - S3: config.S3Driver{ - Region: "default", - }, S3NG: config.S3NGDriver{ Root: filepath.Join(defaults.BaseDataPath(), "storage", "users"), ShareFolder: "/Shares", @@ -103,15 +82,15 @@ func DefaultConfig() *config.Config { func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/storage-users/pkg/logging/logging.go b/extensions/storage-users/pkg/logging/logging.go new file mode 100644 index 0000000000..3c9fb9c661 --- /dev/null +++ b/extensions/storage-users/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/storage-users/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/storage-users/pkg/revaconfig/config.go b/extensions/storage-users/pkg/revaconfig/config.go new file mode 100644 index 0000000000..7794794629 --- /dev/null +++ b/extensions/storage-users/pkg/revaconfig/config.go @@ -0,0 +1,67 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/extensions/storage-users/pkg/config" +) + +// StorageUsersConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func StorageUsersConfigFromStruct(cfg *config.Config) map[string]interface{} { + rcfg := map[string]interface{}{ + "core": map[string]interface{}{ + "tracing_enabled": cfg.Tracing.Enabled, + "tracing_endpoint": cfg.Tracing.Endpoint, + "tracing_collector": cfg.Tracing.Collector, + "tracing_service_name": cfg.Service.Name, + }, + "shared": map[string]interface{}{ + "jwt_secret": cfg.TokenManager.JWTSecret, + "gatewaysvc": cfg.Reva.Address, + "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, + }, + "grpc": map[string]interface{}{ + "network": cfg.GRPC.Protocol, + "address": cfg.GRPC.Addr, + // TODO build services dynamically + "services": map[string]interface{}{ + "storageprovider": map[string]interface{}{ + "driver": cfg.Driver, + "drivers": UserDrivers(cfg), + "mount_id": cfg.MountID, + "expose_data_server": cfg.ExposeDataServer, + "data_server_url": cfg.DataServerURL, + "tmp_folder": cfg.TempFolder, + }, + }, + "interceptors": map[string]interface{}{ + "eventsmiddleware": map[string]interface{}{ + "group": "sharing", + "type": "nats", + "address": cfg.Events.Addr, + "clusterID": cfg.Events.ClusterID, + }, + }, + }, + "http": map[string]interface{}{ + "network": cfg.HTTP.Protocol, + "address": cfg.HTTP.Addr, + // TODO build services dynamically + "services": map[string]interface{}{ + "dataprovider": map[string]interface{}{ + "prefix": cfg.HTTP.Prefix, + "driver": cfg.Driver, + "drivers": UserDrivers(cfg), + "timeout": 86400, + "insecure": cfg.DataProviderInsecure, + "disable_tus": false, + }, + }, + }, + } + if cfg.ReadOnly { + gcfg := rcfg["grpc"].(map[string]interface{}) + gcfg["interceptors"] = map[string]interface{}{ + "readonly": map[string]interface{}{}, + } + } + return rcfg +} diff --git a/extensions/storage-users/pkg/config/user.go b/extensions/storage-users/pkg/revaconfig/user.go similarity index 97% rename from extensions/storage-users/pkg/config/user.go rename to extensions/storage-users/pkg/revaconfig/user.go index 677b7b35bd..a8c7d73363 100644 --- a/extensions/storage-users/pkg/config/user.go +++ b/extensions/storage-users/pkg/revaconfig/user.go @@ -1,6 +1,8 @@ -package config +package revaconfig -func UserDrivers(cfg *Config) map[string]interface{} { +import "github.com/owncloud/ocis/extensions/storage-users/pkg/config" + +func UserDrivers(cfg *config.Config) map[string]interface{} { return map[string]interface{}{ "eos": map[string]interface{}{ "namespace": cfg.Drivers.EOS.Root, diff --git a/extensions/storage-users/pkg/server/debug/option.go b/extensions/storage-users/pkg/server/debug/option.go new file mode 100644 index 0000000000..7493b21c48 --- /dev/null +++ b/extensions/storage-users/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/extensions/storage-users/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Logger log.Logger + Context context.Context + Config *config.Config +} + +// newOptions initializes the available default options. +func newOptions(opts ...Option) Options { + opt := Options{} + + for _, o := range opts { + o(&opt) + } + + return opt +} + +// Logger provides a function to set the logger option. +func Logger(val log.Logger) Option { + return func(o *Options) { + o.Logger = val + } +} + +// Context provides a function to set the context option. +func Context(val context.Context) Option { + return func(o *Options) { + o.Context = val + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} diff --git a/extensions/storage-users/pkg/server/debug/server.go b/extensions/storage-users/pkg/server/debug/server.go new file mode 100644 index 0000000000..65c9fdc7a8 --- /dev/null +++ b/extensions/storage-users/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/extensions/storage-users/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/service/debug" + "github.com/owncloud/ocis/ocis-pkg/version" +) + +// Server initializes the debug service and server. +func Server(opts ...Option) (*http.Server, error) { + options := newOptions(opts...) + + return debug.NewService( + debug.Logger(options.Logger), + debug.Name(options.Config.Service.Name), + debug.Version(version.String), + debug.Address(options.Config.Debug.Addr), + debug.Token(options.Config.Debug.Token), + debug.Pprof(options.Config.Debug.Pprof), + debug.Zpages(options.Config.Debug.Zpages), + debug.Health(health(options.Config)), + debug.Ready(ready(options.Config)), + //debug.CorsAllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins), + //debug.CorsAllowedMethods(options.Config.HTTP.CORS.AllowedMethods), + //debug.CorsAllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders), + //debug.CorsAllowCredentials(options.Config.HTTP.CORS.AllowCredentials), + ), nil +} + +// health implements the health check. +func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} + +// ready implements the ready check. +func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} diff --git a/extensions/storage-users/pkg/tracing/tracing.go b/extensions/storage-users/pkg/tracing/tracing.go new file mode 100644 index 0000000000..d0bcbb5538 --- /dev/null +++ b/extensions/storage-users/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/storage-users/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/storage/.dockerignore b/extensions/storage/.dockerignore deleted file mode 100644 index 4ec85b5e4f..0000000000 --- a/extensions/storage/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!bin/ diff --git a/extensions/storage/docker/Dockerfile.linux.amd64 b/extensions/storage/docker/Dockerfile.linux.amd64 deleted file mode 100644 index 231e033f2e..0000000000 --- a/extensions/storage/docker/Dockerfile.linux.amd64 +++ /dev/null @@ -1,19 +0,0 @@ -FROM amd64/alpine:latest - -RUN apk update && \ - apk upgrade && \ - apk add ca-certificates mailcap && \ - rm -rf /var/cache/apk/* && \ - echo 'hosts: files dns' >| /etc/nsswitch.conf - -LABEL maintainer="ownCloud GmbH " \ - org.label-schema.name="oCIS Reva" \ - org.label-schema.vendor="ownCloud GmbH" \ - org.label-schema.schema-version="1.0" - -EXPOSE 9140 9141 9142 9143 - -ENTRYPOINT ["/usr/bin/ocis-reva"] -CMD ["server"] - -COPY bin/ocis-reva /usr/bin/ocis-reva diff --git a/extensions/storage/docker/Dockerfile.linux.arm b/extensions/storage/docker/Dockerfile.linux.arm deleted file mode 100644 index af07021030..0000000000 --- a/extensions/storage/docker/Dockerfile.linux.arm +++ /dev/null @@ -1,19 +0,0 @@ -FROM arm32v6/alpine:latest - -RUN apk update && \ - apk upgrade && \ - apk add ca-certificates mailcap && \ - rm -rf /var/cache/apk/* && \ - echo 'hosts: files dns' >| /etc/nsswitch.conf - -LABEL maintainer="ownCloud GmbH " \ - org.label-schema.name="oCIS Reva" \ - org.label-schema.vendor="ownCloud GmbH" \ - org.label-schema.schema-version="1.0" - -EXPOSE 9140 9141 9142 9143 - -ENTRYPOINT ["/usr/bin/ocis-reva"] -CMD ["server"] - -COPY bin/ocis-reva /usr/bin/ocis-reva diff --git a/extensions/storage/docker/Dockerfile.linux.arm64 b/extensions/storage/docker/Dockerfile.linux.arm64 deleted file mode 100644 index e8f5ac3626..0000000000 --- a/extensions/storage/docker/Dockerfile.linux.arm64 +++ /dev/null @@ -1,19 +0,0 @@ -FROM arm64v8/alpine:latest - -RUN apk update && \ - apk upgrade && \ - apk add ca-certificates mailcap && \ - rm -rf /var/cache/apk/* && \ - echo 'hosts: files dns' >| /etc/nsswitch.conf - -LABEL maintainer="ownCloud GmbH " \ - org.label-schema.name="oCIS Reva" \ - org.label-schema.vendor="ownCloud GmbH" \ - org.label-schema.schema-version="1.0" - -EXPOSE 9140 9141 9142 9143 - -ENTRYPOINT ["/usr/bin/ocis-reva"] -CMD ["server"] - -COPY bin/ocis-reva /usr/bin/ocis-reva diff --git a/extensions/storage/docker/manifest.tmpl b/extensions/storage/docker/manifest.tmpl deleted file mode 100644 index ac5ae8e046..0000000000 --- a/extensions/storage/docker/manifest.tmpl +++ /dev/null @@ -1,22 +0,0 @@ -image: owncloud/ocis-reva:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}} -{{#if build.tags}} -tags: -{{#each build.tags}} - - {{this}} -{{/each}} -{{/if}} -manifests: - - image: owncloud/ocis-reva:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64 - platform: - architecture: amd64 - os: linux - - image: owncloud/ocis-reva:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64 - platform: - architecture: arm64 - variant: v8 - os: linux - - image: owncloud/ocis-reva:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm - platform: - architecture: arm - variant: v6 - os: linux diff --git a/extensions/storage/pkg/command/root.go b/extensions/storage/pkg/command/root.go deleted file mode 100644 index 864c281f52..0000000000 --- a/extensions/storage/pkg/command/root.go +++ /dev/null @@ -1,45 +0,0 @@ -package command - -import ( - "os" - - "github.com/owncloud/ocis/extensions/storage/pkg/config" - "github.com/owncloud/ocis/ocis-pkg/clihelper" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/urfave/cli/v2" -) - -// GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ - Health(cfg), - } -} - -// Execute is the entry point for the storage command. -func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "storage", - Usage: "Storage service for oCIS", - - Commands: GetCommands(cfg), - }) - - cli.HelpFlag = &cli.BoolFlag{ - Name: "help,h", - Usage: "Show the help", - } - - return app.Run(os.Args) -} - -// NewLogger initializes a service-specific logger instance. -func NewLogger(cfg *config.Config) log.Logger { - return log.NewLogger( - log.Name("storage"), - log.Level(cfg.Log.Level), - log.Pretty(cfg.Log.Pretty), - log.Color(cfg.Log.Color), - log.File(cfg.Log.File), - ) -} diff --git a/extensions/storage/pkg/config/config.go b/extensions/storage/pkg/config/config.go deleted file mode 100644 index c3a626e40f..0000000000 --- a/extensions/storage/pkg/config/config.go +++ /dev/null @@ -1,1848 +0,0 @@ -package config - -import ( - "context" - - "github.com/owncloud/ocis/ocis-pkg/shared" -) - -// Log defines the available logging configuration. -type Log struct { - Level string `yaml:"level"` - Pretty bool `yaml:"pretty"` - Color bool `yaml:"color"` - File string `yaml:"file"` -} - -// Debug defines the available debug configuration. -type Debug struct { - Addr string `yaml:"addr"` - Token string `yaml:"token"` - Pprof bool `yaml:"pprof"` - Zpages bool `yaml:"zpages"` -} - -// Gateway defines the available gateway configuration. -type Gateway struct { - Port - CommitShareToStorageGrant bool `yaml:"commit_share_to_storage_grant"` - CommitShareToStorageRef bool `yaml:"commit_share_to_storage_ref"` - DisableHomeCreationOnLogin bool `yaml:"disable_home_creation_on_login"` - ShareFolder string `yaml:"share_folder"` - LinkGrants string `yaml:"link_grants"` - HomeMapping string `yaml:"home_mapping"` - EtagCacheTTL int `yaml:"etag_cache_ttl"` -} - -// StorageRegistry defines the available storage registry configuration -type StorageRegistry struct { - Driver string `yaml:"driver"` - // HomeProvider is the path in the global namespace that the static storage registry uses to determine the home storage - HomeProvider string `yaml:"home_provider"` - Rules []string `yaml:"rules"` - JSON string `yaml:"json"` -} - -// AppRegistry defines the available app registry configuration -type AppRegistry struct { - Driver string `yaml:"driver"` - MimetypesJSON string `yaml:"mime_types_json"` -} - -// AppProvider defines the available app provider configuration -type AppProvider struct { - Port - ExternalAddr string `yaml:"external_addr"` - Driver string `yaml:"driver"` - WopiDriver WopiDriver `yaml:"wopi_driver"` - AppsURL string `yaml:"apps_url"` - OpenURL string `yaml:"open_url"` - NewURL string `yaml:"new_url"` -} - -type WopiDriver struct { - AppAPIKey string `yaml:"app_api_key"` - AppDesktopOnly bool `yaml:"app_desktop_only"` - AppIconURI string `yaml:"app_icon_uri"` - AppInternalURL string `yaml:"app_internal_url"` - AppName string `yaml:"app_name"` - AppURL string `yaml:"app_url"` - Insecure bool `yaml:"insecure"` - IopSecret string `yaml:"ipo_secret"` - JWTSecret string `yaml:"jwt_secret"` - WopiURL string `yaml:"wopi_url"` -} - -// Sharing defines the available sharing configuration. -type Sharing struct { - Port - UserDriver string `yaml:"user_driver"` - UserJSONFile string `yaml:"user_json_file"` - CS3ProviderAddr string `yaml:"provider_addr"` - CS3ServiceUser string `yaml:"service_user_id"` - CS3ServiceUserIdp string `yaml:"service_user_idp"` - UserSQLUsername string `yaml:"user_sql_username"` - UserSQLPassword string `yaml:"user_sql_password"` - UserSQLHost string `yaml:"user_sql_host"` - UserSQLPort int `yaml:"user_sql_port"` - UserSQLName string `yaml:"user_sql_name"` - PublicDriver string `yaml:"public_driver"` - PublicJSONFile string `yaml:"public_json_file"` - PublicPasswordHashCost int `yaml:"public_password_hash_cost"` - PublicEnableExpiredSharesCleanup bool `yaml:"public_enable_expired_shares_cleanup"` - PublicJanitorRunInterval int `yaml:"public_janitor_run_interval"` - UserStorageMountID string `yaml:"user_storage_mount_id"` - Events Events `yaml:"events"` -} - -type Events struct { - Address string `yaml:"address"` - ClusterID string `yaml:"cluster_id"` -} - -// Port defines the available port configuration. -type Port struct { - // MaxCPUs can be a number or a percentage - MaxCPUs string `yaml:"max_cpus"` - LogLevel string `yaml:"log_level"` - // GRPCNetwork can be tcp, udp or unix - GRPCNetwork string `yaml:"grpc_network"` - // GRPCAddr to listen on, hostname:port (0.0.0.0:9999 for all interfaces) or socket (/var/run/reva/sock) - GRPCAddr string `yaml:"grpc_addr"` - // Protocol can be grpc or http - // HTTPNetwork can be tcp, udp or unix - HTTPNetwork string `yaml:"http_network"` - // HTTPAddr to listen on, hostname:port (0.0.0.0:9100 for all interfaces) or socket (/var/run/reva/sock) - HTTPAddr string `yaml:"http_addr"` - // Protocol can be grpc or http - Protocol string `yaml:"protocol"` - // Endpoint is used by the gateway and registries (eg localhost:9100 or cloud.example.com) - Endpoint string `yaml:"endpoint"` - // DebugAddr for the debug endpoint to bind to - DebugAddr string `yaml:"debug_addr"` - // Services can be used to give a list of services that should be started on this port - Services []string `yaml:"services"` - // Config can be used to configure the reva instance. - // Services and Protocol will be ignored if this is used - Config map[string]interface{} `yaml:"config"` - - // Context allows for context cancellation and propagation - Context context.Context - - // Supervised is used when running under an oCIS runtime supervision tree - Supervised bool // deprecated // TODO: delete me -} - -// Users defines the available users configuration. -type Users struct { - Port - Driver string `yaml:"driver"` - JSON string `yaml:"json"` - UserGroupsCacheExpiration int `yaml:"user_groups_cache_expiration"` -} - -// AuthMachineConfig defines the available configuration for the machine auth driver. -type AuthMachineConfig struct { - MachineAuthAPIKey string `yaml:"machine_auth_api_key"` -} - -// Groups defines the available groups configuration. -type Groups struct { - Port - Driver string `yaml:"driver"` - JSON string `yaml:"json"` - GroupMembersCacheExpiration int `yaml:"group_members_cache_expiration"` -} - -// FrontendPort defines the available frontend configuration. -type FrontendPort struct { - Port - - AppProviderInsecure bool `yaml:"app_provider_insecure"` - AppProviderPrefix string `yaml:"app_provider_prefix"` - ArchiverInsecure bool `yaml:"archiver_insecure"` - ArchiverPrefix string `yaml:"archiver_prefix"` - DatagatewayPrefix string `yaml:"data_gateway_prefix"` - Favorites bool `yaml:"favorites"` - ProjectSpaces bool `yaml:"project_spaces"` - OCSPrefix string `yaml:"ocs_prefix"` - OCSSharePrefix string `yaml:"ocs_share_prefix"` - OCSHomeNamespace string `yaml:"ocs_home_namespace"` - PublicURL string `yaml:"public_url"` - OCSCacheWarmupDriver string `yaml:"ocs_cache_warmup_driver"` - OCSAdditionalInfoAttribute string `yaml:"ocs_additional_info_attribute"` - OCSResourceInfoCacheTTL int `yaml:"ocs_resource_info_cache_ttl"` - Middleware Middleware `yaml:"middleware"` -} - -// Middleware configures reva middlewares. -type Middleware struct { - Auth Auth `yaml:"auth"` -} - -// Auth configures reva http auth middleware. -type Auth struct { - CredentialsByUserAgent map[string]string `yaml:"credentials_by_user_agenr"` -} - -// DataGatewayPort has a public url -type DataGatewayPort struct { - Port - PublicURL string -} - -type DataProvider struct { - Insecure bool `yaml:"insecure"` -} - -// StoragePort defines the available storage configuration. -type StoragePort struct { - Port - Driver string `yaml:"driver"` - MountID string `yaml:"mount_id"` - AlternativeID string `yaml:"alternative_id"` - ExposeDataServer bool `yaml:"expose_data_server"` - // url the data gateway will use to route requests - DataServerURL string `yaml:"data_server_url"` - - // for HTTP ports with only one http service - HTTPPrefix string `yaml:"http_prefix"` - TempFolder string `yaml:"temp_folder"` - ReadOnly bool `yaml:"read_only"` - DataProvider DataProvider `yaml:"data_provider"` - GatewayEndpoint string `yaml:"gateway_endpoint"` -} - -// PublicStorage configures a public storage provider -type PublicStorage struct { - StoragePort - - PublicShareProviderAddr string `yaml:"public_share_provider_addr"` - UserProviderAddr string `yaml:"user_provider_addr"` -} - -// StorageConfig combines all available storage driver configuration parts. -type StorageConfig struct { - EOS DriverEOS `yaml:"eos"` - Local DriverCommon `yaml:"local"` - OwnCloudSQL DriverOwnCloudSQL `yaml:"owncloud_sql"` - S3 DriverS3 `yaml:"s3"` - S3NG DriverS3NG `yaml:"s3ng"` - OCIS DriverOCIS `yaml:"ocis"` -} - -// DriverCommon defines common driver configuration options. -type DriverCommon struct { - // Root is the absolute path to the location of the data - Root string `yaml:"root"` - //ShareFolder defines the name of the folder jailing all shares - ShareFolder string `yaml:"share_folder"` - // UserLayout contains the template used to construct - // the internal path, eg: `{{substr 0 1 .Username}}/{{.Username}}` - UserLayout string `yaml:"user_layout"` - // EnableHome enables the creation of home directories. - EnableHome bool `yaml:"enable_home"` - // PersonalSpaceAliasTemplate contains the template used to construct - // the personal space alias, eg: `"{{.SpaceType}}/{{.User.Username | lower}}"` - PersonalSpaceAliasTemplate string `yaml:"personalspacealias_template"` - // GeneralSpaceAliasTemplate contains the template used to construct - // the general space alias, eg: `{{.SpaceType}}/{{.SpaceName | replace " " "-" | lower}}` - GeneralSpaceAliasTemplate string `yaml:"generalspacealias_template"` -} - -// DriverEOS defines the available EOS driver configuration. -type DriverEOS struct { - DriverCommon - - // ShadowNamespace for storing shadow data - ShadowNamespace string `yaml:"shadow_namespace"` - - // UploadsNamespace for storing upload data - UploadsNamespace string `yaml:"uploads_namespace"` - - // Location of the eos binary. - // Default is /usr/bin/eos. - EosBinary string `yaml:"eos_binary"` - - // Location of the xrdcopy binary. - // Default is /usr/bin/xrdcopy. - XrdcopyBinary string `yaml:"xrd_copy_binary"` - - // URL of the Master EOS MGM. - // Default is root://eos-example.org - MasterURL string `yaml:"master_url"` - - // URI of the EOS MGM grpc server - // Default is empty - GrpcURI string `yaml:"grpc_uri"` - - // URL of the Slave EOS MGM. - // Default is root://eos-example.org - SlaveURL string `yaml:"slave_url"` - - // Location on the local fs where to store reads. - // Defaults to os.TempDir() - CacheDirectory string `yaml:"cache_directory"` - - // Enables logging of the commands executed - // Defaults to false - EnableLogging bool `yaml:"enable_logging"` - - // ShowHiddenSysFiles shows internal EOS files like - // .sys.v# and .sys.a# files. - ShowHiddenSysFiles bool `yaml:"shadow_hidden_files"` - - // ForceSingleUserMode will force connections to EOS to use SingleUsername - ForceSingleUserMode bool `yaml:"force_single_user_mode"` - - // UseKeyTabAuth changes will authenticate requests by using an EOS keytab. - UseKeytab bool `yaml:"user_keytab"` - - // SecProtocol specifies the xrootd security protocol to use between the server and EOS. - SecProtocol string `yaml:"sec_protocol"` - - // Keytab specifies the location of the keytab to use to authenticate to EOS. - Keytab string `yaml:"keytab"` - - // SingleUsername is the username to use when SingleUserMode is enabled - SingleUsername string `yaml:"single_username"` - - // gateway service to use for uid lookups - GatewaySVC string `yaml:"gateway_svc"` -} - -// DriverOCIS defines the available oCIS storage driver configuration. -type DriverOCIS struct { - DriverCommon -} - -// DriverOwnCloudSQL defines the available ownCloudSQL storage driver configuration. -type DriverOwnCloudSQL struct { - DriverCommon - - UploadInfoDir string `yaml:"upload_info_dir"` - DBUsername string `yaml:"db_username"` - DBPassword string `yaml:"db_password"` - DBHost string `yaml:"db_host"` - DBPort int `yaml:"db_port"` - DBName string `yaml:"db_name"` -} - -// DriverS3 defines the available S3 storage driver configuration. -type DriverS3 struct { - DriverCommon - - Region string `yaml:"region"` - AccessKey string `yaml:"access_key"` - SecretKey string `yaml:"secret_key"` - Endpoint string `yaml:"endpoint"` - Bucket string `yaml:"bucket"` -} - -// DriverS3NG defines the available s3ng storage driver configuration. -type DriverS3NG struct { - DriverCommon - - Region string `yaml:"region"` - AccessKey string `yaml:"access_key"` - SecretKey string `yaml:"secret_key"` - Endpoint string `yaml:"endpoint"` - Bucket string `yaml:"bucket"` -} - -// OIDC defines the available OpenID Connect configuration. -type OIDC struct { - Issuer string `yaml:"issuer"` - Insecure bool `yaml:"insecure"` - IDClaim string `yaml:"id_claim"` - UIDClaim string `yaml:"uid_claim"` - GIDClaim string `yaml:"gid_claim"` -} - -// LDAP defines the available ldap configuration. -type LDAP struct { - URI string `yaml:"uri"` - CACert string `yaml:"ca_cert"` - Insecure bool `yaml:"insecure"` - UserBaseDN string `yaml:"user_base_dn"` - GroupBaseDN string `yaml:"group_base_dn"` - UserScope string `yaml:"user_scope"` - GroupScope string `yaml:"group_scope"` - UserObjectClass string `yaml:"user_objectclass"` - GroupObjectClass string `yaml:"group_objectclass"` - UserFilter string `yaml:"user_filter"` - GroupFilter string `yaml:"group_filter"` - LoginAttributes []string `yaml:"login_attributes"` - BindDN string `yaml:"bind_dn"` - BindPassword string `yaml:"bind_password"` - IDP string `yaml:"idp"` - UserSchema LDAPUserSchema `yaml:"user_schema"` - GroupSchema LDAPGroupSchema `yaml:"group_schema"` -} - -// UserGroupRest defines the REST driver specification for user and group resolution. -type UserGroupRest struct { - ClientID string `yaml:"client_id"` - ClientSecret string `yaml:"client_secret"` - RedisAddress string `yaml:"redis_address"` - RedisUsername string `yaml:"redis_username"` - RedisPassword string `yaml:"redis_password"` - IDProvider string `yaml:"idp_provider"` - APIBaseURL string `yaml:"api_base_url"` - OIDCTokenEndpoint string `yaml:"oidc_token_endpoint"` - TargetAPI string `yaml:"target_api"` -} - -// UserOwnCloudSQL defines the available ownCloudSQL user provider configuration. -type UserOwnCloudSQL struct { - DBUsername string `yaml:"db_username"` - DBPassword string `yaml:"db_password"` - DBHost string `yaml:"db_host"` - DBPort int `yaml:"db_port"` - DBName string `yaml:"db_name"` - Idp string `yaml:"idp"` - Nobody int64 `yaml:"nobody"` - JoinUsername bool `yaml:"join_username"` - JoinOwnCloudUUID bool `yaml:"join_owncloud_uuid"` - EnableMedialSearch bool `yaml:"enable_medial_search"` -} - -// LDAPUserSchema defines the available ldap user schema configuration. -type LDAPUserSchema struct { - ID string `yaml:"id"` - IDIsOctetString bool `yaml:"id_is_octet_string"` - Mail string `yaml:"mail"` - DisplayName string `yaml:"display_name"` - Username string `yaml:"user_name"` - UIDNumber string `yaml:"uid_number"` - GIDNumber string `yaml:"gid_number"` -} - -// LDAPGroupSchema defines the available ldap group schema configuration. -type LDAPGroupSchema struct { - ID string `yaml:"id"` - IDIsOctetString bool `yaml:"id_is_octet_string"` - Mail string `yaml:"mail"` - DisplayName string `yaml:"display_name"` - Groupname string `yaml:"group_name"` - Member string `yaml:"member"` - GIDNumber string `yaml:"gid_number"` -} - -// OCDav defines the available ocdav configuration. -type OCDav struct { - // Addr to listen to with the http server for the ocdav service - Addr string `yaml:"addr"` - Prefix string `yaml:"prefix"` - WebdavNamespace string `yaml:"webdav_namespace"` - FilesNamespace string `yaml:"files_namespace"` - SharesNamespace string `yaml:"shares_namespace"` - // PublicURL used to redirect /s/{token} URLs to - PublicURL string `yaml:"public_url"` - - // Addr to listen to with the debug http server - DebugAddr string `yaml:"debug_addr"` - - // GatewaySVC to forward CS3 requests to TODO use registry - GatewaySVC string `yaml:"gateway_svc"` - // JWTSecret used to verify reva access token - JWTSecret string `yaml:"jwt_secret"` - // Insecure certificates allowed when making requests to the gateway - Insecure bool `yaml:"insecure"` - // Timeout in seconds when making requests to the gateway - Timeout int64 `yaml:"timeout"` -} - -// Archiver defines the available archiver configuration. -type Archiver struct { - MaxNumFiles int64 `yaml:"max_num_files"` - MaxSize int64 `yaml:"max_size"` - ArchiverURL string `yaml:"archiver_url"` -} - -// Reva defines the available reva configuration. -type Reva struct { - // JWTSecret used to sign jwt tokens between services - JWTSecret string `yaml:"jwt_secret"` - SkipUserGroupsInToken bool `yaml:"skip_user_grooups_in_token"` - TransferSecret string `yaml:"transfer_secret"` - TransferExpires int `yaml:"transfer_expires"` - OIDC OIDC `yaml:"oidc"` - LDAP LDAP `yaml:"ldap"` - UserGroupRest UserGroupRest `yaml:"user_group_rest"` - UserOwnCloudSQL UserOwnCloudSQL `yaml:"user_owncloud_sql"` - Archiver Archiver `yaml:"archiver"` - UserStorage StorageConfig `yaml:"user_storage"` - MetadataStorage StorageConfig `yaml:"metadata_storage"` - // Ports are used to configure which services to start on which port - Frontend FrontendPort `yaml:"frontend"` - DataGateway DataGatewayPort `yaml:"data_gateway"` - Gateway Gateway `yaml:"gateway"` - StorageRegistry StorageRegistry `yaml:"storage_registry"` - AppRegistry AppRegistry `yaml:"app_registry"` - Users Users `yaml:"users"` - Groups Groups `yaml:"groups"` - AuthProvider Users `yaml:"auth_provider"` - AuthBasic Port `yaml:"auth_basic"` - AuthBearer Port `yaml:"auth_bearer"` - AuthMachine Port `yaml:"auth_machine"` - AuthMachineConfig AuthMachineConfig `yaml:"auth_machine_config"` - Sharing Sharing `yaml:"sharing"` - StorageShares StoragePort `yaml:"storage_shares"` - StorageUsers StoragePort `yaml:"storage_users"` - StoragePublicLink PublicStorage `yaml:"storage_public_link"` - StorageMetadata StoragePort `yaml:"storage_metadata"` - AppProvider AppProvider `yaml:"app_provider"` - Permissions Port `yaml:"permissions"` - // Configs can be used to configure the reva instance. - // Services and Ports will be ignored if this is used - Configs map[string]interface{} `yaml:"configs"` - // chunking and resumable upload config (TUS) - UploadMaxChunkSize int `yaml:"upload_max_chunk_size"` - UploadHTTPMethodOverride string `yaml:"upload_http_method_override"` - // checksumming capabilities - ChecksumSupportedTypes []string `yaml:"checksum_supported_types"` - ChecksumPreferredUploadType string `yaml:"checksum_preferred_upload_type"` - DefaultUploadProtocol string `yaml:"default_upload_protocol"` -} - -// Tracing defines the available tracing configuration. -type Tracing struct { - Enabled bool `yaml:"enabled"` - Type string `yaml:"type"` - Endpoint string `yaml:"endpoint"` - Collector string `yaml:"collector"` - Service string `yaml:"service"` -} - -// Asset defines the available asset configuration. -type Asset struct { - Path string `yaml:"path"` -} - -// Config combines all available configuration parts. -type Config struct { - *shared.Commons - - File string `yaml:"file"` - Log *shared.Log `yaml:"log"` - Debug Debug `yaml:"debug"` - OCDav OCDav `yaml:"ocdav"` - Reva Reva `yaml:"reva"` - Tracing Tracing `yaml:"tracing"` - Asset Asset `yaml:"asset"` -} - -// New initializes a new configuration with or without defaults. -func New() *Config { - return &Config{} -} - -// StructMappings binds a set of environment variables to a destination on cfg. Iterating over this set and editing the -// Destination value of a binding will alter the original value, as it is a pointer to its memory address. This lets -// us propagate changes easier. -func StructMappings(cfg *Config) []shared.EnvBinding { - return structMappings(cfg) -} - -// GetEnv fetches a list of known env variables for this extension. It is to be used by gookit, as it provides a list -// with all the environment variables an extension supports. -func GetEnv(cfg *Config) []string { - var r = make([]string, len(structMappings(cfg))) - for i := range structMappings(cfg) { - r = append(r, structMappings(cfg)[i].EnvVars...) - } - - return r -} - -func structMappings(cfg *Config) []shared.EnvBinding { - return []shared.EnvBinding{ - // Shared - { - EnvVars: []string{"OCIS_LOG_LEVEL", "STORAGE_FRONTEND_LOG_LEVEL"}, - Destination: &cfg.Log.Level, - }, - { - EnvVars: []string{"OCIS_LOG_PRETTY", "STORAGE_FRONTEND_LOG_PRETTY"}, - Destination: &cfg.Log.Pretty, - }, - { - EnvVars: []string{"OCIS_LOG_COLOR", "STORAGE_FRONTEND_LOG_COLOR"}, - Destination: &cfg.Log.Color, - }, - { - EnvVars: []string{"OCIS_INSECURE", "STORAGE_METADATA_DATAPROVIDER_INSECURE"}, - Destination: &cfg.Reva.StorageMetadata.DataProvider.Insecure, - }, - { - EnvVars: []string{"OCIS_INSECURE", "STORAGE_FRONTEND_APPPROVIDER_INSECURE"}, - Destination: &cfg.Reva.Frontend.AppProviderInsecure, - }, - { - EnvVars: []string{"OCIS_INSECURE", "STORAGE_FRONTEND_ARCHIVER_INSECURE"}, - Destination: &cfg.Reva.Frontend.ArchiverInsecure, - }, - { - EnvVars: []string{"OCIS_INSECURE", "STORAGE_OIDC_INSECURE"}, - Destination: &cfg.Reva.OIDC.Insecure, - }, - { - EnvVars: []string{"OCIS_INSECURE", "STORAGE_USERS_DATAPROVIDER_INSECURE"}, - Destination: &cfg.Reva.StorageUsers.DataProvider.Insecure, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_LOCAL_ROOT"}, - Destination: &cfg.Reva.UserStorage.Local.Root, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER"}, - Destination: &cfg.Reva.StorageUsers.Driver, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_OCIS_ROOT"}, - Destination: &cfg.Reva.UserStorage.OCIS.Root, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_OCIS_ROOT"}, - Destination: &cfg.Reva.MetadataStorage.OCIS.Root, - }, - { - EnvVars: []string{"STORAGE_SHARING_USER_JSON_FILE"}, - Destination: &cfg.Reva.Sharing.UserJSONFile, - }, - { - EnvVars: []string{"OCIS_URL", "STORAGE_FRONTEND_PUBLIC_URL"}, - Destination: &cfg.Reva.Frontend.PublicURL, - }, - { - EnvVars: []string{"OCIS_URL", "STORAGE_OIDC_ISSUER"}, - Destination: &cfg.Reva.OIDC.Issuer, - }, - { - EnvVars: []string{"OCIS_URL", "STORAGE_LDAP_IDP"}, - Destination: &cfg.Reva.LDAP.IDP, - }, - { - EnvVars: []string{"OCIS_URL", "STORAGE_USERPROVIDER_OWNCLOUDSQL_IDP"}, - Destination: &cfg.Reva.UserOwnCloudSQL.Idp, - }, - { - EnvVars: []string{"STORAGE_DEBUG_ADDR"}, - Destination: &cfg.Debug.Addr, - }, - - // debug - - { - EnvVars: []string{"STORAGE_DEBUG_TOKEN"}, - Destination: &cfg.Debug.Token, - }, - { - EnvVars: []string{"STORAGE_DEBUG_PPROF"}, - Destination: &cfg.Debug.Pprof, - }, - { - EnvVars: []string{"STORAGE_DEBUG_ZPAGES"}, - Destination: &cfg.Debug.Zpages, - }, - - // app provider - - { - EnvVars: []string{"APP_PROVIDER_DEBUG_ADDR"}, - Destination: &cfg.Reva.AppProvider.DebugAddr, - }, - { - EnvVars: []string{"APP_PROVIDER_GRPC_NETWORK"}, - Destination: &cfg.Reva.AppProvider.GRPCNetwork, - }, - { - EnvVars: []string{"APP_PROVIDER_GRPC_ADDR"}, - Destination: &cfg.Reva.AppProvider.GRPCAddr, - }, - { - EnvVars: []string{"APP_PROVIDER_EXTERNAL_ADDR"}, - Destination: &cfg.Reva.AppProvider.ExternalAddr, - }, - { - EnvVars: []string{"APP_PROVIDER_DRIVER"}, - Destination: &cfg.Reva.AppProvider.Driver, - }, - { - EnvVars: []string{"APP_PROVIDER_WOPI_DRIVER_APP_API_KEY"}, - Destination: &cfg.Reva.AppProvider.WopiDriver.AppAPIKey, - }, - { - EnvVars: []string{"APP_PROVIDER_WOPI_DRIVER_APP_DESKTOP_ONLY"}, - Destination: &cfg.Reva.AppProvider.WopiDriver.AppDesktopOnly, - }, - { - EnvVars: []string{"APP_PROVIDER_WOPI_DRIVER_APP_ICON_URI"}, - Destination: &cfg.Reva.AppProvider.WopiDriver.AppIconURI, - }, - { - EnvVars: []string{"APP_PROVIDER_WOPI_DRIVER_APP_INTERNAL_URL"}, - Destination: &cfg.Reva.AppProvider.WopiDriver.AppInternalURL, - }, - { - EnvVars: []string{"APP_PROVIDER_WOPI_DRIVER_APP_NAME"}, - Destination: &cfg.Reva.AppProvider.WopiDriver.AppName, - }, - { - EnvVars: []string{"APP_PROVIDER_WOPI_DRIVER_APP_URL"}, - Destination: &cfg.Reva.AppProvider.WopiDriver.AppURL, - }, - { - EnvVars: []string{"APP_PROVIDER_WOPI_DRIVER_INSECURE"}, - Destination: &cfg.Reva.AppProvider.WopiDriver.Insecure, - }, - { - EnvVars: []string{"APP_PROVIDER_WOPI_DRIVER_IOP_SECRET"}, - Destination: &cfg.Reva.AppProvider.WopiDriver.IopSecret, - }, - { - EnvVars: []string{"APP_PROVIDER_WOPI_DRIVER_WOPI_URL"}, - Destination: &cfg.Reva.AppProvider.WopiDriver.WopiURL, - }, - - // authbasic - { - EnvVars: []string{"STORAGE_AUTH_BASIC_DEBUG_ADDR"}, - Destination: &cfg.Reva.AuthBasic.DebugAddr, - }, - { - EnvVars: []string{"STORAGE_AUTH_DRIVER"}, - Destination: &cfg.Reva.AuthProvider.Driver, - }, - { - EnvVars: []string{"STORAGE_AUTH_JSON"}, - Destination: &cfg.Reva.AuthProvider.JSON, - }, - { - EnvVars: []string{"STORAGE_AUTH_BASIC_GRPC_NETWORK"}, - Destination: &cfg.Reva.AuthBasic.GRPCNetwork, - }, - { - EnvVars: []string{"STORAGE_AUTH_BASIC_GRPC_ADDR"}, - Destination: &cfg.Reva.AuthBasic.GRPCAddr, - }, - { - EnvVars: []string{"REVA_GATEWAY"}, - Destination: &cfg.Reva.Gateway.Endpoint, - }, - - // authbearer - { - EnvVars: []string{"STORAGE_AUTH_BEARER_DEBUG_ADDR"}, - Destination: &cfg.Reva.AuthBearer.DebugAddr, - }, - { - EnvVars: []string{"STORAGE_OIDC_ID_CLAIM"}, - Destination: &cfg.Reva.OIDC.IDClaim, - }, - { - EnvVars: []string{"STORAGE_OIDC_UID_CLAIM"}, - Destination: &cfg.Reva.OIDC.UIDClaim, - }, - { - EnvVars: []string{"STORAGE_OIDC_GID_CLAIM"}, - Destination: &cfg.Reva.OIDC.GIDClaim, - }, - { - EnvVars: []string{"STORAGE_AUTH_BEARER_GRPC_NETWORK"}, - Destination: &cfg.Reva.AuthBearer.GRPCNetwork, - }, - { - EnvVars: []string{"STORAGE_AUTH_BEARER_GRPC_ADDR"}, - Destination: &cfg.Reva.AuthBearer.GRPCAddr, - }, - - // auth-machine - { - EnvVars: []string{"STORAGE_AUTH_MACHINE_DEBUG_ADDR"}, - Destination: &cfg.Reva.AuthMachine.DebugAddr, - }, - { - EnvVars: []string{"OCIS_MACHINE_AUTH_API_KEY", "STORAGE_AUTH_MACHINE_AUTH_API_KEY"}, - Destination: &cfg.Reva.AuthMachineConfig.MachineAuthAPIKey, - }, - { - EnvVars: []string{"STORAGE_AUTH_MACHINE_GRPC_NETWORK"}, - Destination: &cfg.Reva.AuthMachine.GRPCNetwork, - }, - { - EnvVars: []string{"STORAGE_AUTH_MACHINE_GRPC_ADDR"}, - Destination: &cfg.Reva.AuthMachine.GRPCAddr, - }, - - // frontend - { - EnvVars: []string{"STORAGE_FRONTEND_DEBUG_ADDR"}, - Destination: &cfg.Reva.Frontend.DebugAddr, - }, - { - EnvVars: []string{"STORAGE_TRANSFER_SECRET"}, - Destination: &cfg.Reva.TransferSecret, - }, - { - EnvVars: []string{"STORAGE_ARCHIVER_MAX_NUM_FILES"}, - Destination: &cfg.Reva.Archiver.MaxNumFiles, - }, - { - EnvVars: []string{"STORAGE_ARCHIVER_MAX_SIZE"}, - Destination: &cfg.Reva.Archiver.MaxSize, - }, - { - EnvVars: []string{"STORAGE_FRONTEND_HTTP_NETWORK"}, - Destination: &cfg.Reva.Frontend.HTTPNetwork, - }, - { - EnvVars: []string{"STORAGE_FRONTEND_HTTP_ADDR"}, - Destination: &cfg.Reva.Frontend.HTTPAddr, - }, - { - EnvVars: []string{"STORAGE_FRONTEND_APPPROVIDER_PREFIX"}, - Destination: &cfg.Reva.Frontend.AppProviderPrefix, - }, - { - EnvVars: []string{"STORAGE_FRONTEND_ARCHIVER_PREFIX"}, - Destination: &cfg.Reva.Frontend.ArchiverPrefix, - }, - { - EnvVars: []string{"STORAGE_FRONTEND_DATAGATEWAY_PREFIX"}, - Destination: &cfg.Reva.Frontend.DatagatewayPrefix, - }, - { - EnvVars: []string{"STORAGE_FRONTEND_FAVORITES"}, - Destination: &cfg.Reva.Frontend.Favorites, - }, - { - EnvVars: []string{"STORAGE_FRONTEND_PROJECT_SPACES"}, - Destination: &cfg.Reva.Frontend.ProjectSpaces, - }, - { - EnvVars: []string{"STORAGE_FRONTEND_OCS_PREFIX"}, - Destination: &cfg.Reva.Frontend.OCSPrefix, - }, - { - EnvVars: []string{"STORAGE_FRONTEND_OCS_SHARE_PREFIX"}, - Destination: &cfg.Reva.Frontend.OCSSharePrefix, - }, - { - EnvVars: []string{"STORAGE_FRONTEND_OCS_HOME_NAMESPACE"}, - Destination: &cfg.Reva.Frontend.OCSHomeNamespace, - }, - { - EnvVars: []string{"STORAGE_FRONTEND_OCS_RESOURCE_INFO_CACHE_TTL"}, - Destination: &cfg.Reva.Frontend.OCSResourceInfoCacheTTL, - }, - { - EnvVars: []string{"STORAGE_FRONTEND_OCS_CACHE_WARMUP_DRIVER"}, - Destination: &cfg.Reva.Frontend.OCSCacheWarmupDriver, - }, - { - EnvVars: []string{"STORAGE_FRONTEND_OCS_ADDITIONAL_INFO_ATTRIBUTE"}, - Destination: &cfg.Reva.Frontend.OCSAdditionalInfoAttribute, - }, - { - EnvVars: []string{"STORAGE_FRONTEND_DEFAULT_UPLOAD_PROTOCOL"}, - Destination: &cfg.Reva.DefaultUploadProtocol, - }, - { - EnvVars: []string{"STORAGE_FRONTEND_UPLOAD_MAX_CHUNK_SIZE"}, - Destination: &cfg.Reva.UploadMaxChunkSize, - }, - { - EnvVars: []string{"STORAGE_FRONTEND_UPLOAD_HTTP_METHOD_OVERRIDE"}, - Destination: &cfg.Reva.UploadHTTPMethodOverride, - }, - { - EnvVars: []string{"STORAGE_FRONTEND_CHECKSUM_PREFERRED_UPLOAD_TYPE"}, - Destination: &cfg.Reva.ChecksumPreferredUploadType, - }, - { - EnvVars: []string{"STORAGE_FRONTEND_ARCHIVER_URL"}, - Destination: &cfg.Reva.Archiver.ArchiverURL, - }, - { - EnvVars: []string{"STORAGE_FRONTEND_APP_PROVIDER_APPS_URL"}, - Destination: &cfg.Reva.AppProvider.AppsURL, - }, - { - EnvVars: []string{"STORAGE_FRONTEND_APP_PROVIDER_OPEN_URL"}, - Destination: &cfg.Reva.AppProvider.OpenURL, - }, - { - EnvVars: []string{"STORAGE_FRONTEND_APP_PROVIDER_NEW_URL"}, - Destination: &cfg.Reva.AppProvider.NewURL, - }, - - // gateway - { - EnvVars: []string{"STORAGE_GATEWAY_DEBUG_ADDR"}, - Destination: &cfg.Reva.Gateway.DebugAddr, - }, - { - EnvVars: []string{"STORAGE_TRANSFER_EXPIRES"}, - Destination: &cfg.Reva.TransferExpires, - }, - { - EnvVars: []string{"STORAGE_GATEWAY_GRPC_NETWORK"}, - Destination: &cfg.Reva.Gateway.GRPCNetwork, - }, - { - EnvVars: []string{"STORAGE_GATEWAY_GRPC_ADDR"}, - Destination: &cfg.Reva.Gateway.GRPCAddr, - }, - - { - EnvVars: []string{"STORAGE_GATEWAY_COMMIT_SHARE_TO_STORAGE_GRANT"}, - Destination: &cfg.Reva.Gateway.CommitShareToStorageGrant, - }, - { - EnvVars: []string{"STORAGE_GATEWAY_COMMIT_SHARE_TO_STORAGE_REF"}, - Destination: &cfg.Reva.Gateway.CommitShareToStorageRef, - }, - { - EnvVars: []string{"STORAGE_GATEWAY_SHARE_FOLDER"}, - Destination: &cfg.Reva.Gateway.ShareFolder, - }, - { - EnvVars: []string{"STORAGE_GATEWAY_DISABLE_HOME_CREATION_ON_LOGIN"}, - Destination: &cfg.Reva.Gateway.DisableHomeCreationOnLogin, - }, - { - EnvVars: []string{"STORAGE_GATEWAY_HOME_MAPPING"}, - Destination: &cfg.Reva.Gateway.HomeMapping, - }, - { - EnvVars: []string{"STORAGE_GATEWAY_ETAG_CACHE_TTL"}, - Destination: &cfg.Reva.Gateway.EtagCacheTTL, - }, - { - EnvVars: []string{"STORAGE_AUTH_BASIC_ENDPOINT"}, - Destination: &cfg.Reva.AuthBasic.Endpoint, - }, - { - EnvVars: []string{"STORAGE_AUTH_BEARER_ENDPOINT"}, - Destination: &cfg.Reva.AuthBearer.Endpoint, - }, - { - EnvVars: []string{"STORAGE_AUTH_MACHINE_ENDPOINT"}, - Destination: &cfg.Reva.AuthMachine.Endpoint, - }, - { - EnvVars: []string{"STORAGE_STORAGE_REGISTRY_DRIVER"}, - Destination: &cfg.Reva.StorageRegistry.Driver, - }, - { - EnvVars: []string{"STORAGE_STORAGE_REGISTRY_HOME_PROVIDER"}, - Destination: &cfg.Reva.StorageRegistry.HomeProvider, - }, - { - EnvVars: []string{"STORAGE_STORAGE_REGISTRY_JSON"}, - Destination: &cfg.Reva.StorageRegistry.JSON, - }, - { - EnvVars: []string{"STORAGE_APP_REGISTRY_DRIVER"}, - Destination: &cfg.Reva.AppRegistry.Driver, - }, - { - EnvVars: []string{"STORAGE_APP_REGISTRY_MIMETYPES_JSON"}, - Destination: &cfg.Reva.AppRegistry.MimetypesJSON, - }, - { - EnvVars: []string{"STORAGE_DATAGATEWAY_PUBLIC_URL"}, - Destination: &cfg.Reva.DataGateway.PublicURL, - }, - { - EnvVars: []string{"STORAGE_USERPROVIDER_ENDPOINT"}, - Destination: &cfg.Reva.Users.Endpoint, - }, - { - EnvVars: []string{"STORAGE_GROUPPROVIDER_ENDPOINT"}, - Destination: &cfg.Reva.Groups.Endpoint, - }, - { - EnvVars: []string{"STORAGE_SHARING_ENDPOINT"}, - Destination: &cfg.Reva.Sharing.Endpoint, - }, - { - EnvVars: []string{"STORAGE_APPPROVIDER_ENDPOINT"}, - Destination: &cfg.Reva.AppProvider.Endpoint, - }, - { - EnvVars: []string{"STORAGE_USERS_ENDPOINT"}, - Destination: &cfg.Reva.StorageUsers.Endpoint, - }, - { - EnvVars: []string{"STORAGE_USERS_MOUNT_ID"}, - Destination: &cfg.Reva.StorageUsers.MountID, - }, - { - EnvVars: []string{"STORAGE_SHARES_ENDPOINT"}, - Destination: &cfg.Reva.StorageShares.Endpoint, - }, - { - EnvVars: []string{"STORAGE_PUBLIC_LINK_ENDPOINT"}, - Destination: &cfg.Reva.StoragePublicLink.Endpoint, - }, - - // groups - { - EnvVars: []string{"STORAGE_GROUPPROVIDER_DEBUG_ADDR"}, - Destination: &cfg.Reva.Groups.DebugAddr, - }, - { - EnvVars: []string{"STORAGE_GROUPPROVIDER_NETWORK"}, - Destination: &cfg.Reva.Groups.GRPCNetwork, - }, - { - EnvVars: []string{"STORAGE_GROUPPROVIDER_ADDR"}, - Destination: &cfg.Reva.Groups.GRPCAddr, - }, - { - EnvVars: []string{"STORAGE_GROUPPROVIDER_DRIVER"}, - Destination: &cfg.Reva.Groups.Driver, - }, - { - EnvVars: []string{"STORAGE_GROUPPROVIDER_JSON"}, - Destination: &cfg.Reva.Groups.JSON, - }, - { - EnvVars: []string{"STORAGE_GROUP_CACHE_EXPIRATION"}, - Destination: &cfg.Reva.Groups.GroupMembersCacheExpiration, - }, - - // ldap - { - EnvVars: []string{"LDAP_URI", "STORAGE_LDAP_URI"}, - Destination: &cfg.Reva.LDAP.URI, - }, - { - EnvVars: []string{"LDAP_CACERT", "STORAGE_LDAP_CACERT"}, - Destination: &cfg.Reva.LDAP.CACert, - }, - { - EnvVars: []string{"LDAP_INSECURE", "STORAGE_LDAP_INSECURE"}, - Destination: &cfg.Reva.LDAP.Insecure, - }, - { - EnvVars: []string{"LDAP_USER_BASE_DN", "STORAGE_LDAP_USER_BASE_DN"}, - Destination: &cfg.Reva.LDAP.UserBaseDN, - }, - { - EnvVars: []string{"LDAP_GROUP_BASE_DN", "STORAGE_LDAP_GROUP_BASE_DN"}, - Destination: &cfg.Reva.LDAP.GroupBaseDN, - }, - { - EnvVars: []string{"LDAP_USER_SCOPE", "STORAGE_LDAP_USER_SCOPE"}, - Destination: &cfg.Reva.LDAP.UserScope, - }, - { - EnvVars: []string{"LDAP_GROUP_SCOPE", "STORAGE_LDAP_GROUP_SCOPE"}, - Destination: &cfg.Reva.LDAP.GroupScope, - }, - { - EnvVars: []string{"LDAP_USER_OBJECTCLASS", "STORAGE_LDAP_USER_OBJECTCLASS"}, - Destination: &cfg.Reva.LDAP.UserObjectClass, - }, - { - EnvVars: []string{"LDAP_GROUP_OBJECTCLASS", "STORAGE_LDAP_GROUP_OBJECTCLASS"}, - Destination: &cfg.Reva.LDAP.GroupObjectClass, - }, - { - EnvVars: []string{"LDAP_LOGIN_ATTRIBUTES", "STORAGE_LDAP_LOGIN_ATTRIBUTES"}, - Destination: &cfg.Reva.LDAP.LoginAttributes, - }, - { - EnvVars: []string{"LDAP_USERFILTER", "STORAGE_LDAP_USERFILTER"}, - Destination: &cfg.Reva.LDAP.UserFilter, - }, - { - EnvVars: []string{"LDAP_GROUPFILTER", "STORAGE_LDAP_GROUPFILTER"}, - Destination: &cfg.Reva.LDAP.GroupFilter, - }, - { - EnvVars: []string{"LDAP_BIND_DN", "STORAGE_LDAP_BIND_DN"}, - Destination: &cfg.Reva.LDAP.BindDN, - }, - { - EnvVars: []string{"LDAP_BIND_PASSWORD", "STORAGE_LDAP_BIND_PASSWORD"}, - Destination: &cfg.Reva.LDAP.BindPassword, - }, - { - EnvVars: []string{"LDAP_USER_SCHEMA_ID", "STORAGE_LDAP_USER_SCHEMA_ID"}, - Destination: &cfg.Reva.LDAP.UserSchema.ID, - }, - { - EnvVars: []string{"LDAP_USER_SCHEMA_ID_IS_OCTETSTRING", "STORAGE_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING"}, - Destination: &cfg.Reva.LDAP.UserSchema.IDIsOctetString, - }, - { - EnvVars: []string{"LDAP_USER_SCHEMA_MAIL", "STORAGE_LDAP_USER_SCHEMA_MAIL"}, - Destination: &cfg.Reva.LDAP.UserSchema.Mail, - }, - { - EnvVars: []string{"LDAP_USER_SCHEMA_DISPLAYNAME", "STORAGE_LDAP_USER_SCHEMA_DISPLAYNAME"}, - Destination: &cfg.Reva.LDAP.UserSchema.DisplayName, - }, - { - EnvVars: []string{"LDAP_USER_SCHEMA_USERNAME", "STORAGE_LDAP_USER_SCHEMA_USERNAME"}, - Destination: &cfg.Reva.LDAP.UserSchema.Username, - }, - { - EnvVars: []string{"LDAP_USER_SCHEMA_UID_NUMBER", "STORAGE_LDAP_USER_SCHEMA_UID_NUMBER"}, - Destination: &cfg.Reva.LDAP.UserSchema.UIDNumber, - }, - { - EnvVars: []string{"LDAP_USER_SCHEMA_GID_NUMBER", "STORAGE_LDAP_USER_SCHEMA_GID_NUMBER"}, - Destination: &cfg.Reva.LDAP.UserSchema.GIDNumber, - }, - { - EnvVars: []string{"LDAP_GROUP_SCHEMA_ID", "STORAGE_LDAP_GROUP_SCHEMA_ID"}, - Destination: &cfg.Reva.LDAP.GroupSchema.ID, - }, - { - EnvVars: []string{"LDAP_GROUP_SCHEMA_ID_IS_OCTETSTRING", "STORAGE_LDAP_GROUP_SCHEMA_ID_IS_OCTETSTRING"}, - Destination: &cfg.Reva.LDAP.GroupSchema.IDIsOctetString, - }, - { - EnvVars: []string{"LDAP_GROUP_SCHEMA_MAIL", "STORAGE_LDAP_GROUP_SCHEMA_MAIL"}, - Destination: &cfg.Reva.LDAP.GroupSchema.Mail, - }, - { - EnvVars: []string{"LDAP_GROUP_SCHEMA_DISPLAYNAME", "STORAGE_LDAP_GROUP_SCHEMA_DISPLAYNAME"}, - Destination: &cfg.Reva.LDAP.GroupSchema.DisplayName, - }, - { - EnvVars: []string{"LDAP_GROUP_SCHEMA_GROUPNAME", "STORAGE_LDAP_GROUP_SCHEMA_GROUPNAME"}, - Destination: &cfg.Reva.LDAP.GroupSchema.Groupname, - }, - { - EnvVars: []string{"LDAP_GROUP_SCHEMA_MEMBER", "STORAGE_LDAP_GROUP_SCHEMA_MEMBER"}, - Destination: &cfg.Reva.LDAP.GroupSchema.Member, - }, - { - EnvVars: []string{"LDAP_GROUP_SCHEMA_GID_NUMBER", "STORAGE_LDAP_GROUP_SCHEMA_GID_NUMBER"}, - Destination: &cfg.Reva.LDAP.GroupSchema.GIDNumber, - }, - - // rest - { - EnvVars: []string{"STORAGE_REST_CLIENT_ID"}, - Destination: &cfg.Reva.UserGroupRest.ClientID, - }, - { - EnvVars: []string{"STORAGE_REST_CLIENT_SECRET"}, - Destination: &cfg.Reva.UserGroupRest.ClientSecret, - }, - { - EnvVars: []string{"STORAGE_REST_REDIS_ADDRESS"}, - Destination: &cfg.Reva.UserGroupRest.RedisAddress, - }, - { - EnvVars: []string{"STORAGE_REST_REDIS_USERNAME"}, - Destination: &cfg.Reva.UserGroupRest.RedisUsername, - }, - { - EnvVars: []string{"STORAGE_REST_REDIS_PASSWORD"}, - Destination: &cfg.Reva.UserGroupRest.RedisPassword, - }, - { - EnvVars: []string{"STORAGE_REST_ID_PROVIDER"}, - Destination: &cfg.Reva.UserGroupRest.IDProvider, - }, - { - EnvVars: []string{"STORAGE_REST_API_BASE_URL"}, - Destination: &cfg.Reva.UserGroupRest.APIBaseURL, - }, - { - EnvVars: []string{"STORAGE_REST_OIDC_TOKEN_ENDPOINT"}, - Destination: &cfg.Reva.UserGroupRest.OIDCTokenEndpoint, - }, - { - EnvVars: []string{"STORAGE_REST_TARGET_API"}, - Destination: &cfg.Reva.UserGroupRest.TargetAPI, - }, - - // secret - { - EnvVars: []string{"OCIS_JWT_SECRET", "STORAGE_JWT_SECRET"}, - Destination: &cfg.Reva.JWTSecret, - }, - { - EnvVars: []string{"STORAGE_SKIP_USER_GROUPS_IN_TOKEN"}, - Destination: &cfg.Reva.SkipUserGroupsInToken, - }, - - // sharing - { - EnvVars: []string{"STORAGE_SHARING_DEBUG_ADDR"}, - Destination: &cfg.Reva.Sharing.DebugAddr, - }, - { - EnvVars: []string{"STORAGE_SHARING_GRPC_NETWORK"}, - Destination: &cfg.Reva.Sharing.GRPCNetwork, - }, - { - EnvVars: []string{"STORAGE_SHARING_GRPC_ADDR"}, - Destination: &cfg.Reva.Sharing.GRPCAddr, - }, - { - EnvVars: []string{"STORAGE_SHARING_USER_DRIVER"}, - Destination: &cfg.Reva.Sharing.UserDriver, - }, - { - EnvVars: []string{"STORAGE_SHARING_PUBLIC_DRIVER"}, - Destination: &cfg.Reva.Sharing.PublicDriver, - }, - { - EnvVars: []string{"STORAGE_SHARING_PUBLIC_JSON_FILE"}, - Destination: &cfg.Reva.Sharing.PublicJSONFile, - }, - { - EnvVars: []string{"STORAGE_SHARING_PUBLIC_PASSWORD_HASH_COST"}, - Destination: &cfg.Reva.Sharing.PublicPasswordHashCost, - }, - { - EnvVars: []string{"STORAGE_SHARING_PUBLIC_ENABLE_EXPIRED_SHARES_CLEANUP"}, - Destination: &cfg.Reva.Sharing.PublicEnableExpiredSharesCleanup, - }, - { - EnvVars: []string{"STORAGE_SHARING_PUBLIC_JANITOR_RUN_INTERVAL"}, - Destination: &cfg.Reva.Sharing.PublicJanitorRunInterval, - }, - - // sharing cs3 - - { - EnvVars: []string{"STORAGE_SHARING_CS3_PROVIDER_ADDR"}, - Destination: &cfg.Reva.Sharing.CS3ProviderAddr, - }, - { - EnvVars: []string{"STORAGE_SHARING_CS3_SERVICE_USER"}, - Destination: &cfg.Reva.Sharing.CS3ServiceUser, - }, - { - EnvVars: []string{"OCIS_URL", "STORAGE_SHARING_CS3_SERVICE_USER_IDP"}, - Destination: &cfg.Reva.Sharing.CS3ServiceUserIdp, - }, - - // sharingsql - { - EnvVars: []string{"STORAGE_SHARING_USER_SQL_USERNAME"}, - Destination: &cfg.Reva.Sharing.UserSQLUsername, - }, - { - EnvVars: []string{"STORAGE_SHARING_USER_SQL_PASSWORD"}, - Destination: &cfg.Reva.Sharing.UserSQLPassword, - }, - { - EnvVars: []string{"STORAGE_SHARING_USER_SQL_HOST"}, - Destination: &cfg.Reva.Sharing.UserSQLHost, - }, - { - EnvVars: []string{"STORAGE_SHARING_USER_SQL_PORT"}, - Destination: &cfg.Reva.Sharing.UserSQLPort, - }, - { - EnvVars: []string{"STORAGE_SHARING_USER_SQL_NAME"}, - Destination: &cfg.Reva.Sharing.UserSQLName, - }, - { - EnvVars: []string{"STORAGE_SHARING_EVENTS_ADDRESS"}, - Destination: &cfg.Reva.Sharing.Events.Address, - }, - { - EnvVars: []string{"STORAGE_SHARING_EVENTS_CLUSTER_ID"}, - Destination: &cfg.Reva.Sharing.Events.ClusterID, - }, - - // storage metadata - { - EnvVars: []string{"STORAGE_METADATA_DEBUG_ADDR"}, - Destination: &cfg.Reva.StorageMetadata.DebugAddr, - }, - { - EnvVars: []string{"STORAGE_METADATA_GRPC_NETWORK"}, - Destination: &cfg.Reva.StorageMetadata.GRPCNetwork, - }, - { - EnvVars: []string{"STORAGE_METADATA_GRPC_ADDR"}, - Destination: &cfg.Reva.StorageMetadata.GRPCAddr, - }, - { - EnvVars: []string{"STORAGE_METADATA_DATA_SERVER_URL"}, - Destination: &cfg.Reva.StorageMetadata.DataServerURL, - }, - { - EnvVars: []string{"STORAGE_METADATA_HTTP_NETWORK"}, - Destination: &cfg.Reva.StorageMetadata.HTTPNetwork, - }, - { - EnvVars: []string{"STORAGE_METADATA_HTTP_ADDR"}, - Destination: &cfg.Reva.StorageMetadata.HTTPAddr, - }, - { - EnvVars: []string{"STORAGE_METADATA_TMP_FOLDER"}, - Destination: &cfg.Reva.StorageMetadata.TempFolder, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER"}, - Destination: &cfg.Reva.StorageMetadata.Driver, - }, - - // storage public link - { - EnvVars: []string{"STORAGE_PUBLIC_LINK_DEBUG_ADDR"}, - Destination: &cfg.Reva.StoragePublicLink.DebugAddr, - }, - { - EnvVars: []string{"STORAGE_PUBLIC_LINK_GRPC_NETWORK"}, - Destination: &cfg.Reva.StoragePublicLink.GRPCNetwork, - }, - { - EnvVars: []string{"STORAGE_PUBLIC_LINK_GRPC_ADDR"}, - Destination: &cfg.Reva.StoragePublicLink.GRPCAddr, - }, - - // storage users - { - EnvVars: []string{"STORAGE_USERS_DEBUG_ADDR"}, - Destination: &cfg.Reva.StorageUsers.DebugAddr, - }, - { - EnvVars: []string{"STORAGE_USERS_GRPC_NETWORK"}, - Destination: &cfg.Reva.StorageUsers.GRPCNetwork, - }, - { - EnvVars: []string{"STORAGE_USERS_GRPC_ADDR"}, - Destination: &cfg.Reva.StorageUsers.GRPCAddr, - }, - { - EnvVars: []string{"STORAGE_USERS_HTTP_NETWORK"}, - Destination: &cfg.Reva.StorageUsers.HTTPNetwork, - }, - { - EnvVars: []string{"STORAGE_USERS_HTTP_ADDR"}, - Destination: &cfg.Reva.StorageUsers.HTTPAddr, - }, - { - EnvVars: []string{"OCIS_STORAGE_READ_ONLY", "STORAGE_USERS_READ_ONLY"}, - Destination: &cfg.Reva.StorageUsers.ReadOnly, - }, - { - EnvVars: []string{"STORAGE_USERS_EXPOSE_DATA_SERVER"}, - Destination: &cfg.Reva.StorageUsers.ExposeDataServer, - }, - { - EnvVars: []string{"STORAGE_USERS_DATA_SERVER_URL"}, - Destination: &cfg.Reva.StorageUsers.DataServerURL, - }, - { - EnvVars: []string{"STORAGE_USERS_HTTP_PREFIX"}, - Destination: &cfg.Reva.StorageUsers.HTTPPrefix, - }, - { - EnvVars: []string{"STORAGE_USERS_TMP_FOLDER"}, - Destination: &cfg.Reva.StorageUsers.TempFolder, - }, - - // storage shares - { - EnvVars: []string{"STORAGE_SHARES_DEBUG_ADDR"}, - Destination: &cfg.Reva.StorageShares.DebugAddr, - }, - { - EnvVars: []string{"STORAGE_SHARES_GRPC_NETWORK"}, - Destination: &cfg.Reva.StorageShares.GRPCNetwork, - }, - { - EnvVars: []string{"STORAGE_SHARES_GRPC_ADDR"}, - Destination: &cfg.Reva.StorageShares.GRPCAddr, - }, - { - EnvVars: []string{"STORAGE_SHARES_HTTP_NETWORK"}, - Destination: &cfg.Reva.StorageShares.HTTPNetwork, - }, - { - EnvVars: []string{"STORAGE_SHARES_HTTP_ADDR"}, - Destination: &cfg.Reva.StorageShares.HTTPAddr, - }, - { - EnvVars: []string{"OCIS_STORAGE_READ_ONLY", "STORAGE_SHARES_READ_ONLY"}, - Destination: &cfg.Reva.StorageShares.ReadOnly, - }, - - // tracing - { - EnvVars: []string{"OCIS_TRACING_ENABLED", "STORAGE_TRACING_ENABLED"}, - Destination: &cfg.Tracing.Enabled, - }, - { - EnvVars: []string{"OCIS_TRACING_TYPE", "STORAGE_TRACING_TYPE"}, - Destination: &cfg.Tracing.Type, - }, - { - EnvVars: []string{"OCIS_TRACING_ENDPOINT", "STORAGE_TRACING_ENDPOINT"}, - Destination: &cfg.Tracing.Endpoint, - }, - { - EnvVars: []string{"OCIS_TRACING_COLLECTOR", "STORAGE_TRACING_COLLECTOR"}, - Destination: &cfg.Tracing.Collector, - }, - { - EnvVars: []string{"STORAGE_TRACING_SERVICE"}, - Destination: &cfg.Tracing.Service, - }, - - // users - { - EnvVars: []string{"STORAGE_USERPROVIDER_DEBUG_ADDR"}, - Destination: &cfg.Reva.Users.DebugAddr, - }, - { - EnvVars: []string{"STORAGE_USERPROVIDER_NETWORK"}, - Destination: &cfg.Reva.Users.GRPCNetwork, - }, - { - EnvVars: []string{"STORAGE_USERPROVIDER_ADDR"}, - Destination: &cfg.Reva.Users.GRPCAddr, - }, - { - EnvVars: []string{"STORAGE_USERPROVIDER_DRIVER"}, - Destination: &cfg.Reva.Users.Driver, - }, - { - EnvVars: []string{"STORAGE_USERPROVIDER_JSON"}, - Destination: &cfg.Reva.Users.JSON, - }, - { - EnvVars: []string{"STORAGE_USER_CACHE_EXPIRATION"}, - Destination: &cfg.Reva.Users.UserGroupsCacheExpiration, - }, - { - EnvVars: []string{"STORAGE_USERPROVIDER_OWNCLOUDSQL_DBHOST"}, - Destination: &cfg.Reva.UserOwnCloudSQL.DBHost, - }, - { - EnvVars: []string{"STORAGE_USERPROVIDER_OWNCLOUDSQL_DBPORT"}, - Destination: &cfg.Reva.UserOwnCloudSQL.DBPort, - }, - { - EnvVars: []string{"STORAGE_USERPROVIDER_OWNCLOUDSQL_DBNAME"}, - Destination: &cfg.Reva.UserOwnCloudSQL.DBName, - }, - { - EnvVars: []string{"STORAGE_USERPROVIDER_OWNCLOUDSQL_DBUSER"}, - Destination: &cfg.Reva.UserOwnCloudSQL.DBUsername, - }, - { - EnvVars: []string{"STORAGE_USERPROVIDER_OWNCLOUDSQL_DBPASS"}, - Destination: &cfg.Reva.UserOwnCloudSQL.DBPassword, - }, - { - EnvVars: []string{"STORAGE_USERPROVIDER_OWNCLOUDSQL_NOBODY"}, - Destination: &cfg.Reva.UserOwnCloudSQL.Nobody, - }, - { - EnvVars: []string{"STORAGE_USERPROVIDER_OWNCLOUDSQL_JOIN_USERNAME"}, - Destination: &cfg.Reva.UserOwnCloudSQL.JoinUsername, - }, - { - EnvVars: []string{"STORAGE_USERPROVIDER_OWNCLOUDSQL_JOIN_OWNCLOUDUUID"}, - Destination: &cfg.Reva.UserOwnCloudSQL.JoinOwnCloudUUID, - }, - { - EnvVars: []string{"STORAGE_USERPROVIDER_OWNCLOUDSQL_ENABLE_MEDIAL_SEARCH"}, - Destination: &cfg.Reva.UserOwnCloudSQL.EnableMedialSearch, - }, - - // driver eos - { - EnvVars: []string{"STORAGE_USERS_DRIVER_EOS_NAMESPACE"}, - Destination: &cfg.Reva.UserStorage.EOS.Root, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_EOS_SHADOW_NAMESPACE"}, - Destination: &cfg.Reva.UserStorage.EOS.ShadowNamespace, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_EOS_UPLOADS_NAMESPACE"}, - Destination: &cfg.Reva.UserStorage.EOS.UploadsNamespace, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_EOS_SHARE_FOLDER"}, - Destination: &cfg.Reva.UserStorage.EOS.ShareFolder, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_EOS_BINARY"}, - Destination: &cfg.Reva.UserStorage.EOS.EosBinary, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_EOS_XRDCOPY_BINARY"}, - Destination: &cfg.Reva.UserStorage.EOS.XrdcopyBinary, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_EOS_MASTER_URL"}, - Destination: &cfg.Reva.UserStorage.EOS.MasterURL, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_EOS_SLAVE_URL"}, - Destination: &cfg.Reva.UserStorage.EOS.SlaveURL, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_EOS_CACHE_DIRECTORY"}, - Destination: &cfg.Reva.UserStorage.EOS.CacheDirectory, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_EOS_ENABLE_LOGGING"}, - Destination: &cfg.Reva.UserStorage.EOS.EnableLogging, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_EOS_SHOW_HIDDEN_SYSFILES"}, - Destination: &cfg.Reva.UserStorage.EOS.ShowHiddenSysFiles, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_EOS_FORCE_SINGLEUSER_MODE"}, - Destination: &cfg.Reva.UserStorage.EOS.ForceSingleUserMode, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_EOS_USE_KEYTAB"}, - Destination: &cfg.Reva.UserStorage.EOS.UseKeytab, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_EOS_SEC_PROTOCOL"}, - Destination: &cfg.Reva.UserStorage.EOS.SecProtocol, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_EOS_KEYTAB"}, - Destination: &cfg.Reva.UserStorage.EOS.Keytab, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_EOS_SINGLE_USERNAME"}, - Destination: &cfg.Reva.UserStorage.EOS.SingleUsername, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_EOS_LAYOUT"}, - Destination: &cfg.Reva.UserStorage.EOS.UserLayout, - }, - { - EnvVars: []string{"REVA_GATEWAY"}, - Destination: &cfg.Reva.UserStorage.EOS.GatewaySVC, - }, - - // driver local - { - EnvVars: []string{"STORAGE_USERS_DRIVER_LOCAL_SHARE_FOLDER"}, - Destination: &cfg.Reva.UserStorage.Local.ShareFolder, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_LOCAL_USER_LAYOUT"}, - Destination: &cfg.Reva.UserStorage.Local.UserLayout, - }, - - // driver ocis - { - EnvVars: []string{"STORAGE_USERS_DRIVER_OCIS_LAYOUT"}, - Destination: &cfg.Reva.UserStorage.OCIS.UserLayout, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_OCIS_SHARE_FOLDER"}, - Destination: &cfg.Reva.UserStorage.OCIS.ShareFolder, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_OCIS_PERSONAL_SPACE_ALIAS_TEMPLATE"}, - Destination: &cfg.Reva.UserStorage.OCIS.PersonalSpaceAliasTemplate, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_OCIS_GENERAL_SPACE_ALIAS_TEMPLATE"}, - Destination: &cfg.Reva.UserStorage.OCIS.GeneralSpaceAliasTemplate, - }, - // driver owncloud sql - { - EnvVars: []string{"STORAGE_USERS_DRIVER_OWNCLOUDSQL_DATADIR"}, - Destination: &cfg.Reva.UserStorage.OwnCloudSQL.Root, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_OWNCLOUDSQL_UPLOADINFO_DIR"}, - Destination: &cfg.Reva.UserStorage.OwnCloudSQL.UploadInfoDir, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_OWNCLOUDSQL_SHARE_FOLDER"}, - Destination: &cfg.Reva.UserStorage.OwnCloudSQL.ShareFolder, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_OWNCLOUDSQL_LAYOUT"}, - Destination: &cfg.Reva.UserStorage.OwnCloudSQL.UserLayout, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_OWNCLOUDSQL_DBUSERNAME"}, - Destination: &cfg.Reva.UserStorage.OwnCloudSQL.DBUsername, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_OWNCLOUDSQL_DBPASSWORD"}, - Destination: &cfg.Reva.UserStorage.OwnCloudSQL.DBPassword, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_OWNCLOUDSQL_DBHOST"}, - Destination: &cfg.Reva.UserStorage.OwnCloudSQL.DBHost, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_OWNCLOUDSQL_DBPORT"}, - Destination: &cfg.Reva.UserStorage.OwnCloudSQL.DBPort, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_OWNCLOUDSQL_DBNAME"}, - Destination: &cfg.Reva.UserStorage.OwnCloudSQL.DBName, - }, - - // driver s3 - { - EnvVars: []string{"STORAGE_USERS_DRIVER_S3_REGION"}, - Destination: &cfg.Reva.UserStorage.S3.Region, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_S3_ACCESS_KEY"}, - Destination: &cfg.Reva.UserStorage.S3.AccessKey, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_S3_SECRET_KEY"}, - Destination: &cfg.Reva.UserStorage.S3.SecretKey, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_S3_ENDPOINT"}, - Destination: &cfg.Reva.UserStorage.S3.Endpoint, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_S3_BUCKET"}, - Destination: &cfg.Reva.UserStorage.S3.Bucket, - }, - - // driver s3ng - { - EnvVars: []string{"STORAGE_USERS_DRIVER_S3NG_ROOT"}, - Destination: &cfg.Reva.UserStorage.S3NG.Root, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_S3NG_LAYOUT"}, - Destination: &cfg.Reva.UserStorage.S3NG.UserLayout, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_S3NG_SHARE_FOLDER"}, - Destination: &cfg.Reva.UserStorage.S3NG.ShareFolder, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_S3NG_PERSONAL_SPACE_ALIAS_TEMPLATE"}, - Destination: &cfg.Reva.UserStorage.S3NG.PersonalSpaceAliasTemplate, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_S3NG_GENERAL_SPACE_ALIAS_TEMPLATE"}, - Destination: &cfg.Reva.UserStorage.S3NG.GeneralSpaceAliasTemplate, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_S3NG_REGION"}, - Destination: &cfg.Reva.UserStorage.S3NG.Region, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_S3NG_ACCESS_KEY"}, - Destination: &cfg.Reva.UserStorage.S3NG.AccessKey, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_S3NG_SECRET_KEY"}, - Destination: &cfg.Reva.UserStorage.S3NG.SecretKey, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_S3NG_ENDPOINT"}, - Destination: &cfg.Reva.UserStorage.S3NG.Endpoint, - }, - { - EnvVars: []string{"STORAGE_USERS_DRIVER_S3NG_BUCKET"}, - Destination: &cfg.Reva.UserStorage.S3NG.Bucket, - }, - - // metadata driver eos - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_EOS_NAMESPACE"}, - Destination: &cfg.Reva.MetadataStorage.EOS.Root, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_EOS_SHADOW_NAMESPACE"}, - Destination: &cfg.Reva.MetadataStorage.EOS.ShadowNamespace, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_EOS_UPLOADS_NAMESPACE"}, - Destination: &cfg.Reva.MetadataStorage.EOS.UploadsNamespace, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_EOS_SHARE_FOLDER"}, - Destination: &cfg.Reva.MetadataStorage.EOS.ShareFolder, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_EOS_BINARY"}, - Destination: &cfg.Reva.MetadataStorage.EOS.EosBinary, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_EOS_XRDCOPY_BINARY"}, - Destination: &cfg.Reva.MetadataStorage.EOS.XrdcopyBinary, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_EOS_MASTER_URL"}, - Destination: &cfg.Reva.MetadataStorage.EOS.MasterURL, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_EOS_SLAVE_URL"}, - Destination: &cfg.Reva.MetadataStorage.EOS.SlaveURL, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_EOS_CACHE_DIRECTORY"}, - Destination: &cfg.Reva.MetadataStorage.EOS.CacheDirectory, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_EOS_ENABLE_LOGGING"}, - Destination: &cfg.Reva.MetadataStorage.EOS.EnableLogging, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_EOS_SHOW_HIDDEN_SYSFILES"}, - Destination: &cfg.Reva.MetadataStorage.EOS.ShowHiddenSysFiles, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_EOS_FORCE_SINGLEUSER_MODE"}, - Destination: &cfg.Reva.MetadataStorage.EOS.ForceSingleUserMode, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_EOS_USE_KEYTAB"}, - Destination: &cfg.Reva.MetadataStorage.EOS.UseKeytab, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_EOS_SEC_PROTOCOL"}, - Destination: &cfg.Reva.MetadataStorage.EOS.SecProtocol, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_EOS_KEYTAB"}, - Destination: &cfg.Reva.MetadataStorage.EOS.Keytab, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_EOS_SINGLE_USERNAME"}, - Destination: &cfg.Reva.MetadataStorage.EOS.SingleUsername, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_EOS_LAYOUT"}, - Destination: &cfg.Reva.MetadataStorage.EOS.UserLayout, - }, - { - EnvVars: []string{"REVA_GATEWAY"}, - Destination: &cfg.Reva.MetadataStorage.EOS.GatewaySVC, - }, - - // metadata local driver - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_LOCAL_ROOT"}, - Destination: &cfg.Reva.MetadataStorage.Local.Root, - }, - - // metadata ocis driver - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_OCIS_LAYOUT"}, - Destination: &cfg.Reva.MetadataStorage.OCIS.UserLayout, - }, - - // metadata driver s3 - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_S3_REGION"}, - Destination: &cfg.Reva.MetadataStorage.S3.Region, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_S3_ACCESS_KEY"}, - Destination: &cfg.Reva.MetadataStorage.S3.AccessKey, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_S3_SECRET_KEY"}, - Destination: &cfg.Reva.MetadataStorage.S3.SecretKey, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_S3_ENDPOINT"}, - Destination: &cfg.Reva.MetadataStorage.S3.Endpoint, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_S3_BUCKET"}, - Destination: &cfg.Reva.MetadataStorage.S3.Bucket, - }, - - // driver s3ng - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_S3NG_ROOT"}, - Destination: &cfg.Reva.MetadataStorage.S3NG.Root, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_S3NG_LAYOUT"}, - Destination: &cfg.Reva.MetadataStorage.S3NG.UserLayout, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_S3NG_REGION"}, - Destination: &cfg.Reva.MetadataStorage.S3NG.Region, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_S3NG_ACCESS_KEY"}, - Destination: &cfg.Reva.MetadataStorage.S3NG.AccessKey, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_S3NG_SECRET_KEY"}, - Destination: &cfg.Reva.MetadataStorage.S3NG.SecretKey, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_S3NG_ENDPOINT"}, - Destination: &cfg.Reva.MetadataStorage.S3NG.Endpoint, - }, - { - EnvVars: []string{"STORAGE_METADATA_DRIVER_S3NG_BUCKET"}, - Destination: &cfg.Reva.MetadataStorage.S3NG.Bucket, - }, - - // permissions - { - EnvVars: []string{"STORAGE_PERMISSIONS_ENDPOINT"}, - Destination: &cfg.Reva.Permissions.Endpoint, - }, - - // ocdav - { - EnvVars: []string{"OCDAV_ADDR"}, - Destination: &cfg.OCDav.Addr, - }, - { - EnvVars: []string{"OCDAV_DEBUG_ADDR"}, - Destination: &cfg.OCDav.DebugAddr, - }, - { - EnvVars: []string{"OCDAV_PREFIX"}, - Destination: &cfg.OCDav.Prefix, - }, - { - EnvVars: []string{"OCDAV_WEBDAV_NAMESPACE"}, - Destination: &cfg.OCDav.WebdavNamespace, - }, - { - EnvVars: []string{"OCDAV_FILES_NAMESPACE"}, - Destination: &cfg.OCDav.FilesNamespace, - }, - { - EnvVars: []string{"OCDAV_SHARES_NAMESPACE"}, - Destination: &cfg.OCDav.SharesNamespace, - }, - { - EnvVars: []string{"OCIS_URL", "OCDAV_PUBLIC_URL"}, - Destination: &cfg.OCDav.PublicURL, - }, - { - EnvVars: []string{"OCIS_INSECURE", "OCDAV_INSECURE"}, - Destination: &cfg.OCDav.Insecure, - }, - { - EnvVars: []string{"OCIS_JWT_SECRET", "OCDAV_JWT_SECRET"}, - Destination: &cfg.OCDav.JWTSecret, - }, - } -} diff --git a/extensions/storage/pkg/config/defaults/defaultconfig.go b/extensions/storage/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index 0e2faf43df..0000000000 --- a/extensions/storage/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,459 +0,0 @@ -package defaults - -import ( - "os" - "path" - - "github.com/owncloud/ocis/extensions/storage/pkg/config" - "github.com/owncloud/ocis/ocis-pkg/config/defaults" -) - -const ( - defaultPublicURL = "https://localhost:9200" - defaultShareFolder = "/Shares" - defaultStorageNamespace = "/users/{{.Id.OpaqueId}}" - defaultGatewayAddr = "127.0.0.1:9142" - defaultUserLayout = "{{.Id.OpaqueId}}" - defaultPersonalSpaceAliasTemplate = "{{.SpaceType}}/{{.User.Username | lower}}" - defaultGeneralSpaceAliasTemplate = "{{.SpaceType}}/{{.SpaceName | replace \" \" \"-\" | lower}}" -) - -func FullDefaultConfig() *config.Config { - cfg := DefaultConfig() - EnsureDefaults(cfg) - Sanitize(cfg) - return cfg -} - -func DefaultConfig() *config.Config { - return &config.Config{ - // log is inherited - Debug: config.Debug{ - Addr: "127.0.0.1:9109", - }, - Reva: config.Reva{ - SkipUserGroupsInToken: false, - TransferExpires: 24 * 60 * 60, - OIDC: config.OIDC{ - Issuer: defaultPublicURL, - Insecure: false, - IDClaim: "preferred_username", - }, - LDAP: config.LDAP{ - URI: "ldaps://localhost:9235", - CACert: path.Join(defaults.BaseDataPath(), "idm", "ldap.crt"), - Insecure: false, - UserBaseDN: "ou=users,o=libregraph-idm", - GroupBaseDN: "ou=groups,o=libregraph-idm", - UserScope: "sub", - GroupScope: "sub", - LoginAttributes: []string{"uid", "mail"}, - UserFilter: "", - GroupFilter: "", - UserObjectClass: "inetOrgPerson", - GroupObjectClass: "groupOfNames", - BindDN: "uid=reva,ou=sysusers,o=libregraph-idm", - IDP: defaultPublicURL, - UserSchema: config.LDAPUserSchema{ - ID: "ownclouduuid", - Mail: "mail", - DisplayName: "displayname", - Username: "uid", - UIDNumber: "uidnumber", - GIDNumber: "gidnumber", - }, - GroupSchema: config.LDAPGroupSchema{ - ID: "ownclouduuid", - Mail: "mail", - DisplayName: "cn", - Groupname: "cn", - Member: "member", - GIDNumber: "gidnumber", - }, - }, - UserGroupRest: config.UserGroupRest{ - RedisAddress: "localhost:6379", - }, - UserOwnCloudSQL: config.UserOwnCloudSQL{ - DBUsername: "owncloud", - DBHost: "mysql", - DBPort: 3306, - DBName: "owncloud", - Idp: defaultPublicURL, - Nobody: 90, - JoinUsername: false, - JoinOwnCloudUUID: false, - EnableMedialSearch: false, - }, - Archiver: config.Archiver{ - MaxNumFiles: 10000, - MaxSize: 1073741824, - ArchiverURL: "/archiver", - }, - UserStorage: config.StorageConfig{ - EOS: config.DriverEOS{ - DriverCommon: config.DriverCommon{ - Root: "/eos/dockertest/reva", - ShareFolder: defaultShareFolder, - UserLayout: "{{substr 0 1 .Username}}/{{.Username}}", - }, - ShadowNamespace: "", // Defaults to path.Join(c.Namespace, ".shadow") - UploadsNamespace: "", // Defaults to path.Join(c.Namespace, ".uploads") - EosBinary: "/usr/bin/eos", - XrdcopyBinary: "/usr/bin/xrdcopy", - MasterURL: "root://eos-mgm1.eoscluster.cern.ch:1094", - SlaveURL: "root://eos-mgm1.eoscluster.cern.ch:1094", - CacheDirectory: os.TempDir(), - GatewaySVC: defaultGatewayAddr, - }, - Local: config.DriverCommon{ - Root: path.Join(defaults.BaseDataPath(), "storage", "local", "users"), - ShareFolder: defaultShareFolder, - UserLayout: "{{.Username}}", - EnableHome: false, - }, - OwnCloudSQL: config.DriverOwnCloudSQL{ - DriverCommon: config.DriverCommon{ - Root: path.Join(defaults.BaseDataPath(), "storage", "owncloud"), - ShareFolder: defaultShareFolder, - UserLayout: "{{.Username}}", - EnableHome: false, - }, - UploadInfoDir: path.Join(defaults.BaseDataPath(), "storage", "uploadinfo"), - DBUsername: "owncloud", - DBPassword: "owncloud", - DBHost: "", - DBPort: 3306, - DBName: "owncloud", - }, - S3: config.DriverS3{ - DriverCommon: config.DriverCommon{}, - Region: "default", - AccessKey: "", - SecretKey: "", - Endpoint: "", - Bucket: "", - }, - S3NG: config.DriverS3NG{ - DriverCommon: config.DriverCommon{ - Root: path.Join(defaults.BaseDataPath(), "storage", "users"), - ShareFolder: defaultShareFolder, - UserLayout: defaultUserLayout, - PersonalSpaceAliasTemplate: defaultPersonalSpaceAliasTemplate, - GeneralSpaceAliasTemplate: defaultGeneralSpaceAliasTemplate, - EnableHome: false, - }, - Region: "default", - AccessKey: "", - SecretKey: "", - Endpoint: "", - Bucket: "", - }, - OCIS: config.DriverOCIS{ - DriverCommon: config.DriverCommon{ - Root: path.Join(defaults.BaseDataPath(), "storage", "users"), - ShareFolder: defaultShareFolder, - UserLayout: defaultUserLayout, - PersonalSpaceAliasTemplate: defaultPersonalSpaceAliasTemplate, - GeneralSpaceAliasTemplate: defaultGeneralSpaceAliasTemplate, - }, - }, - }, - MetadataStorage: config.StorageConfig{ - EOS: config.DriverEOS{ - DriverCommon: config.DriverCommon{ - Root: "/eos/dockertest/reva", - ShareFolder: defaultShareFolder, - UserLayout: "{{substr 0 1 .Username}}/{{.Username}}", - EnableHome: false, - }, - ShadowNamespace: "", - UploadsNamespace: "", - EosBinary: "/usr/bin/eos", - XrdcopyBinary: "/usr/bin/xrdcopy", - MasterURL: "root://eos-mgm1.eoscluster.cern.ch:1094", - GrpcURI: "", - SlaveURL: "root://eos-mgm1.eoscluster.cern.ch:1094", - CacheDirectory: os.TempDir(), - EnableLogging: false, - ShowHiddenSysFiles: false, - ForceSingleUserMode: false, - UseKeytab: false, - SecProtocol: "", - Keytab: "", - SingleUsername: "", - GatewaySVC: defaultGatewayAddr, - }, - Local: config.DriverCommon{ - Root: path.Join(defaults.BaseDataPath(), "storage", "local", "metadata"), - }, - OwnCloudSQL: config.DriverOwnCloudSQL{}, - S3: config.DriverS3{ - DriverCommon: config.DriverCommon{}, - Region: "default", - }, - S3NG: config.DriverS3NG{ - DriverCommon: config.DriverCommon{ - Root: path.Join(defaults.BaseDataPath(), "storage", "metadata"), - ShareFolder: "", - UserLayout: defaultUserLayout, - EnableHome: false, - }, - Region: "default", - AccessKey: "", - SecretKey: "", - Endpoint: "", - Bucket: "", - }, - OCIS: config.DriverOCIS{ - DriverCommon: config.DriverCommon{ - Root: path.Join(defaults.BaseDataPath(), "storage", "metadata"), - ShareFolder: "", - UserLayout: defaultUserLayout, - EnableHome: false, - }, - }, - }, - Frontend: config.FrontendPort{ - Port: config.Port{ - MaxCPUs: "", - LogLevel: "", - GRPCNetwork: "", - GRPCAddr: "", - HTTPNetwork: "tcp", - HTTPAddr: "127.0.0.1:9140", - Protocol: "", - Endpoint: "", - DebugAddr: "127.0.0.1:9141", - Services: []string{"datagateway", "ocs", "appprovider"}, - Config: nil, - Context: nil, - Supervised: false, - }, - AppProviderInsecure: false, - AppProviderPrefix: "", - ArchiverInsecure: false, - ArchiverPrefix: "archiver", - DatagatewayPrefix: "data", - Favorites: false, - ProjectSpaces: true, - OCSPrefix: "ocs", - OCSSharePrefix: defaultShareFolder, - OCSHomeNamespace: defaultStorageNamespace, - PublicURL: defaultPublicURL, - OCSCacheWarmupDriver: "", - OCSAdditionalInfoAttribute: "{{.Mail}}", - OCSResourceInfoCacheTTL: 0, - Middleware: config.Middleware{}, - }, - DataGateway: config.DataGatewayPort{ - Port: config.Port{}, - PublicURL: "", - }, - Gateway: config.Gateway{ - Port: config.Port{ - Endpoint: defaultGatewayAddr, - DebugAddr: "127.0.0.1:9143", - GRPCNetwork: "tcp", - GRPCAddr: defaultGatewayAddr, - }, - CommitShareToStorageGrant: true, - CommitShareToStorageRef: true, - DisableHomeCreationOnLogin: true, - ShareFolder: "Shares", - LinkGrants: "", - HomeMapping: "", - EtagCacheTTL: 0, - }, - StorageRegistry: config.StorageRegistry{ - Driver: "spaces", - HomeProvider: "/home", // unused for spaces, static currently not supported - JSON: "", - }, - AppRegistry: config.AppRegistry{ - Driver: "static", - MimetypesJSON: "", - }, - Users: config.Users{ - Port: config.Port{ - Endpoint: "localhost:9144", - DebugAddr: "127.0.0.1:9145", - GRPCNetwork: "tcp", - GRPCAddr: "127.0.0.1:9144", - Services: []string{"userprovider"}, - }, - Driver: "ldap", - UserGroupsCacheExpiration: 5, - }, - Groups: config.Groups{ - Port: config.Port{ - Endpoint: "localhost:9160", - DebugAddr: "127.0.0.1:9161", - GRPCNetwork: "tcp", - GRPCAddr: "127.0.0.1:9160", - Services: []string{"groupprovider"}, - }, - Driver: "ldap", - GroupMembersCacheExpiration: 5, - }, - AuthProvider: config.Users{ - Port: config.Port{}, - Driver: "ldap", - UserGroupsCacheExpiration: 0, - }, - AuthBasic: config.Port{ - GRPCNetwork: "tcp", - GRPCAddr: "127.0.0.1:9146", - DebugAddr: "127.0.0.1:9147", - Services: []string{"authprovider"}, - Endpoint: "localhost:9146", - }, - AuthBearer: config.Port{ - GRPCNetwork: "tcp", - GRPCAddr: "127.0.0.1:9148", - DebugAddr: "127.0.0.1:9149", - Services: []string{"authprovider"}, - Endpoint: "localhost:9148", - }, - AuthMachine: config.Port{ - GRPCNetwork: "tcp", - GRPCAddr: "127.0.0.1:9166", - DebugAddr: "127.0.0.1:9167", - Services: []string{"authprovider"}, - Endpoint: "localhost:9166", - }, - AuthMachineConfig: config.AuthMachineConfig{}, - Sharing: config.Sharing{ - Port: config.Port{ - Endpoint: "localhost:9150", - DebugAddr: "127.0.0.1:9151", - GRPCNetwork: "tcp", - GRPCAddr: "127.0.0.1:9150", - Services: []string{"usershareprovider", "publicshareprovider"}, - }, - CS3ProviderAddr: "127.0.0.1:9215", // metadata storage - CS3ServiceUser: "95cb8724-03b2-11eb-a0a6-c33ef8ef53ad", - CS3ServiceUserIdp: "internal", - UserDriver: "json", - UserJSONFile: path.Join(defaults.BaseDataPath(), "storage", "shares.json"), - UserSQLUsername: "", - UserSQLPassword: "", - UserSQLHost: "", - UserSQLPort: 1433, - UserSQLName: "", - PublicDriver: "json", - PublicJSONFile: path.Join(defaults.BaseDataPath(), "storage", "publicshares.json"), - PublicPasswordHashCost: 11, - PublicEnableExpiredSharesCleanup: true, - PublicJanitorRunInterval: 60, - UserStorageMountID: "", - Events: config.Events{ - Address: "127.0.0.1:9233", - ClusterID: "ocis-cluster", - }, - }, - StorageShares: config.StoragePort{ - Port: config.Port{ - Endpoint: "localhost:9154", - DebugAddr: "127.0.0.1:9156", - GRPCNetwork: "tcp", - GRPCAddr: "127.0.0.1:9154", - HTTPNetwork: "tcp", - HTTPAddr: "127.0.0.1:9155", - }, - ReadOnly: false, - AlternativeID: "1284d238-aa92-42ce-bdc4-0b0000009154", - MountID: "1284d238-aa92-42ce-bdc4-0b0000009157", - }, - StorageUsers: config.StoragePort{ - Port: config.Port{ - Endpoint: "localhost:9157", - DebugAddr: "127.0.0.1:9159", - GRPCNetwork: "tcp", - GRPCAddr: "127.0.0.1:9157", - HTTPNetwork: "tcp", - HTTPAddr: "127.0.0.1:9158", - }, - MountID: "1284d238-aa92-42ce-bdc4-0b0000009157", - Driver: "ocis", - DataServerURL: "http://localhost:9158/data", - HTTPPrefix: "data", - TempFolder: path.Join(defaults.BaseDataPath(), "tmp", "users"), - }, - StoragePublicLink: config.PublicStorage{ - StoragePort: config.StoragePort{ - Port: config.Port{ - Endpoint: "localhost:9178", - DebugAddr: "127.0.0.1:9179", - GRPCNetwork: "tcp", - GRPCAddr: "127.0.0.1:9178", - }, - MountID: "7993447f-687f-490d-875c-ac95e89a62a4", - }, - PublicShareProviderAddr: "", - UserProviderAddr: "", - }, - StorageMetadata: config.StoragePort{ - Port: config.Port{ - GRPCNetwork: "tcp", - GRPCAddr: "127.0.0.1:9215", - HTTPNetwork: "tcp", - HTTPAddr: "127.0.0.1:9216", - DebugAddr: "127.0.0.1:9217", - }, - Driver: "ocis", - ExposeDataServer: false, - DataServerURL: "http://localhost:9216/data", - TempFolder: path.Join(defaults.BaseDataPath(), "tmp", "metadata"), - DataProvider: config.DataProvider{}, - }, - AppProvider: config.AppProvider{ - Port: config.Port{ - GRPCNetwork: "tcp", - GRPCAddr: "127.0.0.1:9164", - DebugAddr: "127.0.0.1:9165", - Endpoint: "localhost:9164", - Services: []string{"appprovider"}, - }, - ExternalAddr: "127.0.0.1:9164", - WopiDriver: config.WopiDriver{}, - AppsURL: "/app/list", - OpenURL: "/app/open", - NewURL: "/app/new", - }, - Permissions: config.Port{ - Endpoint: "localhost:9191", - }, - Configs: nil, - UploadMaxChunkSize: 1e+8, - UploadHTTPMethodOverride: "", - ChecksumSupportedTypes: []string{"sha1", "md5", "adler32"}, - ChecksumPreferredUploadType: "", - DefaultUploadProtocol: "tus", - }, - // TODO move ocdav config to a separate service - OCDav: config.OCDav{ - Addr: "127.0.0.1:0", // :0 to pick any local free port - DebugAddr: "127.0.0.1:9163", - WebdavNamespace: defaultStorageNamespace, - FilesNamespace: defaultStorageNamespace, - SharesNamespace: defaultShareFolder, - PublicURL: defaultPublicURL, - Prefix: "", - GatewaySVC: defaultGatewayAddr, - Insecure: false, // true? - Timeout: 84300, - }, - Tracing: config.Tracing{ - Service: "storage", - Type: "jaeger", - }, - Asset: config.Asset{}, - } -} - -func EnsureDefaults(cfg *config.Config) { -} - -func Sanitize(cfg *config.Config) { -} diff --git a/extensions/storage/pkg/tracing/tracing.go b/extensions/storage/pkg/tracing/tracing.go deleted file mode 100644 index acb788e21c..0000000000 --- a/extensions/storage/pkg/tracing/tracing.go +++ /dev/null @@ -1,38 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/extensions/storage/pkg/config" - "github.com/owncloud/ocis/ocis-pkg/log" -) - -// Configure for Reva serves only as informational / instructive log messages. Tracing config will be delegated directly -// to Reva services. -func Configure(cfg *config.Config, logger log.Logger) { - if cfg.Tracing.Enabled { - switch cfg.Tracing.Type { - case "agent": - logger.Error(). - Str("type", cfg.Tracing.Type). - Msg("Reva only supports the jaeger tracing backend") - - case "jaeger": - logger.Info(). - Str("type", cfg.Tracing.Type). - Msg("configuring storage to use the jaeger tracing backend") - - case "zipkin": - logger.Error(). - Str("type", cfg.Tracing.Type). - Msg("Reva only supports the jaeger tracing backend") - - default: - logger.Warn(). - Str("type", cfg.Tracing.Type). - Msg("Unknown tracing backend") - } - - } else { - logger.Debug(). - Msg("Tracing is not enabled") - } -} diff --git a/extensions/storage/reflex.conf b/extensions/storage/reflex.conf deleted file mode 100644 index 1035f881c5..0000000000 --- a/extensions/storage/reflex.conf +++ /dev/null @@ -1,5 +0,0 @@ -# backend --r '^(cmd|pkg)/.*\.go$' -R '^node_modules/' -s -- sh -c 'make bin/ocis-reva-debug && bin/ocis-reva-debug --log-level debug server --debug-pprof --debug-zpages --asset-path assets/' - -# frontend --r '^ui/.*\.(vue|js)$' -R '^node_modules/' -- sh -c 'yarn build' diff --git a/extensions/store/pkg/command/root.go b/extensions/store/pkg/command/root.go index 9dc06b0e74..4ab42c4465 100644 --- a/extensions/store/pkg/command/root.go +++ b/extensions/store/pkg/command/root.go @@ -28,7 +28,7 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the ocis-store command. func Execute(cfg *config.Config) error { app := clihelper.DefaultApp(&cli.App{ - Name: "ocis-store", + Name: "store", Usage: "Service to store values for ocis extensions", Commands: GetCommands(cfg), }) diff --git a/extensions/thumbnails/pkg/command/root.go b/extensions/thumbnails/pkg/command/root.go index 44d4948dbe..07fca1eae4 100644 --- a/extensions/thumbnails/pkg/command/root.go +++ b/extensions/thumbnails/pkg/command/root.go @@ -28,7 +28,7 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the ocis-thumbnails command. func Execute(cfg *config.Config) error { app := clihelper.DefaultApp(&cli.App{ - Name: "ocis-thumbnails", + Name: "thumbnails", Usage: "Example usage", Commands: GetCommands(cfg), }) diff --git a/extensions/thumbnails/pkg/service/grpc/v0/service.go b/extensions/thumbnails/pkg/service/grpc/v0/service.go index 22a3465c93..dc6faa31f8 100644 --- a/extensions/thumbnails/pkg/service/grpc/v0/service.go +++ b/extensions/thumbnails/pkg/service/grpc/v0/service.go @@ -12,6 +12,7 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" revactx "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/golang-jwt/jwt/v4" "github.com/owncloud/ocis/extensions/thumbnails/pkg/preprocessor" "github.com/owncloud/ocis/extensions/thumbnails/pkg/service/grpc/v0/decorators" @@ -257,15 +258,11 @@ func (g Thumbnail) stat(path, auth string) (*provider.StatResponse, error) { var ref *provider.Reference if strings.Contains(path, "!") { - parts := strings.Split(path, "!") - spaceID, path := parts[0], parts[1] - ref = &provider.Reference{ - ResourceId: &provider.ResourceId{ - StorageId: spaceID, - OpaqueId: spaceID, - }, - Path: path, + parsed, err := storagespace.ParseReference(path) + if err != nil { + return nil, err } + ref = &parsed } else { ref = &provider.Reference{ Path: path, diff --git a/extensions/thumbnails/pkg/thumbnail/imgsource/cs3.go b/extensions/thumbnails/pkg/thumbnail/imgsource/cs3.go index 9b0cdfa258..9b38b7ba6c 100644 --- a/extensions/thumbnails/pkg/thumbnail/imgsource/cs3.go +++ b/extensions/thumbnails/pkg/thumbnail/imgsource/cs3.go @@ -13,6 +13,7 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/rhttp" + "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/owncloud/ocis/extensions/thumbnails/pkg/config" "github.com/pkg/errors" "google.golang.org/grpc/metadata" @@ -45,16 +46,11 @@ func (s CS3) Get(ctx context.Context, path string) (io.ReadCloser, error) { } var ref *provider.Reference if strings.Contains(path, "!") { - parts := strings.Split(path, "!") - spaceID, path := parts[0], parts[1] - ref = &provider.Reference{ - ResourceId: &provider.ResourceId{ - StorageId: spaceID, - OpaqueId: spaceID, - }, - // Spaces requests need relative paths. - Path: "." + path, + parsed, err := storagespace.ParseReference(path) + if err != nil { + return nil, err } + ref = &parsed } else { ref = &provider.Reference{ Path: path, diff --git a/extensions/user/Makefile b/extensions/user/Makefile new file mode 100644 index 0000000000..c249998fc9 --- /dev/null +++ b/extensions/user/Makefile @@ -0,0 +1,37 @@ +SHELL := bash +NAME := user + +include ../../.make/recursion.mk + +############ tooling ############ +ifneq (, $(shell which go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI +include ../../.bingo/Variables.mk +endif + +############ go tooling ############ +include ../../.make/go.mk + +############ release ############ +include ../../.make/release.mk + +############ docs generate ############ +include ../../.make/docs.mk + +.PHONY: docs-generate +docs-generate: config-docs-generate + +############ generate ############ +include ../../.make/generate.mk + +.PHONY: ci-go-generate +ci-go-generate: # CI runs ci-node-generate automatically before this target + +.PHONY: ci-node-generate +ci-node-generate: + +############ licenses ############ +.PHONY: ci-node-check-licenses +ci-node-check-licenses: + +.PHONY: ci-node-save-licenses +ci-node-save-licenses: diff --git a/extensions/user/cmd/user/main.go b/extensions/user/cmd/user/main.go new file mode 100644 index 0000000000..97d008cb00 --- /dev/null +++ b/extensions/user/cmd/user/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/extensions/user/pkg/command" + "github.com/owncloud/ocis/extensions/user/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/user/pkg/command/command.go b/extensions/user/pkg/command/command.go deleted file mode 100644 index 27e7cabfab..0000000000 --- a/extensions/user/pkg/command/command.go +++ /dev/null @@ -1,245 +0,0 @@ -package command - -import ( - "context" - "flag" - "fmt" - "os" - "path" - "path/filepath" - - "github.com/cs3org/reva/v2/cmd/revad/runtime" - "github.com/gofrs/uuid" - "github.com/oklog/run" - "github.com/owncloud/ocis/extensions/storage/pkg/server/debug" - "github.com/owncloud/ocis/extensions/user/pkg/config" - "github.com/owncloud/ocis/extensions/user/pkg/config/parser" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/ldap" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/owncloud/ocis/ocis-pkg/tracing" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" -) - -// User is the entrypoint for the user command. -func User(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "users", - Usage: "start users service", - Before: func(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - } - return err - }, - Action: func(c *cli.Context) error { - logCfg := cfg.Logging - logger := log.NewLogger( - log.Level(logCfg.Level), - log.File(logCfg.File), - log.Pretty(logCfg.Pretty), - log.Color(logCfg.Color), - ) - tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // precreate folders - if cfg.Driver == "json" && cfg.Drivers.JSON.File != "" { - if err := os.MkdirAll(filepath.Dir(cfg.Drivers.JSON.File), os.FileMode(0700)); err != nil { - return err - } - } - - uuid := uuid.Must(uuid.NewV4()) - pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+uuid.String()+".pid") - - rcfg := usersConfigFromStruct(c, cfg) - - logger.Debug(). - Str("server", "users"). - Interface("reva-config", rcfg). - Msg("config") - - if cfg.Driver == "ldap" { - if err := ldap.WaitForCA(logger, cfg.Drivers.LDAP.Insecure, cfg.Drivers.LDAP.CACert); err != nil { - logger.Error().Err(err).Msg("The configured LDAP CA cert does not exist") - return err - } - } - - gr.Add(func() error { - runtime.RunWithOptions( - rcfg, - pidFile, - runtime.WithLogger(&logger.Logger), - ) - return nil - }, func(_ error) { - logger.Info(). - Str("server", c.Command.Name). - Msg("Shutting down server") - - cancel() - }) - - debugServer, err := debug.Server( - debug.Name(c.Command.Name+"-debug"), - debug.Addr(cfg.Debug.Addr), - debug.Logger(logger), - debug.Context(ctx), - debug.Pprof(cfg.Debug.Pprof), - debug.Zpages(cfg.Debug.Zpages), - debug.Token(cfg.Debug.Token), - ) - - if err != nil { - logger.Info().Err(err).Str("server", c.Command.Name+"-debug").Msg("Failed to initialize server") - return err - } - - gr.Add(debugServer.ListenAndServe, func(_ error) { - cancel() - }) - - if !cfg.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() - }, - } -} - -// usersConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func usersConfigFromStruct(c *cli.Context, cfg *config.Config) map[string]interface{} { - rcfg := map[string]interface{}{ - "core": map[string]interface{}{ - "tracing_enabled": cfg.Tracing.Enabled, - "tracing_endpoint": cfg.Tracing.Endpoint, - "tracing_collector": cfg.Tracing.Collector, - "tracing_service_name": c.Command.Name, - }, - "shared": map[string]interface{}{ - "jwt_secret": cfg.TokenManager.JWTSecret, - "gatewaysvc": cfg.Reva.Address, - "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, - }, - "grpc": map[string]interface{}{ - "network": cfg.GRPC.Protocol, - "address": cfg.GRPC.Addr, - // TODO build services dynamically - "services": map[string]interface{}{ - "userprovider": map[string]interface{}{ - "driver": cfg.Driver, - "drivers": map[string]interface{}{ - "json": map[string]interface{}{ - "users": cfg.Drivers.JSON.File, - }, - "ldap": ldapConfigFromString(cfg.Drivers.LDAP), - "rest": map[string]interface{}{ - "client_id": cfg.Drivers.REST.ClientID, - "client_secret": cfg.Drivers.REST.ClientSecret, - "redis_address": cfg.Drivers.REST.RedisAddr, - "redis_username": cfg.Drivers.REST.RedisUsername, - "redis_password": cfg.Drivers.REST.RedisPassword, - "user_groups_cache_expiration": cfg.UsersCacheExpiration, - "id_provider": cfg.Drivers.REST.IDProvider, - "api_base_url": cfg.Drivers.REST.APIBaseURL, - "oidc_token_endpoint": cfg.Drivers.REST.OIDCTokenEndpoint, - "target_api": cfg.Drivers.REST.TargetAPI, - }, - "owncloudsql": map[string]interface{}{ - "dbusername": cfg.Drivers.OwnCloudSQL.DBUsername, - "dbpassword": cfg.Drivers.OwnCloudSQL.DBPassword, - "dbhost": cfg.Drivers.OwnCloudSQL.DBHost, - "dbport": cfg.Drivers.OwnCloudSQL.DBPort, - "dbname": cfg.Drivers.OwnCloudSQL.DBName, - "idp": cfg.Drivers.OwnCloudSQL.IDP, - "nobody": cfg.Drivers.OwnCloudSQL.Nobody, - "join_username": cfg.Drivers.OwnCloudSQL.JoinUsername, - "join_ownclouduuid": cfg.Drivers.OwnCloudSQL.JoinOwnCloudUUID, - "enable_medial_search": cfg.Drivers.OwnCloudSQL.EnableMedialSearch, - }, - }, - }, - }, - }, - } - return rcfg -} - -// UserProviderSutureService allows for the storage-userprovider command to be embedded and supervised by a suture supervisor tree. -type UserProviderSutureService struct { - cfg *config.Config -} - -// NewUserProviderSutureService creates a new storage.UserProvider -func NewUserProvider(cfg *ociscfg.Config) suture.Service { - cfg.User.Commons = cfg.Commons - return UserProviderSutureService{ - cfg: cfg.User, - } -} - -func (s UserProviderSutureService) Serve(ctx context.Context) error { - // s.cfg.Reva.Users.Context = ctx - cmd := User(s.cfg) - f := &flag.FlagSet{} - cmdFlags := cmd.Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if cmd.Before != nil { - if err := cmd.Before(cliCtx); err != nil { - return err - } - } - if err := cmd.Action(cliCtx); err != nil { - return err - } - - return nil -} - -func ldapConfigFromString(cfg config.LDAPDriver) map[string]interface{} { - return map[string]interface{}{ - "uri": cfg.URI, - "cacert": cfg.CACert, - "insecure": cfg.Insecure, - "bind_username": cfg.BindDN, - "bind_password": cfg.BindPassword, - "user_base_dn": cfg.UserBaseDN, - "group_base_dn": cfg.GroupBaseDN, - "user_scope": cfg.UserScope, - "group_scope": cfg.GroupScope, - "user_filter": cfg.UserFilter, - "group_filter": cfg.GroupFilter, - "user_objectclass": cfg.UserObjectClass, - "group_objectclass": cfg.GroupObjectClass, - "login_attributes": cfg.LoginAttributes, - "idp": cfg.IDP, - "user_schema": map[string]interface{}{ - "id": cfg.UserSchema.ID, - "idIsOctetString": cfg.UserSchema.IDIsOctetString, - "mail": cfg.UserSchema.Mail, - "displayName": cfg.UserSchema.DisplayName, - "userName": cfg.UserSchema.Username, - }, - "group_schema": map[string]interface{}{ - "id": cfg.GroupSchema.ID, - "idIsOctetString": cfg.GroupSchema.IDIsOctetString, - "mail": cfg.GroupSchema.Mail, - "displayName": cfg.GroupSchema.DisplayName, - "groupName": cfg.GroupSchema.Groupname, - "member": cfg.GroupSchema.Member, - }, - } -} diff --git a/extensions/storage/pkg/command/health.go b/extensions/user/pkg/command/health.go similarity index 68% rename from extensions/storage/pkg/command/health.go rename to extensions/user/pkg/command/health.go index 1d8882e37d..1039a0efc0 100644 --- a/extensions/storage/pkg/command/health.go +++ b/extensions/user/pkg/command/health.go @@ -4,7 +4,9 @@ import ( "fmt" "net/http" - "github.com/owncloud/ocis/extensions/storage/pkg/config" + "github.com/owncloud/ocis/extensions/user/pkg/config" + "github.com/owncloud/ocis/extensions/user/pkg/config/parser" + "github.com/owncloud/ocis/extensions/user/pkg/logging" "github.com/urfave/cli/v2" ) @@ -14,8 +16,15 @@ func Health(cfg *config.Config) *cli.Command { Name: "health", Usage: "check health status", Category: "info", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, Action: func(c *cli.Context) error { - logger := NewLogger(cfg) + logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( fmt.Sprintf( diff --git a/extensions/user/pkg/command/root.go b/extensions/user/pkg/command/root.go new file mode 100644 index 0000000000..2f4c83aca6 --- /dev/null +++ b/extensions/user/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/extensions/user/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-user command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "user", + Usage: "Provide users for oCIS", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the user command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new user.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.User.Commons = cfg.Commons + return SutureService{ + cfg: cfg.User, + } +} + +func (s SutureService) Serve(ctx context.Context) error { + s.cfg.Context = ctx + if err := Execute(s.cfg); err != nil { + return err + } + + return nil +} diff --git a/extensions/user/pkg/command/server.go b/extensions/user/pkg/command/server.go new file mode 100644 index 0000000000..97baa1f9c4 --- /dev/null +++ b/extensions/user/pkg/command/server.go @@ -0,0 +1,120 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/user/pkg/config" + "github.com/owncloud/ocis/extensions/user/pkg/config/parser" + "github.com/owncloud/ocis/extensions/user/pkg/logging" + "github.com/owncloud/ocis/extensions/user/pkg/revaconfig" + "github.com/owncloud/ocis/extensions/user/pkg/server/debug" + "github.com/owncloud/ocis/extensions/user/pkg/tracing" + "github.com/owncloud/ocis/ocis-pkg/ldap" + "github.com/owncloud/ocis/ocis-pkg/service/external" + "github.com/owncloud/ocis/ocis-pkg/sync" + "github.com/owncloud/ocis/ocis-pkg/version" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start %s extension without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.UsersConfigFromStruct(cfg) + + // the reva runtime calls os.Exit in the case of a failure and there is no way for the oCIS + // runtime to catch it and restart a reva service. Therefore we need to ensure the service has + // everything it needs, before starting the service. + // In this case: CA certificates + if cfg.Driver == "ldap" { + ldapCfg := cfg.Drivers.LDAP + if err := ldap.WaitForCA(logger, ldapCfg.Insecure, ldapCfg.CACert); err != nil { + logger.Error().Err(err).Msg("The configured LDAP CA cert does not exist") + return err + } + } + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(_ error) { + logger.Info(). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + if err := external.RegisterGRPCEndpoint( + ctx, + cfg.GRPC.Namespace+"."+cfg.Service.Name, + uuid.Must(uuid.NewV4()).String(), + cfg.GRPC.Addr, + version.String, + logger, + ); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc endpoint") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the extension. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/extensions/user/pkg/command/version.go b/extensions/user/pkg/command/version.go new file mode 100644 index 0000000000..8330670428 --- /dev/null +++ b/extensions/user/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/ocis-pkg/registry" + "github.com/owncloud/ocis/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/extensions/user/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running extension instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.String) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/extensions/user/pkg/config/config.go b/extensions/user/pkg/config/config.go index 41cc0ab6e6..c044b2adab 100644 --- a/extensions/user/pkg/config/config.go +++ b/extensions/user/pkg/config/config.go @@ -1,24 +1,31 @@ package config -import "github.com/owncloud/ocis/ocis-pkg/shared" +import ( + "context" + + "github.com/owncloud/ocis/ocis-pkg/shared" +) type Config struct { *shared.Commons `yaml:"-"` Service Service `yaml:"-"` Tracing *Tracing `yaml:"tracing"` - Logging *Logging `yaml:"log"` + Log *Log `yaml:"log"` Debug Debug `yaml:"debug"` - Supervised bool `yaml:"-"` GRPC GRPCConfig `yaml:"grpc"` TokenManager *TokenManager `yaml:"token_manager"` Reva *Reva `yaml:"reva"` - SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token"` - UsersCacheExpiration int `yaml:"users_cache_expiration"` - Driver string `yaml:"driver"` - Drivers Drivers `yaml:"drivers"` + SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"USER_SKIP_USER_GROUPS_IN_TOKEN"` + + UsersCacheExpiration int `yaml:"users_cache_expiration"` + Driver string `yaml:"driver"` + Drivers Drivers `yaml:"drivers"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` } type Tracing struct { Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;USERS_TRACING_ENABLED" desc:"Activates tracing."` @@ -27,7 +34,7 @@ type Tracing struct { Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;USERS_TRACING_COLLECTOR"` } -type Logging struct { +type Log struct { Level string `yaml:"level" env:"OCIS_LOG_LEVEL;USERS_LOG_LEVEL" desc:"The log level."` Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;USERS_LOG_PRETTY" desc:"Activates pretty log output."` Color bool `yaml:"color" env:"OCIS_LOG_COLOR;USERS_LOG_COLOR" desc:"Activates colorized log output."` @@ -46,71 +53,71 @@ type Debug struct { } type GRPCConfig struct { - Addr string `yaml:"addr" env:"USERS_GRPC_ADDR" desc:"The address of the grpc service."` - Protocol string `yaml:"protocol" env:"USERS_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` + Addr string `yaml:"addr" env:"USERS_GRPC_ADDR" desc:"The address of the grpc service."` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"USERS_GRPC_PROTOCOL" desc:"The transport protocol of the grpc service."` } type Drivers struct { - JSON JSONDriver - LDAP LDAPDriver - OwnCloudSQL OwnCloudSQLDriver - REST RESTProvider + LDAP LDAPDriver `yaml:"ldap"` + OwnCloudSQL OwnCloudSQLDriver `yaml:"owncloudsql"` + + JSON JSONDriver `yaml:"json,omitempty"` // not supported by the oCIS product, therefore not part of docs + REST RESTProvider `yaml:"rest,omitempty"` // not supported by the oCIS product, therefore not part of docs } type JSONDriver struct { - File string + File string `yaml:"file"` } type LDAPDriver struct { - URI string `env:"LDAP_URI;USERS_LDAP_URI"` - CACert string `env:"LDAP_CACERT;USERS_LDAP_CACERT"` - Insecure bool `env:"LDAP_INSECURE;USERS_LDAP_INSECURE"` - BindDN string `env:"LDAP_BIND_DN;USERS_LDAP_BIND_DN"` - BindPassword string `yaml:"bind_password" env:"LDAP_BIND_PASSWORD;USERS_LDAP_BIND_PASSWORD"` - UserBaseDN string `env:"LDAP_USER_BASE_DN;USERS_LDAP_USER_BASE_DN"` - GroupBaseDN string `env:"LDAP_GROUP_BASE_DN;USERS_LDAP_GROUP_BASE_DN"` - UserScope string `env:"LDAP_USER_SCOPE;USERS_LDAP_USER_SCOPE"` - GroupScope string `env:"LDAP_GROUP_SCOPE;USERS_LDAP_GROUP_SCOPE"` - UserFilter string `env:"LDAP_USERFILTER;USERS_LDAP_USERFILTER"` - GroupFilter string `env:"LDAP_GROUPFILTER;USERS_LDAP_USERFILTER"` - UserObjectClass string `env:"LDAP_USER_OBJECTCLASS;USERS_LDAP_USER_OBJECTCLASS"` - GroupObjectClass string `env:"LDAP_GROUP_OBJECTCLASS;USERS_LDAP_GROUP_OBJECTCLASS"` - LoginAttributes []string `env:"LDAP_LOGIN_ATTRIBUTES;USERS_LDAP_LOGIN_ATTRIBUTES"` - IDP string `env:"OCIS_URL;USERS_IDP_URL"` // TODO what is this for? - GatewayEndpoint string // TODO do we need this here? - UserSchema LDAPUserSchema - GroupSchema LDAPGroupSchema + URI string `yaml:"uri" env:"LDAP_URI;USERS_LDAP_URI"` + CACert string `yaml:"ca_cert" env:"LDAP_CACERT;USERS_LDAP_CACERT"` + Insecure bool `yaml:"insecure" env:"LDAP_INSECURE;USERS_LDAP_INSECURE"` + BindDN string `yaml:"bind_dn" env:"LDAP_BIND_DN;USERS_LDAP_BIND_DN"` + BindPassword string `yaml:"bind_password" env:"LDAP_BIND_PASSWORD;USERS_LDAP_BIND_PASSWORD"` + UserBaseDN string `yaml:"user_base_dn" env:"LDAP_USER_BASE_DN;USERS_LDAP_USER_BASE_DN"` + GroupBaseDN string `yaml:"group_base_dn" env:"LDAP_GROUP_BASE_DN;USERS_LDAP_GROUP_BASE_DN"` + UserScope string `yaml:"user_scope" env:"LDAP_USER_SCOPE;USERS_LDAP_USER_SCOPE"` + GroupScope string `yaml:"group_scope" env:"LDAP_GROUP_SCOPE;USERS_LDAP_GROUP_SCOPE"` + UserFilter string `yaml:"user_filter" env:"LDAP_USERFILTER;USERS_LDAP_USERFILTER"` + GroupFilter string `yaml:"group_filter" env:"LDAP_GROUPFILTER;USERS_LDAP_USERFILTER"` + UserObjectClass string `yaml:"user_object_class" env:"LDAP_USER_OBJECTCLASS;USERS_LDAP_USER_OBJECTCLASS"` + GroupObjectClass string `yaml:"group_object_class" env:"LDAP_GROUP_OBJECTCLASS;USERS_LDAP_GROUP_OBJECTCLASS"` + LoginAttributes []string `yaml:"login_attributes" env:"LDAP_LOGIN_ATTRIBUTES;USERS_LDAP_LOGIN_ATTRIBUTES"` + IDP string `yaml:"idp" env:"OCIS_URL;USERS_IDP_URL"` + UserSchema LDAPUserSchema `yaml:"user_schema"` + GroupSchema LDAPGroupSchema `yaml:"group_schema"` } type LDAPUserSchema struct { - ID string `env:"LDAP_USER_SCHEMA_ID;USERS_LDAP_USER_SCHEMA_ID"` - IDIsOctetString bool `env:"LDAP_USER_SCHEMA_ID_IS_OCTETSTRING;USERS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING"` - Mail string `env:"LDAP_USER_SCHEMA_MAIL;USERS_LDAP_USER_SCHEMA_MAIL"` - DisplayName string `env:"LDAP_USER_SCHEMA_DISPLAYNAME;USERS_LDAP_USER_SCHEMA_DISPLAYNAME"` - Username string `env:"LDAP_USER_SCHEMA_USERNAME;USERS_LDAP_USER_SCHEMA_USERNAME"` + ID string `yaml:"id" env:"LDAP_USER_SCHEMA_ID;USERS_LDAP_USER_SCHEMA_ID"` + IDIsOctetString bool `yaml:"id_is_octet_string" env:"LDAP_USER_SCHEMA_ID_IS_OCTETSTRING;USERS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING"` + Mail string `yaml:"mail" env:"LDAP_USER_SCHEMA_MAIL;USERS_LDAP_USER_SCHEMA_MAIL"` + DisplayName string `yaml:"display_name" env:"LDAP_USER_SCHEMA_DISPLAYNAME;USERS_LDAP_USER_SCHEMA_DISPLAYNAME"` + Username string `yaml:"user_name" env:"LDAP_USER_SCHEMA_USERNAME;USERS_LDAP_USER_SCHEMA_USERNAME"` } type LDAPGroupSchema struct { - ID string `env:"LDAP_GROUP_SCHEMA_ID;USERS_LDAP_GROUP_SCHEMA_ID"` - IDIsOctetString bool `env:"LDAP_GROUP_SCHEMA_ID_IS_OCTETSTRING;USERS_LDAP_GROUP_SCHEMA_ID_IS_OCTETSTRING"` - Mail string `env:"LDAP_GROUP_SCHEMA_MAIL;USERS_LDAP_GROUP_SCHEMA_MAIL"` - DisplayName string `env:"LDAP_GROUP_SCHEMA_DISPLAYNAME;USERS_LDAP_GROUP_SCHEMA_DISPLAYNAME"` - Groupname string `env:"LDAP_GROUP_SCHEMA_GROUPNAME;USERS_LDAP_GROUP_SCHEMA_GROUPNAME"` - Member string `env:"LDAP_GROUP_SCHEMA_MEMBER;USERS_LDAP_GROUP_SCHEMA_MEMBER"` + ID string `yaml:"id" env:"LDAP_GROUP_SCHEMA_ID;USERS_LDAP_GROUP_SCHEMA_ID"` + IDIsOctetString bool `yaml:"id_is_octet_string" env:"LDAP_GROUP_SCHEMA_ID_IS_OCTETSTRING;USERS_LDAP_GROUP_SCHEMA_ID_IS_OCTETSTRING"` + Mail string `yaml:"mail" env:"LDAP_GROUP_SCHEMA_MAIL;USERS_LDAP_GROUP_SCHEMA_MAIL"` + DisplayName string `yaml:"display_name" env:"LDAP_GROUP_SCHEMA_DISPLAYNAME;USERS_LDAP_GROUP_SCHEMA_DISPLAYNAME"` + Groupname string `yaml:"group_name" env:"LDAP_GROUP_SCHEMA_GROUPNAME;USERS_LDAP_GROUP_SCHEMA_GROUPNAME"` + Member string `yaml:"member" env:"LDAP_GROUP_SCHEMA_MEMBER;USERS_LDAP_GROUP_SCHEMA_MEMBER"` } type OwnCloudSQLDriver struct { - DBUsername string - DBPassword string - DBHost string - DBPort int - DBName string - IDP string // TODO do we need this? - Nobody int64 // TODO what is this? - JoinUsername bool - JoinOwnCloudUUID bool - EnableMedialSearch bool + DBUsername string `yaml:"db_username" env:"USERS_OWNCLOUDSQL_DB_USERNAME"` + DBPassword string `yaml:"db_password" env:"USERS_OWNCLOUDSQL_DB_PASSWORD"` + DBHost string `yaml:"db_host" env:"USERS_OWNCLOUDSQL_DB_HOST"` + DBPort int `yaml:"db_port" env:"USERS_OWNCLOUDSQL_DB_PORT"` + DBName string `yaml:"db_name" env:"USERS_OWNCLOUDSQL_DB_NAME"` + IDP string `yaml:"idp" env:"USERS_OWNCLOUDSQL_IDP"` + Nobody int64 `yaml:"nobody" env:"USERS_OWNCLOUDSQL_NOBODY"` // TODO what is this? + JoinUsername bool `yaml:"join_username" env:"USERS_OWNCLOUDSQL_JOIN_USERNAME"` + JoinOwnCloudUUID bool `yaml:"join_owncloud_uuid" env:"USERS_OWNCLOUDSQL_JOIN_OWNCLOUD_UUID"` + EnableMedialSearch bool `yaml:"enable_medial_search" env:"USERS_OWNCLOUDSQL_ENABLE_MEDIAL_SEARCH"` } - type RESTProvider struct { ClientID string ClientSecret string diff --git a/extensions/user/pkg/config/defaults/defaultconfig.go b/extensions/user/pkg/config/defaults/defaultconfig.go index b7212abd0b..b3f4ac3386 100644 --- a/extensions/user/pkg/config/defaults/defaultconfig.go +++ b/extensions/user/pkg/config/defaults/defaultconfig.go @@ -23,8 +23,9 @@ func DefaultConfig() *config.Config { Zpages: false, }, GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9144", - Protocol: "tcp", + Addr: "127.0.0.1:9144", + Namespace: "com.owncloud.api", + Protocol: "tcp", }, Service: config.Service{ Name: "user", @@ -77,24 +78,21 @@ func DefaultConfig() *config.Config { JoinOwnCloudUUID: false, EnableMedialSearch: false, }, - REST: config.RESTProvider{ - RedisAddr: "localhost:6379", - }, }, } } func EnsureDefaults(cfg *config.Config) { // provide with defaults for shared logging, since we need a valid destination address for BindEnv. - if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { - cfg.Logging = &config.Logging{ + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ Level: cfg.Commons.Log.Level, Pretty: cfg.Commons.Log.Pretty, Color: cfg.Commons.Log.Color, File: cfg.Commons.Log.File, } - } else if cfg.Logging == nil { - cfg.Logging = &config.Logging{} + } else if cfg.Log == nil { + cfg.Log = &config.Log{} } // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { diff --git a/extensions/user/pkg/logging/logging.go b/extensions/user/pkg/logging/logging.go new file mode 100644 index 0000000000..d855cfeb94 --- /dev/null +++ b/extensions/user/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/extensions/user/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/extensions/user/pkg/revaconfig/config.go b/extensions/user/pkg/revaconfig/config.go new file mode 100644 index 0000000000..1ce6e51b1a --- /dev/null +++ b/extensions/user/pkg/revaconfig/config.go @@ -0,0 +1,86 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/extensions/user/pkg/config" +) + +// UsersConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func UsersConfigFromStruct(cfg *config.Config) map[string]interface{} { + rcfg := map[string]interface{}{ + "core": map[string]interface{}{ + "tracing_enabled": cfg.Tracing.Enabled, + "tracing_endpoint": cfg.Tracing.Endpoint, + "tracing_collector": cfg.Tracing.Collector, + "tracing_service_name": cfg.Service.Name, + }, + "shared": map[string]interface{}{ + "jwt_secret": cfg.TokenManager.JWTSecret, + "gatewaysvc": cfg.Reva.Address, + "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, + }, + "grpc": map[string]interface{}{ + "network": cfg.GRPC.Protocol, + "address": cfg.GRPC.Addr, + // TODO build services dynamically + "services": map[string]interface{}{ + "userprovider": map[string]interface{}{ + "driver": cfg.Driver, + "drivers": map[string]interface{}{ + "json": map[string]interface{}{ + "users": cfg.Drivers.JSON.File, + }, + "ldap": ldapConfigFromString(cfg.Drivers.LDAP), + "owncloudsql": map[string]interface{}{ + "dbusername": cfg.Drivers.OwnCloudSQL.DBUsername, + "dbpassword": cfg.Drivers.OwnCloudSQL.DBPassword, + "dbhost": cfg.Drivers.OwnCloudSQL.DBHost, + "dbport": cfg.Drivers.OwnCloudSQL.DBPort, + "dbname": cfg.Drivers.OwnCloudSQL.DBName, + "idp": cfg.Drivers.OwnCloudSQL.IDP, + "nobody": cfg.Drivers.OwnCloudSQL.Nobody, + "join_username": cfg.Drivers.OwnCloudSQL.JoinUsername, + "join_ownclouduuid": cfg.Drivers.OwnCloudSQL.JoinOwnCloudUUID, + "enable_medial_search": cfg.Drivers.OwnCloudSQL.EnableMedialSearch, + }, + }, + }, + }, + }, + } + return rcfg +} + +func ldapConfigFromString(cfg config.LDAPDriver) map[string]interface{} { + return map[string]interface{}{ + "uri": cfg.URI, + "cacert": cfg.CACert, + "insecure": cfg.Insecure, + "bind_username": cfg.BindDN, + "bind_password": cfg.BindPassword, + "user_base_dn": cfg.UserBaseDN, + "group_base_dn": cfg.GroupBaseDN, + "user_scope": cfg.UserScope, + "group_scope": cfg.GroupScope, + "user_filter": cfg.UserFilter, + "group_filter": cfg.GroupFilter, + "user_objectclass": cfg.UserObjectClass, + "group_objectclass": cfg.GroupObjectClass, + "login_attributes": cfg.LoginAttributes, + "idp": cfg.IDP, + "user_schema": map[string]interface{}{ + "id": cfg.UserSchema.ID, + "idIsOctetString": cfg.UserSchema.IDIsOctetString, + "mail": cfg.UserSchema.Mail, + "displayName": cfg.UserSchema.DisplayName, + "userName": cfg.UserSchema.Username, + }, + "group_schema": map[string]interface{}{ + "id": cfg.GroupSchema.ID, + "idIsOctetString": cfg.GroupSchema.IDIsOctetString, + "mail": cfg.GroupSchema.Mail, + "displayName": cfg.GroupSchema.DisplayName, + "groupName": cfg.GroupSchema.Groupname, + "member": cfg.GroupSchema.Member, + }, + } +} diff --git a/extensions/user/pkg/server/debug/option.go b/extensions/user/pkg/server/debug/option.go new file mode 100644 index 0000000000..31bbf15052 --- /dev/null +++ b/extensions/user/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/extensions/user/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Logger log.Logger + Context context.Context + Config *config.Config +} + +// newOptions initializes the available default options. +func newOptions(opts ...Option) Options { + opt := Options{} + + for _, o := range opts { + o(&opt) + } + + return opt +} + +// Logger provides a function to set the logger option. +func Logger(val log.Logger) Option { + return func(o *Options) { + o.Logger = val + } +} + +// Context provides a function to set the context option. +func Context(val context.Context) Option { + return func(o *Options) { + o.Context = val + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} diff --git a/extensions/storage/pkg/server/debug/server.go b/extensions/user/pkg/server/debug/server.go similarity index 71% rename from extensions/storage/pkg/server/debug/server.go rename to extensions/user/pkg/server/debug/server.go index 0038e858e8..27adbcdc64 100644 --- a/extensions/storage/pkg/server/debug/server.go +++ b/extensions/user/pkg/server/debug/server.go @@ -4,7 +4,7 @@ import ( "io" "net/http" - "github.com/owncloud/ocis/extensions/storage/pkg/config" + "github.com/owncloud/ocis/extensions/user/pkg/config" "github.com/owncloud/ocis/ocis-pkg/service/debug" "github.com/owncloud/ocis/ocis-pkg/version" ) @@ -15,14 +15,18 @@ func Server(opts ...Option) (*http.Server, error) { return debug.NewService( debug.Logger(options.Logger), - debug.Name(options.Name), + debug.Name(options.Config.Service.Name), debug.Version(version.String), - debug.Address(options.Addr), - debug.Token(options.Token), - debug.Pprof(options.Pprof), - debug.Zpages(options.Zpages), + debug.Address(options.Config.Debug.Addr), + debug.Token(options.Config.Debug.Token), + debug.Pprof(options.Config.Debug.Pprof), + debug.Zpages(options.Config.Debug.Zpages), debug.Health(health(options.Config)), debug.Ready(ready(options.Config)), + //debug.CorsAllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins), + //debug.CorsAllowedMethods(options.Config.HTTP.CORS.AllowedMethods), + //debug.CorsAllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders), + //debug.CorsAllowCredentials(options.Config.HTTP.CORS.AllowCredentials), ), nil } diff --git a/extensions/user/pkg/tracing/tracing.go b/extensions/user/pkg/tracing/tracing.go new file mode 100644 index 0000000000..836d0516c1 --- /dev/null +++ b/extensions/user/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/extensions/user/pkg/config" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + return nil +} diff --git a/extensions/web/Makefile b/extensions/web/Makefile index 39bfce1fe4..1200e1fb9a 100644 --- a/extensions/web/Makefile +++ b/extensions/web/Makefile @@ -1,6 +1,6 @@ SHELL := bash NAME := web -WEB_ASSETS_VERSION = v5.4.0 +WEB_ASSETS_VERSION = v5.5.0-rc.2 include ../../.make/recursion.mk diff --git a/extensions/webdav/pkg/command/root.go b/extensions/webdav/pkg/command/root.go index fe2a7f3c20..d8b32188e5 100644 --- a/extensions/webdav/pkg/command/root.go +++ b/extensions/webdav/pkg/command/root.go @@ -48,7 +48,7 @@ type SutureService struct { // NewSutureService creates a new webdav.SutureService func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Proxy.Commons = cfg.Commons + cfg.WebDAV.Commons = cfg.Commons return SutureService{ cfg: cfg.WebDAV, } diff --git a/extensions/webdav/pkg/service/v0/service.go b/extensions/webdav/pkg/service/v0/service.go index ad6faff6de..589db405ee 100644 --- a/extensions/webdav/pkg/service/v0/service.go +++ b/extensions/webdav/pkg/service/v0/service.go @@ -4,6 +4,7 @@ import ( "encoding/xml" "io" "net/http" + "path" "path/filepath" "strings" @@ -52,7 +53,9 @@ func NewService(opts ...Option) (Service, error) { conf := options.Config m := chi.NewMux() - chi.RegisterMethod("REPORT") + // Comment back in after resolving the issue in go-chi. + // See comment in line 82. + // chi.RegisterMethod("REPORT") m.Use(options.Middleware...) gwc, err := pool.GetGatewayServiceClient(conf.RevaGateway) @@ -75,7 +78,22 @@ func NewService(opts ...Option) (Service, error) { r.Get("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnail) r.Head("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnailHead) - r.MethodFunc("REPORT", "/remote.php/dav/files/{id}/*", svc.Search) + // r.MethodFunc("REPORT", "/remote.php/dav/files/{id}/*", svc.Search) + + // This is a workaround for the go-chi concurrent map read write issue. + // After the issue has been solved upstream in go-chi we should switch + // back to using `chi.RegisterMethod`. + m.MethodNotAllowed(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + routePrefix := path.Join(options.Config.HTTP.Root, "/remote.php/dav/files/") + if req.Method == "REPORT" && strings.HasPrefix(req.URL.Path, routePrefix) { + // The URLParam will not be available here. If it is needed it + // needs to be passed manually or chi needs to be fixed + // To use it properly. + svc.Search(w, req) + return + } + w.WriteHeader(http.StatusMethodNotAllowed) + })) }) return svc, nil diff --git a/go.mod b/go.mod index 5a1db99ccb..394b2abf65 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/blevesearch/bleve_index_api v1.0.1 github.com/coreos/go-oidc/v3 v3.1.0 github.com/cs3org/go-cs3apis v0.0.0-20220412090512-93c5918b4bde - github.com/cs3org/reva/v2 v2.0.0-20220503062800-28d8d2578a8f + github.com/cs3org/reva/v2 v2.0.0-20220503122729-afc8ead0c9a6 github.com/disintegration/imaging v1.6.2 github.com/glauth/glauth/v2 v2.0.0-20211021011345-ef3151c28733 github.com/go-chi/chi/v5 v5.0.7 diff --git a/go.sum b/go.sum index b7940583f6..d0a58a377b 100644 --- a/go.sum +++ b/go.sum @@ -318,10 +318,8 @@ github.com/cs3org/go-cs3apis v0.0.0-20220412090512-93c5918b4bde h1:WrD9O8ZaWvsm0 github.com/cs3org/go-cs3apis v0.0.0-20220412090512-93c5918b4bde/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= github.com/cs3org/reva v1.18.0 h1:MbPS5ZAa8RzKcTxAVeSDdISB3XXqLIxqB03BTN5ReBY= github.com/cs3org/reva v1.18.0/go.mod h1:e5VDUDu4vVWIeVkZcW//n6UZzhGGMa+Tz/whCiX3N6o= -github.com/cs3org/reva/v2 v2.0.0-20220502122639-bfbf8690a043 h1:wAvf45pBDnWIN4kpyWpD9uRl9y147ioAXJkfztrMSCM= -github.com/cs3org/reva/v2 v2.0.0-20220502122639-bfbf8690a043/go.mod h1:2e/4HcIy54Mic3V7Ow0bz4n5dkZU0dHIZSWomFe5vng= -github.com/cs3org/reva/v2 v2.0.0-20220503062800-28d8d2578a8f h1:3ge1ufZLjNni3FftOucKd0h9k9SGqzVoNnQ4Ssr03gQ= -github.com/cs3org/reva/v2 v2.0.0-20220503062800-28d8d2578a8f/go.mod h1:2e/4HcIy54Mic3V7Ow0bz4n5dkZU0dHIZSWomFe5vng= +github.com/cs3org/reva/v2 v2.0.0-20220503122729-afc8ead0c9a6 h1:7NmN0CkDNS+YDFPvT+n73f8WJHkRVG3OmPnOwWNPdlM= +github.com/cs3org/reva/v2 v2.0.0-20220503122729-afc8ead0c9a6/go.mod h1:2e/4HcIy54Mic3V7Ow0bz4n5dkZU0dHIZSWomFe5vng= github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 h1:Z9lwXumT5ACSmJ7WGnFl+OMLLjpz5uR2fyz7dC255FI= github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8/go.mod h1:4abs/jPXcmJzYoYGF91JF9Uq9s/KL5n1jvFDix8KcqY= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= diff --git a/ocis-pkg/config/config.go b/ocis-pkg/config/config.go index 8f6b1e0d83..3ace931c41 100644 --- a/ocis-pkg/config/config.go +++ b/ocis-pkg/config/config.go @@ -4,7 +4,8 @@ import ( "github.com/owncloud/ocis/ocis-pkg/shared" accounts "github.com/owncloud/ocis/extensions/accounts/pkg/config" - appprovider "github.com/owncloud/ocis/extensions/appprovider/pkg/config" + appProvider "github.com/owncloud/ocis/extensions/app-provider/pkg/config" + appRegistry "github.com/owncloud/ocis/extensions/app-registry/pkg/config" audit "github.com/owncloud/ocis/extensions/audit/pkg/config" authbasic "github.com/owncloud/ocis/extensions/auth-basic/pkg/config" authbearer "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config" @@ -71,35 +72,36 @@ type Config struct { MetadataUserID string `yaml:"metadata_user_id" env:"METADATA_USER_ID"` Runtime Runtime `yaml:"runtime"` - Audit *audit.Config `yaml:"audit"` Accounts *accounts.Config `yaml:"accounts"` - GLAuth *glauth.Config `yaml:"glauth"` - Graph *graph.Config `yaml:"graph"` - GraphExplorer *graphExplorer.Config `yaml:"graph-explorer"` - IDP *idp.Config `yaml:"idp"` - IDM *idm.Config `yaml:"idm"` - Nats *nats.Config `yaml:"nats"` - Notifications *notifications.Config `yaml:"notifications"` - OCS *ocs.Config `yaml:"ocs"` - Web *web.Config `yaml:"web"` - Proxy *proxy.Config `yaml:"proxy"` - Settings *settings.Config `yaml:"settings"` - Gateway *gateway.Config `yaml:"gateway"` - Frontend *frontend.Config `yaml:"frontend"` + AppProvider *appProvider.Config `yaml:"app-provider"` + AppRegistry *appRegistry.Config `yaml:"app-registry"` + Audit *audit.Config `yaml:"audit"` AuthBasic *authbasic.Config `yaml:"auth-basic"` AuthBearer *authbearer.Config `yaml:"auth-bearer"` AuthMachine *authmachine.Config `yaml:"auth-machine"` - User *user.Config `yaml:"user"` + Frontend *frontend.Config `yaml:"frontend"` + Gateway *gateway.Config `yaml:"gateway"` + GLAuth *glauth.Config `yaml:"glauth"` + Graph *graph.Config `yaml:"graph"` + GraphExplorer *graphExplorer.Config `yaml:"graph-explorer"` Group *group.Config `yaml:"group"` - AppProvider *appprovider.Config `yaml:"appprovider"` + IDM *idm.Config `yaml:"idm"` + IDP *idp.Config `yaml:"idp"` + Nats *nats.Config `yaml:"nats"` + Notifications *notifications.Config `yaml:"notifications"` + OCDav *ocdav.Config `yaml:"ocdav"` + OCS *ocs.Config `yaml:"ocs"` + Proxy *proxy.Config `yaml:"proxy"` + Settings *settings.Config `yaml:"settings"` Sharing *sharing.Config `yaml:"sharing"` StorageMetadata *storagemetadata.Config `yaml:"storage-metadata"` StoragePublicLink *storagepublic.Config `yaml:"storage-public"` - StorageUsers *storageusers.Config `yaml:"storage-users"` StorageShares *storageshares.Config `yaml:"storage-shares"` - OCDav *ocdav.Config `yaml:"ocdav"` + StorageUsers *storageusers.Config `yaml:"storage-users"` Store *store.Config `yaml:"store"` Thumbnails *thumbnails.Config `yaml:"thumbnails"` + User *user.Config `yaml:"user"` + Web *web.Config `yaml:"web"` WebDAV *webdav.Config `yaml:"webdav"` Search *search.Config `yaml:"search"` } diff --git a/ocis-pkg/config/defaultconfig.go b/ocis-pkg/config/defaultconfig.go index 8798918bfc..332d6de0e4 100644 --- a/ocis-pkg/config/defaultconfig.go +++ b/ocis-pkg/config/defaultconfig.go @@ -2,7 +2,8 @@ package config import ( accounts "github.com/owncloud/ocis/extensions/accounts/pkg/config/defaults" - appprovider "github.com/owncloud/ocis/extensions/appprovider/pkg/config/defaults" + appProvider "github.com/owncloud/ocis/extensions/app-provider/pkg/config/defaults" + appRegistry "github.com/owncloud/ocis/extensions/app-registry/pkg/config/defaults" audit "github.com/owncloud/ocis/extensions/audit/pkg/config/defaults" authbasic "github.com/owncloud/ocis/extensions/auth-basic/pkg/config/defaults" authbearer "github.com/owncloud/ocis/extensions/auth-bearer/pkg/config/defaults" @@ -40,8 +41,10 @@ func DefaultConfig() *Config { Port: "9250", Host: "localhost", }, + Accounts: accounts.DefaultConfig(), - AppProvider: appprovider.DefaultConfig(), + AppProvider: appProvider.DefaultConfig(), + AppRegistry: appRegistry.DefaultConfig(), Audit: audit.DefaultConfig(), AuthBasic: authbasic.DefaultConfig(), AuthBearer: authbearer.DefaultConfig(), diff --git a/extensions/storage/pkg/service/external/external.go b/ocis-pkg/service/external/external.go similarity index 52% rename from extensions/storage/pkg/service/external/external.go rename to ocis-pkg/service/external/external.go index 4ef64adacf..02310c3339 100644 --- a/extensions/storage/pkg/service/external/external.go +++ b/ocis-pkg/service/external/external.go @@ -12,7 +12,6 @@ import ( // RegisterGRPCEndpoint publishes an arbitrary endpoint to the service-registry. This allows to query nodes of // non-micro GRPC-services like reva. No health-checks are done, thus the caller is responsible for canceling. -// func RegisterGRPCEndpoint(ctx context.Context, serviceID, uuid, addr string, version string, logger log.Logger) error { node := ®istry.Node{ Id: serviceID + "-" + uuid, @@ -66,3 +65,59 @@ func RegisterGRPCEndpoint(ctx context.Context, serviceID, uuid, addr string, ver return nil } + +// RegisterHTTPEndpoint publishes an arbitrary endpoint to the service-registry. This allows to query nodes of +// non-micro HTTP-services like reva. No health-checks are done, thus the caller is responsible for canceling. +func RegisterHTTPEndpoint(ctx context.Context, serviceID, uuid, addr string, version string, logger log.Logger) error { + node := ®istry.Node{ + Id: serviceID + "-" + uuid, + Address: addr, + Metadata: make(map[string]string), + } + ocisRegistry := oregistry.GetRegistry() + + node.Metadata["broker"] = broker.String() + node.Metadata["registry"] = ocisRegistry.String() + node.Metadata["server"] = "http" + node.Metadata["transport"] = "http" + node.Metadata["protocol"] = "http" + + service := ®istry.Service{ + Name: serviceID, + Version: version, + Nodes: []*registry.Node{node}, + Endpoints: make([]*registry.Endpoint, 0), + } + + logger.Info().Msgf("registering external service %v@%v", node.Id, node.Address) + + rOpts := []registry.RegisterOption{registry.RegisterTTL(time.Minute)} + if err := ocisRegistry.Register(service, rOpts...); err != nil { + logger.Fatal().Err(err).Msgf("Registration error for external service %v", serviceID) + } + + t := time.NewTicker(time.Second * 30) + + go func() { + for { + select { + case <-t.C: + logger.Debug().Interface("service", service).Msg("refreshing external service-registration") + err := ocisRegistry.Register(service, rOpts...) + if err != nil { + logger.Error().Err(err).Msgf("registration error for external service %v", serviceID) + } + case <-ctx.Done(): + logger.Debug().Interface("service", service).Msg("unregistering") + t.Stop() + err := ocisRegistry.Deregister(service) + if err != nil { + logger.Err(err).Msgf("Error unregistering external service %v", serviceID) + } + + } + } + }() + + return nil +} diff --git a/extensions/storage/pkg/service/external/external_test.go b/ocis-pkg/service/external/external_test.go similarity index 100% rename from extensions/storage/pkg/service/external/external_test.go rename to ocis-pkg/service/external/external_test.go diff --git a/ocis/docker/Dockerfile.linux.amd64 b/ocis/docker/Dockerfile.linux.amd64 index 8d33914136..f2ac0931e2 100644 --- a/ocis/docker/Dockerfile.linux.amd64 +++ b/ocis/docker/Dockerfile.linux.amd64 @@ -1,4 +1,4 @@ -FROM amd64/alpine:3.14 +FROM amd64/alpine:3.15 ARG VERSION="" ARG REVISION="" @@ -26,10 +26,10 @@ RUN addgroup -g 1000 -S ocis-group && \ RUN mkdir -p /var/lib/ocis && \ chown -R ocis-user:ocis-group /var/lib/ocis && \ - chmod -R 777 /var/lib/ocis && \ + chmod -R 750 /var/lib/ocis && \ mkdir -p /etc/ocis && \ chown -R ocis-user:ocis-group /etc/ocis && \ - chmod -R 777 /etc/ocis + chmod -R 750 /etc/ocis VOLUME [ "/var/lib/ocis", "/etc/ocis" ] WORKDIR /var/lib/ocis diff --git a/ocis/docker/Dockerfile.linux.arm b/ocis/docker/Dockerfile.linux.arm index cb6f757b85..b9b2d67862 100644 --- a/ocis/docker/Dockerfile.linux.arm +++ b/ocis/docker/Dockerfile.linux.arm @@ -1,4 +1,4 @@ -FROM arm32v6/alpine:3.14 +FROM arm32v6/alpine:3.15 ARG VERSION="" ARG REVISION="" @@ -26,10 +26,10 @@ RUN addgroup -g 1000 -S ocis-group && \ RUN mkdir -p /var/lib/ocis && \ chown -R ocis-user:ocis-group /var/lib/ocis && \ - chmod -R 777 /var/lib/ocis && \ + chmod -R 750 /var/lib/ocis && \ mkdir -p /etc/ocis && \ chown -R ocis-user:ocis-group /etc/ocis && \ - chmod -R 777 /etc/ocis + chmod -R 750 /etc/ocis VOLUME [ "/var/lib/ocis", "/etc/ocis" ] WORKDIR /var/lib/ocis diff --git a/ocis/docker/Dockerfile.linux.arm64 b/ocis/docker/Dockerfile.linux.arm64 index 7601ed39e7..6c8b159546 100644 --- a/ocis/docker/Dockerfile.linux.arm64 +++ b/ocis/docker/Dockerfile.linux.arm64 @@ -1,4 +1,4 @@ -FROM arm64v8/alpine:3.14 +FROM arm64v8/alpine:3.15 ARG VERSION="" ARG REVISION="" @@ -26,10 +26,10 @@ RUN addgroup -g 1000 -S ocis-group && \ RUN mkdir -p /var/lib/ocis && \ chown -R ocis-user:ocis-group /var/lib/ocis && \ - chmod -R 777 /var/lib/ocis && \ + chmod -R 750 /var/lib/ocis && \ mkdir -p /etc/ocis && \ chown -R ocis-user:ocis-group /etc/ocis && \ - chmod -R 777 /etc/ocis + chmod -R 750 /etc/ocis VOLUME [ "/var/lib/ocis", "/etc/ocis" ] WORKDIR /var/lib/ocis diff --git a/ocis/pkg/command/app-registry.go b/ocis/pkg/command/app-registry.go new file mode 100644 index 0000000000..74dcb85ab5 --- /dev/null +++ b/ocis/pkg/command/app-registry.go @@ -0,0 +1,33 @@ +package command + +import ( + "fmt" + + "github.com/owncloud/ocis/extensions/app-registry/pkg/command" + "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/config/parser" + "github.com/owncloud/ocis/ocis/pkg/register" + "github.com/urfave/cli/v2" +) + +// AppRegistryCommand is the entrypoint for the AppRegistry command. +func AppRegistryCommand(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: cfg.AppRegistry.Service.Name, + Usage: subcommandDescription(cfg.AppRegistry.Service.Name), + Category: "extensions", + Before: func(c *cli.Context) error { + if err := parser.ParseConfig(cfg); err != nil { + fmt.Printf("%v", err) + return err + } + cfg.AppRegistry.Commons = cfg.Commons + return nil + }, + Subcommands: command.GetCommands(cfg.AppRegistry), + } +} + +func init() { + register.AddCommand(AppRegistryCommand) +} diff --git a/ocis/pkg/command/appprovider.go b/ocis/pkg/command/appprovider.go new file mode 100644 index 0000000000..0c10f0d2bc --- /dev/null +++ b/ocis/pkg/command/appprovider.go @@ -0,0 +1,33 @@ +package command + +import ( + "fmt" + + "github.com/owncloud/ocis/extensions/app-provider/pkg/command" + "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/config/parser" + "github.com/owncloud/ocis/ocis/pkg/register" + "github.com/urfave/cli/v2" +) + +// AppProviderCommand is the entrypoint for the app provider command. +func AppProviderCommand(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: cfg.AppProvider.Service.Name, + Usage: subcommandDescription(cfg.AppProvider.Service.Name), + Category: "extensions", + Before: func(c *cli.Context) error { + if err := parser.ParseConfig(cfg); err != nil { + fmt.Printf("%v", err) + return err + } + cfg.AppProvider.Commons = cfg.Commons + return nil + }, + Subcommands: command.GetCommands(cfg.AppProvider), + } +} + +func init() { + register.AddCommand(AppProviderCommand) +} diff --git a/ocis/pkg/command/storageauthbasic.go b/ocis/pkg/command/auth-basic.go similarity index 56% rename from ocis/pkg/command/storageauthbasic.go rename to ocis/pkg/command/auth-basic.go index 4c8dba5cdb..569ca434b6 100644 --- a/ocis/pkg/command/storageauthbasic.go +++ b/ocis/pkg/command/auth-basic.go @@ -10,11 +10,11 @@ import ( "github.com/urfave/cli/v2" ) -// StorageAuthBasicCommand is the entrypoint for the reva-auth-basic command. -func StorageAuthBasicCommand(cfg *config.Config) *cli.Command { +// AuthBasicCommand is the entrypoint for the AuthBasic command. +func AuthBasicCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "storage-auth-basic", - Usage: "start storage auth-basic service", + Name: cfg.AuthBasic.Service.Name, + Usage: subcommandDescription(cfg.AuthBasic.Service.Name), Category: "extensions", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg); err != nil { @@ -24,13 +24,10 @@ func StorageAuthBasicCommand(cfg *config.Config) *cli.Command { cfg.AuthBasic.Commons = cfg.Commons return nil }, - Action: func(c *cli.Context) error { - origCmd := command.AuthBasic(cfg.AuthBasic) - return handleOriginalAction(c, origCmd) - }, + Subcommands: command.GetCommands(cfg.AuthBasic), } } func init() { - register.AddCommand(StorageAuthBasicCommand) + register.AddCommand(AuthBasicCommand) } diff --git a/ocis/pkg/command/storageauthbearer.go b/ocis/pkg/command/auth-bearer.go similarity index 56% rename from ocis/pkg/command/storageauthbearer.go rename to ocis/pkg/command/auth-bearer.go index 20b641cda4..e44fcb88c0 100644 --- a/ocis/pkg/command/storageauthbearer.go +++ b/ocis/pkg/command/auth-bearer.go @@ -10,11 +10,11 @@ import ( "github.com/urfave/cli/v2" ) -// StorageAuthBearerCommand is the entrypoint for the reva-auth-bearer command. -func StorageAuthBearerCommand(cfg *config.Config) *cli.Command { +// AuthBearerCommand is the entrypoint for the AuthBearer command. +func AuthBearerCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "storage-auth-bearer", - Usage: "Start storage auth-bearer service", + Name: cfg.AuthBearer.Service.Name, + Usage: subcommandDescription(cfg.AuthBearer.Service.Name), Category: "extensions", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg); err != nil { @@ -24,13 +24,10 @@ func StorageAuthBearerCommand(cfg *config.Config) *cli.Command { cfg.AuthBearer.Commons = cfg.Commons return nil }, - Action: func(c *cli.Context) error { - origCmd := command.AuthBearer(cfg.AuthBearer) - return handleOriginalAction(c, origCmd) - }, + Subcommands: command.GetCommands(cfg.AuthBearer), } } func init() { - register.AddCommand(StorageAuthBearerCommand) + register.AddCommand(AuthBearerCommand) } diff --git a/ocis/pkg/command/storageauthmachine.go b/ocis/pkg/command/auth-machine.go similarity index 56% rename from ocis/pkg/command/storageauthmachine.go rename to ocis/pkg/command/auth-machine.go index f42ecb4b55..4ed22342f3 100644 --- a/ocis/pkg/command/storageauthmachine.go +++ b/ocis/pkg/command/auth-machine.go @@ -10,11 +10,11 @@ import ( "github.com/urfave/cli/v2" ) -// StorageAuthMachineCommand is the entrypoint for the reva-auth-machine command. -func StorageAuthMachineCommand(cfg *config.Config) *cli.Command { +// AuthMachineCommand is the entrypoint for the AuthMachine command. +func AuthMachineCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "storage-auth-machine", - Usage: "start storage auth-machine service", + Name: cfg.AuthMachine.Service.Name, + Usage: subcommandDescription(cfg.AuthMachine.Service.Name), Category: "extensions", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg); err != nil { @@ -24,13 +24,10 @@ func StorageAuthMachineCommand(cfg *config.Config) *cli.Command { cfg.AuthMachine.Commons = cfg.Commons return nil }, - Action: func(c *cli.Context) error { - origCmd := command.AuthMachine(cfg.AuthMachine) - return handleOriginalAction(c, origCmd) - }, + Subcommands: command.GetCommands(cfg.AuthMachine), } } func init() { - register.AddCommand(StorageAuthMachineCommand) + register.AddCommand(AuthMachineCommand) } diff --git a/ocis/pkg/command/common.go b/ocis/pkg/command/common.go index 77bfa381ed..dd65d0339d 100644 --- a/ocis/pkg/command/common.go +++ b/ocis/pkg/command/common.go @@ -2,21 +2,8 @@ package command import ( "fmt" - - "github.com/urfave/cli/v2" ) -func handleOriginalAction(c *cli.Context, cmd *cli.Command) error { - - if cmd.Before != nil { - if err := cmd.Before(c); err != nil { - return err - } - } - - return cli.HandleAction(cmd.Action, c) -} - func subcommandDescription(serviceName string) string { return fmt.Sprintf("%s extension commands", serviceName) } diff --git a/ocis/pkg/command/storagefrontend.go b/ocis/pkg/command/frontend.go similarity index 57% rename from ocis/pkg/command/storagefrontend.go rename to ocis/pkg/command/frontend.go index 05252414a9..0e8ad6142e 100644 --- a/ocis/pkg/command/storagefrontend.go +++ b/ocis/pkg/command/frontend.go @@ -10,11 +10,11 @@ import ( "github.com/urfave/cli/v2" ) -// StorageFrontendCommand is the entrypoint for the reva-frontend command. -func StorageFrontendCommand(cfg *config.Config) *cli.Command { +// FrontendCommand is the entrypoint for the Frontend command. +func FrontendCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "storage-frontend", - Usage: "start storage frontend", + Name: cfg.Frontend.Service.Name, + Usage: subcommandDescription(cfg.Frontend.Service.Name), Category: "extensions", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg); err != nil { @@ -24,13 +24,10 @@ func StorageFrontendCommand(cfg *config.Config) *cli.Command { cfg.Frontend.Commons = cfg.Commons return nil }, - Action: func(c *cli.Context) error { - origCmd := command.Frontend(cfg.Frontend) - return handleOriginalAction(c, origCmd) - }, + Subcommands: command.GetCommands(cfg.Frontend), } } func init() { - register.AddCommand(StorageFrontendCommand) + register.AddCommand(FrontendCommand) } diff --git a/ocis/pkg/command/storagegateway.go b/ocis/pkg/command/gateway.go similarity index 58% rename from ocis/pkg/command/storagegateway.go rename to ocis/pkg/command/gateway.go index 17047b6068..b6af96d067 100644 --- a/ocis/pkg/command/storagegateway.go +++ b/ocis/pkg/command/gateway.go @@ -10,11 +10,11 @@ import ( "github.com/urfave/cli/v2" ) -// StorageGatewayCommand is the entrypoint for the reva-gateway command. -func StorageGatewayCommand(cfg *config.Config) *cli.Command { +// GatewayCommand is the entrypoint for the Gateway command. +func GatewayCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "storage-gateway", - Usage: "start storage gateway", + Name: cfg.Gateway.Service.Name, + Usage: subcommandDescription(cfg.Gateway.Service.Name), Category: "extensions", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg); err != nil { @@ -24,13 +24,10 @@ func StorageGatewayCommand(cfg *config.Config) *cli.Command { cfg.Gateway.Commons = cfg.Commons return nil }, - Action: func(c *cli.Context) error { - origCmd := command.Gateway(cfg.Gateway) - return handleOriginalAction(c, origCmd) - }, + Subcommands: command.GetCommands(cfg.Gateway), } } func init() { - register.AddCommand(StorageGatewayCommand) + register.AddCommand(GatewayCommand) } diff --git a/ocis/pkg/command/storagegroupprovider.go b/ocis/pkg/command/group.go similarity index 55% rename from ocis/pkg/command/storagegroupprovider.go rename to ocis/pkg/command/group.go index c93de96c11..fe40ba15ce 100644 --- a/ocis/pkg/command/storagegroupprovider.go +++ b/ocis/pkg/command/group.go @@ -10,11 +10,11 @@ import ( "github.com/urfave/cli/v2" ) -// StorageGroupProviderCommand is the entrypoint for the storage-groupprovider command. -func StorageGroupProviderCommand(cfg *config.Config) *cli.Command { +// GroupCommand is the entrypoint for the Group command. +func GroupCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "storage-groupprovider", - Usage: "start storage groupprovider service", + Name: cfg.Group.Service.Name, + Usage: subcommandDescription(cfg.Group.Service.Name), Category: "extensions", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg); err != nil { @@ -24,13 +24,10 @@ func StorageGroupProviderCommand(cfg *config.Config) *cli.Command { cfg.Group.Commons = cfg.Commons return nil }, - Action: func(c *cli.Context) error { - origCmd := command.Groups(cfg.Group) - return handleOriginalAction(c, origCmd) - }, + Subcommands: command.GetCommands(cfg.Group), } } func init() { - register.AddCommand(StorageGroupProviderCommand) + register.AddCommand(GroupCommand) } diff --git a/ocis/pkg/command/ocdav.go b/ocis/pkg/command/ocdav.go index 85fdeb4570..b9adf24924 100644 --- a/ocis/pkg/command/ocdav.go +++ b/ocis/pkg/command/ocdav.go @@ -10,11 +10,11 @@ import ( "github.com/urfave/cli/v2" ) -// OCDavCommand is the entrypoint for the ocdav command. +// OCDavCommand is the entrypoint for the OCDav command. func OCDavCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "ocdav", - Usage: "start ocdav", + Name: cfg.OCDav.Service.Name, + Usage: subcommandDescription(cfg.OCDav.Service.Name), Category: "extensions", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg); err != nil { @@ -24,10 +24,7 @@ func OCDavCommand(cfg *config.Config) *cli.Command { cfg.OCDav.Commons = cfg.Commons return nil }, - Action: func(c *cli.Context) error { - origCmd := command.OCDav(cfg.OCDav) - return handleOriginalAction(c, origCmd) - }, + Subcommands: command.GetCommands(cfg.OCDav), } } diff --git a/ocis/pkg/command/storagesharing.go b/ocis/pkg/command/sharing.go similarity index 57% rename from ocis/pkg/command/storagesharing.go rename to ocis/pkg/command/sharing.go index 63c7e2aa06..de5b391a95 100644 --- a/ocis/pkg/command/storagesharing.go +++ b/ocis/pkg/command/sharing.go @@ -10,11 +10,11 @@ import ( "github.com/urfave/cli/v2" ) -// StorageSharingCommand is the entrypoint for the reva-sharing command. -func StorageSharingCommand(cfg *config.Config) *cli.Command { +// SharingCommand is the entrypoint for the Sharing command. +func SharingCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "storage-sharing", - Usage: "start storage sharing service", + Name: cfg.Sharing.Service.Name, + Usage: subcommandDescription(cfg.Sharing.Service.Name), Category: "extensions", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg); err != nil { @@ -24,13 +24,10 @@ func StorageSharingCommand(cfg *config.Config) *cli.Command { cfg.Sharing.Commons = cfg.Commons return nil }, - Action: func(c *cli.Context) error { - origCmd := command.Sharing(cfg.Sharing) - return handleOriginalAction(c, origCmd) - }, + Subcommands: command.GetCommands(cfg.Sharing), } } func init() { - register.AddCommand(StorageSharingCommand) + register.AddCommand(SharingCommand) } diff --git a/ocis/pkg/command/storagemetadata.go b/ocis/pkg/command/storage-metadata.go similarity index 67% rename from ocis/pkg/command/storagemetadata.go rename to ocis/pkg/command/storage-metadata.go index 2965c36cfc..e70c6a120d 100644 --- a/ocis/pkg/command/storagemetadata.go +++ b/ocis/pkg/command/storage-metadata.go @@ -10,11 +10,11 @@ import ( "github.com/urfave/cli/v2" ) -// StorageMetadataCommand is the entrypoint for the storage-metadata command. +// StorageMetadataCommand is the entrypoint for the StorageMetadata command. func StorageMetadataCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "storage-metadata", - Usage: "start storage and data service for metadata", + Name: cfg.StorageMetadata.Service.Name, + Usage: subcommandDescription(cfg.StorageMetadata.Service.Name), Category: "extensions", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg); err != nil { @@ -24,10 +24,7 @@ func StorageMetadataCommand(cfg *config.Config) *cli.Command { cfg.StorageMetadata.Commons = cfg.Commons return nil }, - Action: func(c *cli.Context) error { - origCmd := command.StorageMetadata(cfg.StorageMetadata) - return handleOriginalAction(c, origCmd) - }, + Subcommands: command.GetCommands(cfg.StorageMetadata), } } diff --git a/ocis/pkg/command/storagepubliclink.go b/ocis/pkg/command/storage-publiclink.go similarity index 67% rename from ocis/pkg/command/storagepubliclink.go rename to ocis/pkg/command/storage-publiclink.go index fe2f8b75e8..c084b66439 100644 --- a/ocis/pkg/command/storagepubliclink.go +++ b/ocis/pkg/command/storage-publiclink.go @@ -10,11 +10,11 @@ import ( "github.com/urfave/cli/v2" ) -// StoragePublicLinkCommand is the entrypoint for the reva-storage-oc command. +// StoragePublicLinkCommand is the entrypoint for the StoragePublicLink command. func StoragePublicLinkCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "storage-public-link", - Usage: "start storage public link storage", + Name: cfg.StoragePublicLink.Service.Name, + Usage: subcommandDescription(cfg.StoragePublicLink.Service.Name), Category: "extensions", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg); err != nil { @@ -24,10 +24,7 @@ func StoragePublicLinkCommand(cfg *config.Config) *cli.Command { cfg.StoragePublicLink.Commons = cfg.Commons return nil }, - Action: func(c *cli.Context) error { - origCmd := command.StoragePublicLink(cfg.StoragePublicLink) - return handleOriginalAction(c, origCmd) - }, + Subcommands: command.GetCommands(cfg.StoragePublicLink), } } diff --git a/ocis/pkg/command/storageshares.go b/ocis/pkg/command/storage-shares.go similarity index 67% rename from ocis/pkg/command/storageshares.go rename to ocis/pkg/command/storage-shares.go index 62f13c38a8..dd337dc5e9 100644 --- a/ocis/pkg/command/storageshares.go +++ b/ocis/pkg/command/storage-shares.go @@ -10,11 +10,11 @@ import ( "github.com/urfave/cli/v2" ) -// StorageSharesCommand is the entrypoint for the storage-shares command. +// StorageSharesCommand is the entrypoint for the StorageShares command. func StorageSharesCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "storage-shares", - Usage: "start storage and data provider for shares jail", + Name: cfg.StorageShares.Service.Name, + Usage: subcommandDescription(cfg.StorageShares.Service.Name), Category: "extensions", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg); err != nil { @@ -24,10 +24,7 @@ func StorageSharesCommand(cfg *config.Config) *cli.Command { cfg.StorageShares.Commons = cfg.Commons return nil }, - Action: func(c *cli.Context) error { - origCmd := command.StorageShares(cfg.StorageShares) - return handleOriginalAction(c, origCmd) - }, + Subcommands: command.GetCommands(cfg.StorageShares), } } diff --git a/ocis/pkg/command/storage-users.go b/ocis/pkg/command/storage-users.go new file mode 100644 index 0000000000..e1a4aaf3ea --- /dev/null +++ b/ocis/pkg/command/storage-users.go @@ -0,0 +1,33 @@ +package command + +import ( + "fmt" + + "github.com/owncloud/ocis/extensions/storage-users/pkg/command" + "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/config/parser" + "github.com/owncloud/ocis/ocis/pkg/register" + "github.com/urfave/cli/v2" +) + +// StorageUsersCommand is the entrypoint for the StorageUsers command. +func StorageUsersCommand(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: cfg.StorageUsers.Service.Name, + Usage: subcommandDescription(cfg.StorageUsers.Service.Name), + Category: "extensions", + Before: func(c *cli.Context) error { + if err := parser.ParseConfig(cfg); err != nil { + fmt.Printf("%v", err) + return err + } + cfg.StorageUsers.Commons = cfg.Commons + return nil + }, + Subcommands: command.GetCommands(cfg.StorageUsers), + } +} + +func init() { + register.AddCommand(StorageUsersCommand) +} diff --git a/ocis/pkg/command/storageappprovider.go b/ocis/pkg/command/storageappprovider.go deleted file mode 100644 index f44aa55fad..0000000000 --- a/ocis/pkg/command/storageappprovider.go +++ /dev/null @@ -1,36 +0,0 @@ -package command - -import ( - "fmt" - - "github.com/owncloud/ocis/extensions/appprovider/pkg/command" - "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/config/parser" - "github.com/owncloud/ocis/ocis/pkg/register" - "github.com/urfave/cli/v2" -) - -// StorageAppProviderCommand is the entrypoint for the reva-app-provider command. -func StorageAppProviderCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "storage-app-provider", - Usage: "start storage app-provider service", - Category: "extensions", - Before: func(c *cli.Context) error { - if err := parser.ParseConfig(cfg); err != nil { - fmt.Printf("%v", err) - return err - } - cfg.AppProvider.Commons = cfg.Commons - return nil - }, - Action: func(c *cli.Context) error { - origCmd := command.AppProvider(cfg.AppProvider) - return handleOriginalAction(c, origCmd) - }, - } -} - -func init() { - register.AddCommand(StorageAppProviderCommand) -} diff --git a/ocis/pkg/command/storageusers.go b/ocis/pkg/command/storageusers.go deleted file mode 100644 index 4ca75f2061..0000000000 --- a/ocis/pkg/command/storageusers.go +++ /dev/null @@ -1,25 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/extensions/storage-users/pkg/command" - "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis/pkg/register" - "github.com/urfave/cli/v2" -) - -// StorageUsersCommand is the entrypoint for the storage-users command. -func StorageUsersCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "storage-users", - Usage: "start storage and data provider for /users mount", - Category: "extensions", - Action: func(c *cli.Context) error { - origCmd := command.StorageUsers(cfg.StorageUsers) - return handleOriginalAction(c, origCmd) - }, - } -} - -func init() { - register.AddCommand(StorageUsersCommand) -} diff --git a/ocis/pkg/command/storageuserprovider.go b/ocis/pkg/command/user.go similarity index 51% rename from ocis/pkg/command/storageuserprovider.go rename to ocis/pkg/command/user.go index 5e3af09569..7685f1fc42 100644 --- a/ocis/pkg/command/storageuserprovider.go +++ b/ocis/pkg/command/user.go @@ -10,27 +10,24 @@ import ( "github.com/urfave/cli/v2" ) -// StorageUserProviderCommand is the entrypoint for the storage-userprovider command. -func StorageUserProviderCommand(cfg *config.Config) *cli.Command { +// UserCommand is the entrypoint for the User command. +func UserCommand(cfg *config.Config) *cli.Command { return &cli.Command{ - Name: "storage-userprovider", - Usage: "start storage userprovider service", + Name: cfg.User.Service.Name, + Usage: subcommandDescription(cfg.User.Service.Name), Category: "extensions", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg); err != nil { fmt.Printf("%v", err) return err } - cfg.StorageUsers.Commons = cfg.Commons + cfg.User.Commons = cfg.Commons return nil }, - Action: func(c *cli.Context) error { - origCmd := command.User(cfg.User) - return handleOriginalAction(c, origCmd) - }, + Subcommands: command.GetCommands(cfg.User), } } func init() { - register.AddCommand(StorageUserProviderCommand) + register.AddCommand(UserCommand) } diff --git a/ocis/pkg/init/init.go b/ocis/pkg/init/init.go index 4f8bf8a51a..3fc566f0e5 100644 --- a/ocis/pkg/init/init.go +++ b/ocis/pkg/init/init.go @@ -56,8 +56,7 @@ type IdmExtension struct { } type FrontendExtension struct { - Archiver InsecureExtension - AppProvider InsecureExtension `yaml:"app_provider"` + Archiver InsecureExtension } type AuthbasicExtension struct { @@ -256,9 +255,6 @@ func CreateConfig(insecure, forceOverwrite bool, configPath, adminPassword strin }, } cfg.Frontend = FrontendExtension{ - AppProvider: InsecureExtension{ - Insecure: true, - }, Archiver: InsecureExtension{ Insecure: true, }, diff --git a/ocis/pkg/runtime/service/service.go b/ocis/pkg/runtime/service/service.go index 99f947155b..2a1ff379bf 100644 --- a/ocis/pkg/runtime/service/service.go +++ b/ocis/pkg/runtime/service/service.go @@ -20,7 +20,8 @@ import ( "github.com/olekukonko/tablewriter" accounts "github.com/owncloud/ocis/extensions/accounts/pkg/command" - appprovider "github.com/owncloud/ocis/extensions/appprovider/pkg/command" + appProvider "github.com/owncloud/ocis/extensions/app-provider/pkg/command" + appRegistry "github.com/owncloud/ocis/extensions/app-registry/pkg/command" authbasic "github.com/owncloud/ocis/extensions/auth-basic/pkg/command" authbearer "github.com/owncloud/ocis/extensions/auth-bearer/pkg/command" authmachine "github.com/owncloud/ocis/extensions/auth-machine/pkg/command" @@ -109,7 +110,7 @@ func NewService(options ...Option) (*Service, error) { s.ServicesRegistry[opts.Config.Settings.Service.Name] = settings.NewSutureService s.ServicesRegistry[opts.Config.Nats.Service.Name] = nats.NewSutureService - s.ServicesRegistry[opts.Config.StorageMetadata.Service.Name] = storagemetadata.NewStorageMetadata + s.ServicesRegistry[opts.Config.StorageMetadata.Service.Name] = storagemetadata.NewSutureService s.ServicesRegistry[opts.Config.GLAuth.Service.Name] = glauth.NewSutureService s.ServicesRegistry[opts.Config.Graph.Service.Name] = graph.NewSutureService s.ServicesRegistry[opts.Config.GraphExplorer.Service.Name] = graphExplorer.NewSutureService @@ -119,23 +120,24 @@ func NewService(options ...Option) (*Service, error) { s.ServicesRegistry[opts.Config.Thumbnails.Service.Name] = thumbnails.NewSutureService s.ServicesRegistry[opts.Config.Web.Service.Name] = web.NewSutureService s.ServicesRegistry[opts.Config.WebDAV.Service.Name] = webdav.NewSutureService - s.ServicesRegistry[opts.Config.Frontend.Service.Name] = frontend.NewFrontend - s.ServicesRegistry[opts.Config.OCDav.Service.Name] = ocdav.NewOCDav - s.ServicesRegistry[opts.Config.Gateway.Service.Name] = gateway.NewGateway - s.ServicesRegistry[opts.Config.User.Service.Name] = user.NewUserProvider - s.ServicesRegistry[opts.Config.Group.Service.Name] = group.NewGroupProvider - s.ServicesRegistry[opts.Config.AuthBasic.Service.Name] = authbasic.NewAuthBasic - s.ServicesRegistry[opts.Config.AuthBearer.Service.Name] = authbearer.NewAuthBearer - s.ServicesRegistry[opts.Config.AuthMachine.Service.Name] = authmachine.NewAuthMachine - s.ServicesRegistry[opts.Config.StorageUsers.Service.Name] = storageusers.NewStorageUsers - s.ServicesRegistry[opts.Config.StorageShares.Service.Name] = storageshares.NewStorageShares - s.ServicesRegistry[opts.Config.StoragePublicLink.Service.Name] = storagepublic.NewStoragePublicLink - s.ServicesRegistry[opts.Config.AppProvider.Service.Name] = appprovider.NewAppProvider + s.ServicesRegistry[opts.Config.Frontend.Service.Name] = frontend.NewSutureService + s.ServicesRegistry[opts.Config.OCDav.Service.Name] = ocdav.NewSutureService + s.ServicesRegistry[opts.Config.Gateway.Service.Name] = gateway.NewSutureService + s.ServicesRegistry[opts.Config.AppRegistry.Service.Name] = appRegistry.NewSutureService + s.ServicesRegistry[opts.Config.User.Service.Name] = user.NewSutureService + s.ServicesRegistry[opts.Config.Group.Service.Name] = group.NewSutureService + s.ServicesRegistry[opts.Config.AuthBasic.Service.Name] = authbasic.NewSutureService + s.ServicesRegistry[opts.Config.AuthBearer.Service.Name] = authbearer.NewSutureService + s.ServicesRegistry[opts.Config.AuthMachine.Service.Name] = authmachine.NewSutureService + s.ServicesRegistry[opts.Config.StorageUsers.Service.Name] = storageusers.NewSutureService + s.ServicesRegistry[opts.Config.StorageShares.Service.Name] = storageshares.NewSutureService + s.ServicesRegistry[opts.Config.StoragePublicLink.Service.Name] = storagepublic.NewSutureService + s.ServicesRegistry[opts.Config.AppProvider.Service.Name] = appProvider.NewSutureService s.ServicesRegistry[opts.Config.Notifications.Service.Name] = notifications.NewSutureService s.ServicesRegistry[opts.Config.Search.Service.Name] = search.NewSutureService // populate delayed services - s.Delayed[opts.Config.Sharing.Service.Name] = sharing.NewSharing + s.Delayed[opts.Config.Sharing.Service.Name] = sharing.NewSutureService s.Delayed[opts.Config.Accounts.Service.Name] = accounts.NewSutureService s.Delayed[opts.Config.Proxy.Service.Name] = proxy.NewSutureService s.Delayed[opts.Config.IDP.Service.Name] = idp.NewSutureService @@ -256,7 +258,7 @@ func (s *Service) generateRunSet(cfg *ociscfg.Config) { } for name := range s.ServicesRegistry { - // don't run glauth by default but keep the possiblity to start it via cfg.Runtime.Extensions for now + // don't run glauth by default but keep the possibility to start it via cfg.Runtime.Extensions for now if name == "glauth" { continue } @@ -264,7 +266,7 @@ func (s *Service) generateRunSet(cfg *ociscfg.Config) { } for name := range s.Delayed { - // don't run accounts by default but keep the possiblity to start it via cfg.Runtime.Extensions for now + // don't run accounts by default but keep the possibility to start it via cfg.Runtime.Extensions for now if name == "accounts" { continue } diff --git a/tests/acceptance/expected-failures-API-on-OCIS-storage.md b/tests/acceptance/expected-failures-API-on-OCIS-storage.md index 87bdf5ad1b..24c9964812 100644 --- a/tests/acceptance/expected-failures-API-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-API-on-OCIS-storage.md @@ -1,4 +1,4 @@ -## Scenarios from ownCloud10 core API tests that are expected to fail with OCIS storage +## Scenarios from ownCloud10 core API tests that are expected to fail with OCIS storage while running with the Graph API The expected failures in this file are from features in the owncloud/core repo. @@ -615,49 +615,6 @@ cannot share a folder with create permission - [apiTrashbin/trashbinSharingToShares.feature:212](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L212) - [apiTrashbin/trashbinSharingToShares.feature:236](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L236) -#### [changing user quota gives ocs status 103 / cannot set user quota using the ocs endpoint](https://github.com/owncloud/product/issues/247) - -_getting and setting quota_ - -- [apiMain/quota.feature:10](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/quota.feature#L10) -- [apiMain/quota.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/quota.feature#L23) -- [apiMain/quota.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/quota.feature#L38) -- [apiMain/quota.feature:53](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/quota.feature#L53) -- [apiMain/quota.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/quota.feature#L72) -- [apiMain/quota.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/quota.feature#L93) -- [apiMain/quota.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/quota.feature#L116) -- [apiMain/quota.feature:139](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/quota.feature#L139) -- [apiMain/quota.feature:166](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/quota.feature#L166) -- [apiMain/quota.feature:187](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/quota.feature#L187) -- [apiMain/quota.feature:210](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/quota.feature#L210) -- [apiMain/quota.feature:217](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/quota.feature#L217) -- [apiMain/quota.feature:224](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/quota.feature#L224) -- [apiMain/quota.feature:239](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/quota.feature#L239) -- [apiMain/quota.feature:253](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/quota.feature#L253) -- [apiMain/quota.feature:269](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/quota.feature#L269) -- [apiMain/quota.feature:283](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/quota.feature#L283) -- [apiMain/quota.feature:289](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/quota.feature#L289) - Scenario Outline: Retrieving folder quota when no quota is set -- [apiWebdavProperties1/getQuota.feature:18](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L18) -- [apiWebdavProperties1/getQuota.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L19) -- [apiWebdavProperties1/getQuota.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L24) - Scenario Outline: Retrieving folder quota when quota is set -- [apiWebdavProperties1/getQuota.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L34) -- [apiWebdavProperties1/getQuota.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L35) -- [apiWebdavProperties1/getQuota.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L40) - Scenario Outline: Retrieving folder quota of shared folder with quota when no quota is set for recipient -- [apiWebdavProperties1/getQuota.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L61) -- [apiWebdavProperties1/getQuota.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L62) -- [apiWebdavProperties1/getQuota.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L67) - Scenario Outline: Retrieving folder quota when quota is set and a file was uploaded -- [apiWebdavProperties1/getQuota.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L81) -- [apiWebdavProperties1/getQuota.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L82) -- [apiWebdavProperties1/getQuota.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L87) - Scenario Outline: Retrieving folder quota when quota is set and a file was received -- [apiWebdavProperties1/getQuota.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L103) -- [apiWebdavProperties1/getQuota.feature:104](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L104) -- [apiWebdavProperties1/getQuota.feature:109](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L109) - #### [Private link support](https://github.com/owncloud/product/issues/201) #### [oc:privatelink property not returned in webdav responses](https://github.com/owncloud/product/issues/262) @@ -668,21 +625,9 @@ _getting and setting quota_ #### [changing user quota gives ocs status 103 / Cannot set quota](https://github.com/owncloud/product/issues/247) -- [apiShareOperationsToShares2/uploadToShare.feature:193](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L193) -- [apiShareOperationsToShares2/uploadToShare.feature:194](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L194) -- [apiShareOperationsToShares2/uploadToShare.feature:199](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L199) -- [apiShareOperationsToShares2/uploadToShare.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L218) -- [apiShareOperationsToShares2/uploadToShare.feature:219](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L219) -- [apiShareOperationsToShares2/uploadToShare.feature:224](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L224) - [apiShareOperationsToShares2/uploadToShare.feature:245](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L245) - [apiShareOperationsToShares2/uploadToShare.feature:246](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L246) - [apiShareOperationsToShares2/uploadToShare.feature:251](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L251) -- [apiShareOperationsToShares2/uploadToShare.feature:270](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L270) -- [apiShareOperationsToShares2/uploadToShare.feature:271](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L271) -- [apiShareOperationsToShares2/uploadToShare.feature:276](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L276) -- [apiShareOperationsToShares2/uploadToShare.feature:297](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L297) -- [apiShareOperationsToShares2/uploadToShare.feature:298](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L298) -- [apiShareOperationsToShares2/uploadToShare.feature:303](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L303) #### [not possible to move file into a received folder](https://github.com/owncloud/ocis/issues/764) @@ -943,29 +888,6 @@ User and group management features special character username not valid -- [apiProvisioning-v1/addUser.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/addUser.feature#L20) -- [apiProvisioning-v1/addUser.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/addUser.feature#L40) -- [apiProvisioning-v1/addUser.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/addUser.feature#L47) -- [apiProvisioning-v1/addUser.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/addUser.feature#L83) -- [apiProvisioning-v1/addUser.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/addUser.feature#L116) -- [apiProvisioning-v1/addUser.feature:123](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/addUser.feature#L123) -- [apiProvisioning-v1/addUser.feature:205](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/addUser.feature#L205) -- [apiProvisioning-v1/deleteUser.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/deleteUser.feature#L19) -- [apiProvisioning-v1/disableUser.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/disableUser.feature#L19) -- [apiProvisioning-v1/editUser.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/editUser.feature#L29) -- [apiProvisioning-v1/editUser.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/editUser.feature#L30) -- [apiProvisioning-v1/enableUser.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/enableUser.feature#L20) -- [apiProvisioning-v1/getUser.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/getUser.feature#L38) -- [apiProvisioning-v1/getUser.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/getUser.feature#L39) -- [apiProvisioning-v2/addUser.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/addUser.feature#L20) -- [apiProvisioning-v2/addUser.feature:123](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/addUser.feature#L123) -- [apiProvisioning-v2/deleteUser.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/deleteUser.feature#L19) -- [apiProvisioning-v2/disableUser.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/disableUser.feature#L19) -- [apiProvisioning-v2/editUser.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/editUser.feature#L29) -- [apiProvisioning-v2/editUser.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/editUser.feature#L30) -- [apiProvisioning-v2/enableUser.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/enableUser.feature#L20) -- [apiProvisioning-v2/getUser.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/getUser.feature#L38) -- [apiProvisioning-v2/getUser.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/getUser.feature#L39) - [apiTrashbin/trashbinFilesFolders.feature:306](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L306) - [apiTrashbin/trashbinFilesFolders.feature:307](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L307) - [apiTrashbin/trashbinFilesFolders.feature:308](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L308) @@ -973,79 +895,6 @@ special character username not valid - [apiTrashbin/trashbinFilesFolders.feature:317](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L317) - [apiTrashbin/trashbinFilesFolders.feature:318](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L318) -#### [Client token generation not implemented](https://github.com/owncloud/ocis/issues/197) - -- [apiProvisioning-v1/apiProvisioningUsingAppPassword.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/apiProvisioningUsingAppPassword.feature#L39) -- [apiProvisioning-v1/apiProvisioningUsingAppPassword.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/apiProvisioningUsingAppPassword.feature#L68) -- [apiProvisioning-v2/apiProvisioningUsingAppPassword.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/apiProvisioningUsingAppPassword.feature#L39) -- [apiProvisioning-v2/apiProvisioningUsingAppPassword.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/apiProvisioningUsingAppPassword.feature#L67) - -#### [disable users /cloud/users/disable|enable not available](https://github.com/owncloud/ocis/issues/1420) - -- [apiProvisioning-v1/enableUser.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/enableUser.feature#L100) -- [apiProvisioning-v1/enableUser.feature:110](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/enableUser.feature#L110) -- [apiProvisioning-v1/enableUser.feature:137](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/enableUser.feature#L137) -- [apiProvisioning-v1/disableUser.feature:104](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/disableUser.feature#L104) -- [apiProvisioning-v1/disableUser.feature:135](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/disableUser.feature#L135) -- [apiProvisioning-v1/disableUser.feature:177](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/disableUser.feature#L177) -- [apiProvisioning-v1/disableUser.feature:185](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/disableUser.feature#L185) -- [apiProvisioning-v1/disableUser.feature:251](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/disableUser.feature#L251) -- [apiProvisioning-v1/disableUser.feature:271](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/disableUser.feature#L271) -- [apiProvisioning-v2/disableUser.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/disableUser.feature#L82) -- [apiProvisioning-v2/disableUser.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/disableUser.feature#L102) -- [apiProvisioning-v2/disableUser.feature:111](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/disableUser.feature#L111) -- [apiProvisioning-v2/disableUser.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/disableUser.feature#L133) -- [apiProvisioning-v2/disableUser.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/disableUser.feature#L174) -- [apiProvisioning-v2/disableUser.feature:182](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/disableUser.feature#L182) -- [apiProvisioning-v2/disableUser.feature:248](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/disableUser.feature#L248) -- [apiProvisioning-v2/disableUser.feature:268](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/disableUser.feature#L268) - -#### [Update displayname to empty value is successful](https://github.com/owncloud/ocis/issues/2167) - -- [apiProvisioning-v1/editUser.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/editUser.feature#L48) -- [apiProvisioning-v2/editUser.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/editUser.feature#L49) - -#### [changing user quota gives ocs status 103 / Cannot set quota](https://github.com/owncloud/product/issues/247) - -- [apiProvisioning-v1/editUser.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/editUser.feature#L57) -- [apiProvisioning-v1/editUser.feature:129](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/editUser.feature#L129) -- [apiProvisioning-v2/editUser.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/editUser.feature#L58) -- [apiProvisioning-v2/editUser.feature:130](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/editUser.feature#L130) -- [apiProvisioning-v2/enableUser.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/enableUser.feature#L40) -- [apiProvisioning-v2/enableUser.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/enableUser.feature#L62) -- [apiProvisioning-v2/enableUser.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/enableUser.feature#L72) - -#### [user can get info of other users/ cloud/users endpoints not authenticated](https://github.com/owncloud/product/issues/248) - -- [apiProvisioning-v2/deleteUser.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/deleteUser.feature#L57) -- [apiProvisioning-v2/getUser.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/getUser.feature#L92) -- [apiProvisioning-v2/getUsers.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/getUsers.feature#L44) - -#### [incorrect ocs(v2) status value when getting info of user that does not exist should be 404, gives 998](https://github.com/owncloud/product/issues/250) - -_ocs: api compatibility, return correct status code_ - -- [apiProvisioning-v2/getUser.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/getUser.feature#L55) - -#### [Provisioning API does not provide last_login in response](https://github.com/owncloud/ocis/issues/2729) - -- [apiProvisioning-v1/getUser.feature:11](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/getUser.feature#L11) -- [apiProvisioning-v1/getUser.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/getUser.feature#L42) -- [apiProvisioning-v1/getUser.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/getUser.feature#L92) -- [apiProvisioning-v1/getUser.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/getUser.feature#L103) -- [apiProvisioning-v1/getUser.feature:129](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/getUser.feature#L129) -- [apiProvisioning-v1/getUser.feature:130](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/getUser.feature#L130) -- [apiProvisioning-v1/getUser.feature:146](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/getUser.feature#L146) -- [apiProvisioning-v1/getUser.feature:147](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/getUser.feature#L147) -- [apiProvisioning-v1/getUser.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/getUser.feature#L150) -- [apiProvisioning-v2/getUser.feature:11](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/getUser.feature#L11) -- [apiProvisioning-v2/getUser.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/getUser.feature#L42) -- [apiProvisioning-v2/getUser.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/getUser.feature#L103) -- [apiProvisioning-v2/getUser.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/getUser.feature#L116) -- [apiProvisioning-v2/getUser.feature:129](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/getUser.feature#L129) -- [apiProvisioning-v2/getUser.feature:142](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/getUser.feature#L142) -- [apiProvisioning-v2/getUser.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v2/getUser.feature#L155) - #### [incorrect ocs(v2) status value when getting info of share that does not exist should be 404, gives 998](https://github.com/owncloud/product/issues/250) _ocs: api compatibility, return correct status code_ @@ -1059,74 +908,6 @@ _ocs: api compatibility, return correct status code_ - [apiShareOperationsToShares2/shareAccessByID.feature:54](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L54) - [apiShareOperationsToShares2/shareAccessByID.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L55) -#### [Different ocs status-text and status-code in oCIS and oC10 using ocs api v1](https://github.com/owncloud/ocis/issues/1777) - -- [apiProvisioningGroups-v1/addToGroup.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v1/addToGroup.feature#L150) - -#### [creating existing group doesn't gives error](https://github.com/owncloud/product/issues/282) - -- [apiProvisioningGroups-v1/addGroup.feature:134](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v1/addGroup.feature#L134) -- [apiProvisioningGroups-v2/addGroup.feature:129](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v2/addGroup.feature#L129) - -#### [group names with space at the start or end should not be allowed](https://github.com/owncloud/ocis/issues/2876) - -- [apiProvisioningGroups-v1/addGroup.feature:158](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v1/addGroup.feature#L158) -- [apiProvisioningGroups-v1/addGroup.feature:166](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v1/addGroup.feature#L166) -- [apiProvisioningGroups-v1/addGroup.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v1/addGroup.feature#L174) -- [apiProvisioningGroups-v2/addGroup.feature:154](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v2/addGroup.feature#L154) -- [apiProvisioningGroups-v2/addGroup.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v2/addGroup.feature#L162) -- [apiProvisioningGroups-v2/addGroup.feature:170](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v2/addGroup.feature#L170) - -#### [cannot create group with '/'](https://github.com/owncloud/product/issues/285) - -- [apiProvisioningGroups-v1/addToGroup.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v1/addToGroup.feature#L82) -- [apiProvisioningGroups-v1/deleteGroup.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v1/deleteGroup.feature#L85) -- [apiProvisioningGroups-v1/deleteGroup.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v1/deleteGroup.feature#L86) -- [apiProvisioningGroups-v1/deleteGroup.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v1/deleteGroup.feature#L87) -- [apiProvisioningGroups-v1/getUserGroups.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v1/getUserGroups.feature#L38) -- [apiProvisioningGroups-v1/removeFromGroup.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v1/removeFromGroup.feature#L132) - -- [apiProvisioningGroups-v2/addToGroup.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v2/addToGroup.feature#L82) -- [apiProvisioningGroups-v2/deleteGroup.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v2/deleteGroup.feature#L85) -- [apiProvisioningGroups-v2/deleteGroup.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v2/deleteGroup.feature#L86) -- [apiProvisioningGroups-v2/deleteGroup.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v2/deleteGroup.feature#L87) -- [apiProvisioningGroups-v2/getUserGroups.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v2/getUserGroups.feature#L38) -- [apiProvisioningGroups-v2/removeFromGroup.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v2/removeFromGroup.feature#L132) - -#### [adding user to non-existent group gives wrong statuscode](https://github.com/owncloud/product/issues/286) - -- [apiProvisioningGroups-v1/addToGroup.feature:159](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v1/addToGroup.feature#L159) -- [apiProvisioningGroups-v1/removeFromGroup.feature:178](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v1/removeFromGroup.feature#L178) -- [apiProvisioningGroups-v2/removeFromGroup.feature:178](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v2/removeFromGroup.feature#L178) - -#### [adding user to empty group gives wrong statuscode](https://github.com/owncloud/product/issues/287) - -- [apiProvisioningGroups-v1/addToGroup.feature:167](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v1/addToGroup.feature#L167) - -#### [Cannot create user with different username and emails](https://github.com/owncloud/product/issues/187) - -_special character username not valid_ - -- [apiProvisioningGroups-v1/getGroup.feature:11](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v1/getGroup.feature#L11) -- [apiProvisioningGroups-v2/getGroup.feature:11](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v2/getGroup.feature#L11) - -#### [normal users can list the members of the group](https://github.com/owncloud/product/issues/290) - -- [apiProvisioningGroups-v1/getGroup.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v1/getGroup.feature#L81) -- [apiProvisioningGroups-v1/deleteGroup.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v1/deleteGroup.feature#L90) -- [apiProvisioningGroups-v2/getGroup.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v2/getGroup.feature#L83) - -#### [ocs v2 invalid status code for group endpoints](https://github.com/owncloud/product/issues/291) - -- [apiProvisioningGroups-v2/addGroup.feature:137](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v2/addGroup.feature#L137) -- [apiProvisioningGroups-v2/addToGroup.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v2/addToGroup.feature#L133) -- [apiProvisioningGroups-v2/addToGroup.feature:141](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v2/addToGroup.feature#L141) -- [apiProvisioningGroups-v2/addToGroup.feature:158](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v2/addToGroup.feature#L158) -- [apiProvisioningGroups-v2/deleteGroup.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v2/deleteGroup.feature#L91) -- [apiProvisioningGroups-v2/getGroup.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v2/getGroup.feature#L33) -- [apiProvisioningGroups-v2/getUserGroups.feature:73](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v2/getUserGroups.feature#L73) -- [apiProvisioningGroups-v2/removeFromGroup.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioningGroups-v2/removeFromGroup.feature#L215) - ### Other API, search, favorites, config, capabilities, not existing endpoints, CORS and others @@ -1235,6 +1016,15 @@ _ocdav: api compatibility, return correct status code_ - [apiWebdavOperations/search.feature:265](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L265) - [apiWebdavOperations/search.feature:270](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L270) +#### [Support for favorites](https://github.com/owncloud/ocis/issues/1228) + +- [apiFavorites/favorites.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L115) +- [apiFavorites/favorites.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L116) +- [apiFavorites/favorites.feature:141](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L141) +- [apiFavorites/favorites.feature:142](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L142) +- [apiFavorites/favorites.feature:267](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L267) +- [apiFavorites/favorites.feature:268](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L268) + And other missing implementation of favorites - [apiFavorites/favorites.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L162) @@ -1819,10 +1609,6 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers - [apiWebdavOperations/listFiles.feature:256](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/listFiles.feature#L256) - [apiWebdavOperations/listFiles.feature:294](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/listFiles.feature#L294) -#### [OCS response is not returned when a disabled user tries to enable himself](https://github.com/owncloud/ocis/issues/3254) - -- [apiProvisioning-v1/enableUser.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiProvisioning-v1/enableUser.feature#L62) - #### [Trying to modify a shared file using spaces end-point returns 409 HTTP status code](https://github.com/owncloud/ocis/issues/3241) - [apiMain/checksums.feature:233](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L233) @@ -1844,6 +1630,21 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers - [apiShareManagementBasicToShares/createShareToSharesFolder.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L132) - [apiShareManagementBasicToShares/createShareToSharesFolder.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L133) +### [graph/users: enable/disable users](https://github.com/owncloud/ocis/issues/3064) + +- [apiWebdavOperations/refuseAccess.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L35) +- [apiWebdavOperations/refuseAccess.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L36) +- [apiWebdavOperations/refuseAccess.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L41) + +### [Group name is case-insensitive](https://github.com/owncloud/ocis/issues/3167#issuecomment-1090045359) + +- [apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature#L49) +- [apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature#L50) +- [apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature#L51) +- [apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature#L52) +- [apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature:53](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature#L53) +- [apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature:54](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature#L54) + #### [OCS status code zero](https://github.com/owncloud/ocis/issues/3621) - [apiShareManagementToShares/moveReceivedShare.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveReceivedShare.feature#L32) @@ -1882,5 +1683,5 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers - [apiTrashbin/trashbinFilesFolders.feature:408](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L408) - [apiTrashbin/trashbinFilesFolders.feature:445](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L445) - Note: always have an empty line at the end of this file. - The bash script that processes this file requires that the last line has a newline on the end. +Note: always have an empty line at the end of this file. +The bash script that processes this file requires that the last line has a newline on the end. diff --git a/tests/acceptance/expected-failures-graphAPI-on-OCIS-storage.md b/tests/acceptance/expected-failures-graphAPI-on-OCIS-storage.md deleted file mode 100644 index 24c9964812..0000000000 --- a/tests/acceptance/expected-failures-graphAPI-on-OCIS-storage.md +++ /dev/null @@ -1,1687 +0,0 @@ -## Scenarios from ownCloud10 core API tests that are expected to fail with OCIS storage while running with the Graph API - -The expected failures in this file are from features in the owncloud/core repo. - -### File - -Basic file management like up and download, move, copy, properties, trash, versions and chunking. - -#### [Getting information about a folder overwritten by a file gives 500 error instead of 404](https://github.com/owncloud/ocis/issues/1239) - -- [apiWebdavProperties1/copyFile.feature:286](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L286) -- [apiWebdavProperties1/copyFile.feature:287](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L287) -- [apiWebdavProperties1/copyFile.feature:309](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L309) -- [apiWebdavProperties1/copyFile.feature:310](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L310) - -#### [Custom dav properties with namespaces are rendered incorrectly](https://github.com/owncloud/ocis/issues/2140) - -_ocdav: double check the webdav property parsing when custom namespaces are used_ - -- [apiWebdavProperties1/setFileProperties.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L37) -- [apiWebdavProperties1/setFileProperties.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L38) -- [apiWebdavProperties1/setFileProperties.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L43) -- [apiWebdavProperties1/setFileProperties.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L78) -- [apiWebdavProperties1/setFileProperties.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L79) -- [apiWebdavProperties1/setFileProperties.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L84) - -#### [Cannot set custom webDav properties](https://github.com/owncloud/product/issues/264) - -- [apiWebdavProperties2/getFileProperties.feature:360](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L360) -- [apiWebdavProperties2/getFileProperties.feature:365](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L365) -- [apiWebdavProperties2/getFileProperties.feature:370](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L370) -- [apiWebdavProperties2/getFileProperties.feature:401](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L401) -- [apiWebdavProperties2/getFileProperties.feature:406](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L406) -- [apiWebdavProperties2/getFileProperties.feature:411](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L411) - -#### [downloading an old version of a file returns 501](https://github.com/owncloud/ocis/issues/2261) - -- [apiVersions/fileVersions.feature:444](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersions.feature#L444) -- [apiVersions/fileVersions.feature:462](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersions.feature#L462) -- [apiVersions/fileVersionsSharingToShares.feature:306](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L306) - -#### [file versions do not report the version author](https://github.com/owncloud/ocis/issues/2914) - -- [apiVersions/fileVersionAuthor.feature:14](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L14) -- [apiVersions/fileVersionAuthor.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L37) -- [apiVersions/fileVersionAuthor.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L58) -- [apiVersions/fileVersionAuthor.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L78) -- [apiVersions/fileVersionAuthor.feature:104](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L104) -- [apiVersions/fileVersionAuthor.feature:129](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L129) -- [apiVersions/fileVersionAuthor.feature:154](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L154) -- [apiVersions/fileVersionAuthor.feature:180](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L180) -- [apiVersions/fileVersionAuthor.feature:223](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L223) - -### Sync - -Synchronization features like etag propagation, setting mtime and locking files - -#### [Uploading an old method chunked file with checksum should fail using new DAV path](https://github.com/owncloud/ocis/issues/2323) - -- [apiMain/checksums.feature:381](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L381) -- [apiMain/checksums.feature:386](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L386) - -#### [Webdav LOCK operations](https://github.com/owncloud/ocis/issues/1284) - -- [apiWebdavLocks/exclusiveLocks.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L43) -- [apiWebdavLocks/exclusiveLocks.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L44) -- [apiWebdavLocks/exclusiveLocks.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L45) -- [apiWebdavLocks/exclusiveLocks.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L46) -- [apiWebdavLocks/exclusiveLocks.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L51) -- [apiWebdavLocks/exclusiveLocks.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L52) -- [apiWebdavLocks/exclusiveLocks.feature:69](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L69) -- [apiWebdavLocks/exclusiveLocks.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L70) -- [apiWebdavLocks/exclusiveLocks.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L71) -- [apiWebdavLocks/exclusiveLocks.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L72) -- [apiWebdavLocks/exclusiveLocks.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L77) -- [apiWebdavLocks/exclusiveLocks.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L78) -- [apiWebdavLocks/exclusiveLocks.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L94) -- [apiWebdavLocks/exclusiveLocks.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L95) -- [apiWebdavLocks/exclusiveLocks.feature:96](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L96) -- [apiWebdavLocks/exclusiveLocks.feature:97](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L97) -- [apiWebdavLocks/exclusiveLocks.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L102) -- [apiWebdavLocks/exclusiveLocks.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L103) -- [apiWebdavLocks/exclusiveLocks.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L120) -- [apiWebdavLocks/exclusiveLocks.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L121) -- [apiWebdavLocks/exclusiveLocks.feature:122](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L122) -- [apiWebdavLocks/exclusiveLocks.feature:123](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L123) -- [apiWebdavLocks/exclusiveLocks.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L128) -- [apiWebdavLocks/exclusiveLocks.feature:129](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L129) -- [apiWebdavLocks/exclusiveLocks.feature:147](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L147) -- [apiWebdavLocks/exclusiveLocks.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L148) -- [apiWebdavLocks/exclusiveLocks.feature:149](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L149) -- [apiWebdavLocks/exclusiveLocks.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L150) -- [apiWebdavLocks/exclusiveLocks.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L155) -- [apiWebdavLocks/exclusiveLocks.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L156) -- [apiWebdavLocks/exclusiveLocks.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L174) -- [apiWebdavLocks/exclusiveLocks.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L175) -- [apiWebdavLocks/exclusiveLocks.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L176) -- [apiWebdavLocks/exclusiveLocks.feature:177](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L177) -- [apiWebdavLocks/exclusiveLocks.feature:182](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L182) -- [apiWebdavLocks/exclusiveLocks.feature:183](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L183) -- [apiWebdavLocks/exclusiveLocks.feature:201](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L201) -- [apiWebdavLocks/exclusiveLocks.feature:202](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L202) -- [apiWebdavLocks/exclusiveLocks.feature:203](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L203) -- [apiWebdavLocks/exclusiveLocks.feature:204](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L204) -- [apiWebdavLocks/exclusiveLocks.feature:209](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L209) -- [apiWebdavLocks/exclusiveLocks.feature:210](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L210) -- [apiWebdavLocks/folder.feature:18](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L18) -- [apiWebdavLocks/folder.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L19) -- [apiWebdavLocks/folder.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L20) -- [apiWebdavLocks/folder.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L21) -- [apiWebdavLocks/folder.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L26) -- [apiWebdavLocks/folder.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L27) -- [apiWebdavLocks/folder.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L41) -- [apiWebdavLocks/folder.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L42) -- [apiWebdavLocks/folder.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L43) -- [apiWebdavLocks/folder.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L44) -- [apiWebdavLocks/folder.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L49) -- [apiWebdavLocks/folder.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L50) -- [apiWebdavLocks/folder.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L63) -- [apiWebdavLocks/folder.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L64) -- [apiWebdavLocks/folder.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L65) -- [apiWebdavLocks/folder.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L66) -- [apiWebdavLocks/folder.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L71) -- [apiWebdavLocks/folder.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L72) -- [apiWebdavLocks/folder.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L86) -- [apiWebdavLocks/folder.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L87) -- [apiWebdavLocks/folder.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L88) -- [apiWebdavLocks/folder.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L89) -- [apiWebdavLocks/folder.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L94) -- [apiWebdavLocks/folder.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L95) -- [apiWebdavLocks/folder.feature:110](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L110) -- [apiWebdavLocks/folder.feature:111](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L111) -- [apiWebdavLocks/folder.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L112) -- [apiWebdavLocks/folder.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L113) -- [apiWebdavLocks/folder.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L118) -- [apiWebdavLocks/folder.feature:119](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L119) -- [apiWebdavLocks/folder.feature:135](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L135) -- [apiWebdavLocks/folder.feature:136](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L136) -- [apiWebdavLocks/folder.feature:137](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L137) -- [apiWebdavLocks/folder.feature:138](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L138) -- [apiWebdavLocks/folder.feature:144](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L144) -- [apiWebdavLocks/folder.feature:143](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L143) -- [apiWebdavLocks/folder.feature:161](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L161) -- [apiWebdavLocks/folder.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L162) -- [apiWebdavLocks/folder.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L163) -- [apiWebdavLocks/folder.feature:164](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L164) -- [apiWebdavLocks/folder.feature:169](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L169) -- [apiWebdavLocks/folder.feature:170](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L170) -- [apiWebdavLocks/publicLink.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L33) -- [apiWebdavLocks/publicLink.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L34) -- [apiWebdavLocks/publicLink.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L35) -- [apiWebdavLocks/publicLink.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L36) -- [apiWebdavLocks/publicLink.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L41) -- [apiWebdavLocks/publicLink.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L42) -- [apiWebdavLocks/publicLink.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L43) -- [apiWebdavLocks/publicLink.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L44) -- [apiWebdavLocks/publicLink.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L60) -- [apiWebdavLocks/publicLink.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L61) -- [apiWebdavLocks/publicLink.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L78) -- [apiWebdavLocks/publicLink.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L79) -- [apiWebdavLocks/publicLink.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L98) -- [apiWebdavLocks/publicLink.feature:99](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L99) -- [apiWebdavLocks/publicLink.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L117) -- [apiWebdavLocks/publicLink.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L118) -- [apiWebdavLocks/publicLinkLockdiscovery.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L23) -- [apiWebdavLocks/publicLinkLockdiscovery.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L24) -- [apiWebdavLocks/publicLinkLockdiscovery.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L39) -- [apiWebdavLocks/publicLinkLockdiscovery.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L40) -- [apiWebdavLocks/publicLinkLockdiscovery.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L55) -- [apiWebdavLocks/publicLinkLockdiscovery.feature:56](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L56) -- [apiWebdavLocks/publicLinkLockdiscovery.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L71) -- [apiWebdavLocks/publicLinkLockdiscovery.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L72) -- [apiWebdavLocks/publicLinkLockdiscovery.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L87) -- [apiWebdavLocks/publicLinkLockdiscovery.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L88) -- [apiWebdavLocks/publicLinkLockdiscovery.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L103) -- [apiWebdavLocks/publicLinkLockdiscovery.feature:104](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L104) -- [apiWebdavLocks/publicLinkLockdiscovery.feature:119](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L119) -- [apiWebdavLocks/publicLinkLockdiscovery.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L120) -- [apiWebdavLocks/requestsWithToken.feature:73](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L73) -- [apiWebdavLocks/requestsWithToken.feature:74](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L74) -- [apiWebdavLocks/requestsWithToken.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L75) -- [apiWebdavLocks/requestsWithToken.feature:76](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L76) -- [apiWebdavLocks/requestsWithToken.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L81) -- [apiWebdavLocks/requestsWithToken.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L82) -- [apiWebdavLocks/requestsWithToken.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L100) -- [apiWebdavLocks/requestsWithToken.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L101) -- [apiWebdavLocks/requestsWithToken.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L102) -- [apiWebdavLocks/requestsWithToken.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L103) -- [apiWebdavLocks/requestsWithToken.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L108) -- [apiWebdavLocks/requestsWithToken.feature:109](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L109) -- [apiWebdavLocks/requestsWithToken.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L131) -- [apiWebdavLocks/requestsWithToken.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L132) -- [apiWebdavLocks/requestsWithToken.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L156) -- [apiWebdavLocks/requestsWithToken.feature:157](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L157) -- [apiWebdavLocks/requestsWithToken.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L162) -- [apiWebdavLocks2/resharedSharesToShares.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L31) -- [apiWebdavLocks2/resharedSharesToShares.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L32) -- [apiWebdavLocks2/resharedSharesToShares.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L33) -- [apiWebdavLocks2/resharedSharesToShares.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L34) -- [apiWebdavLocks2/resharedSharesToShares.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L61) -- [apiWebdavLocks2/resharedSharesToShares.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L62) -- [apiWebdavLocks2/resharedSharesToShares.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L63) -- [apiWebdavLocks2/resharedSharesToShares.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L64) -- [apiWebdavLocks2/resharedSharesToShares.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L87) -- [apiWebdavLocks2/resharedSharesToShares.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L88) -- [apiWebdavLocks2/resharedSharesToShares.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L89) -- [apiWebdavLocks2/resharedSharesToShares.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L90) -- [apiWebdavLocks2/resharedSharesToShares.feature:109](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L109) -- [apiWebdavLocks2/resharedSharesToShares.feature:110](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L110) -- [apiWebdavLocks2/resharedSharesToShares.feature:111](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L111) -- [apiWebdavLocks2/resharedSharesToShares.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L112) -- [apiWebdavLocks2/resharedSharesToShares.feature:136](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L136) -- [apiWebdavLocks2/resharedSharesToShares.feature:137](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L137) -- [apiWebdavLocks2/resharedSharesToShares.feature:138](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L138) -- [apiWebdavLocks2/resharedSharesToShares.feature:139](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L139) -- [apiWebdavLocks2/setTimeout.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L24) -- [apiWebdavLocks2/setTimeout.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L25) -- [apiWebdavLocks2/setTimeout.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L26) -- [apiWebdavLocks2/setTimeout.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L27) -- [apiWebdavLocks2/setTimeout.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L32) -- [apiWebdavLocks2/setTimeout.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L33) -- [apiWebdavLocks2/setTimeout.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L47) -- [apiWebdavLocks2/setTimeout.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L48) -- [apiWebdavLocks2/setTimeout.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L49) -- [apiWebdavLocks2/setTimeout.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L50) -- [apiWebdavLocks2/setTimeout.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L51) -- [apiWebdavLocks2/setTimeout.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L52) -- [apiWebdavLocks2/setTimeout.feature:53](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L53) -- [apiWebdavLocks2/setTimeout.feature:54](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L54) -- [apiWebdavLocks2/setTimeout.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L55) -- [apiWebdavLocks2/setTimeout.feature:56](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L56) -- [apiWebdavLocks2/setTimeout.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L61) -- [apiWebdavLocks2/setTimeout.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L62) -- [apiWebdavLocks2/setTimeout.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L63) -- [apiWebdavLocks2/setTimeout.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L64) -- [apiWebdavLocks2/setTimeout.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L65) -- [apiWebdavLocks2/setTimeout.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L81) -- [apiWebdavLocks2/setTimeout.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L82) -- [apiWebdavLocks2/setTimeout.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L83) -- [apiWebdavLocks2/setTimeout.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L84) -- [apiWebdavLocks2/setTimeout.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L85) -- [apiWebdavLocks2/setTimeout.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L86) -- [apiWebdavLocks2/setTimeout.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L87) -- [apiWebdavLocks2/setTimeout.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L88) -- [apiWebdavLocks2/setTimeout.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L89) -- [apiWebdavLocks2/setTimeout.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L90) -- [apiWebdavLocks2/setTimeout.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L91) -- [apiWebdavLocks2/setTimeout.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L92) -- [apiWebdavLocks2/setTimeout.feature:97](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L97) -- [apiWebdavLocks2/setTimeout.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L98) -- [apiWebdavLocks2/setTimeout.feature:99](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L99) -- [apiWebdavLocks2/setTimeout.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L100) -- [apiWebdavLocks2/setTimeout.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L101) -- [apiWebdavLocks2/setTimeout.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L102) -- [apiWebdavLocks2/setTimeout.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L117) -- [apiWebdavLocks2/setTimeout.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L118) -- [apiWebdavLocks2/setTimeout.feature:119](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L119) -- [apiWebdavLocks2/setTimeout.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L120) -- [apiWebdavLocks2/setTimeout.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L121) -- [apiWebdavLocks2/setTimeout.feature:122](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L122) -- [apiWebdavLocks2/setTimeout.feature:123](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L123) -- [apiWebdavLocks2/setTimeout.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L124) -- [apiWebdavLocks2/setTimeout.feature:125](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L125) -- [apiWebdavLocks2/setTimeout.feature:126](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L126) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L32) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L33) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L34) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L35) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L36) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L37) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L38) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L39) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L40) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L41) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L66) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L67) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L68) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:69](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L69) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L70) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L71) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L72) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:73](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L73) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:74](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L74) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L75) -- [apiWebdavLocks3/independentLocks.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L25) -- [apiWebdavLocks3/independentLocks.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L26) -- [apiWebdavLocks3/independentLocks.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L27) -- [apiWebdavLocks3/independentLocks.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L28) -- [apiWebdavLocks3/independentLocks.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L33) -- [apiWebdavLocks3/independentLocks.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L34) -- [apiWebdavLocks3/independentLocks.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L51) -- [apiWebdavLocks3/independentLocks.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L52) -- [apiWebdavLocks3/independentLocks.feature:53](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L53) -- [apiWebdavLocks3/independentLocks.feature:54](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L54) -- [apiWebdavLocks3/independentLocks.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L59) -- [apiWebdavLocks3/independentLocks.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L60) -- [apiWebdavLocks3/independentLocks.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L77) -- [apiWebdavLocks3/independentLocks.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L78) -- [apiWebdavLocks3/independentLocks.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L79) -- [apiWebdavLocks3/independentLocks.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L80) -- [apiWebdavLocks3/independentLocks.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L85) -- [apiWebdavLocks3/independentLocks.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L86) -- [apiWebdavLocks3/independentLocks.feature:105](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L105) -- [apiWebdavLocks3/independentLocks.feature:106](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L106) -- [apiWebdavLocks3/independentLocks.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L107) -- [apiWebdavLocks3/independentLocks.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L108) -- [apiWebdavLocks3/independentLocks.feature:109](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L109) -- [apiWebdavLocks3/independentLocks.feature:110](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L110) -- [apiWebdavLocks3/independentLocks.feature:111](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L111) -- [apiWebdavLocks3/independentLocks.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L112) -- [apiWebdavLocks3/independentLocks.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L117) -- [apiWebdavLocks3/independentLocks.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L118) -- [apiWebdavLocks3/independentLocks.feature:119](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L119) -- [apiWebdavLocks3/independentLocks.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L120) -- [apiWebdavLocks3/independentLocksShareToShares.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L28) -- [apiWebdavLocks3/independentLocksShareToShares.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L29) -- [apiWebdavLocks3/independentLocksShareToShares.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L30) -- [apiWebdavLocks3/independentLocksShareToShares.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L31) -- [apiWebdavLocks3/independentLocksShareToShares.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L36) -- [apiWebdavLocks3/independentLocksShareToShares.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L37) -- [apiWebdavLocks3/independentLocksShareToShares.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L57) -- [apiWebdavLocks3/independentLocksShareToShares.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L58) -- [apiWebdavLocks3/independentLocksShareToShares.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L59) -- [apiWebdavLocks3/independentLocksShareToShares.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L60) -- [apiWebdavLocks3/independentLocksShareToShares.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L65) -- [apiWebdavLocks3/independentLocksShareToShares.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L66) -- [apiWebdavLocks3/independentLocksShareToShares.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L87) -- [apiWebdavLocks3/independentLocksShareToShares.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L88) -- [apiWebdavLocks3/independentLocksShareToShares.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L89) -- [apiWebdavLocks3/independentLocksShareToShares.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L90) -- [apiWebdavLocks3/independentLocksShareToShares.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L95) -- [apiWebdavLocks3/independentLocksShareToShares.feature:96](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L96) -- [apiWebdavLocks3/independentLocksShareToShares.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L116) -- [apiWebdavLocks3/independentLocksShareToShares.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L117) -- [apiWebdavLocks3/independentLocksShareToShares.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L118) -- [apiWebdavLocks3/independentLocksShareToShares.feature:119](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L119) -- [apiWebdavLocks3/independentLocksShareToShares.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L124) -- [apiWebdavLocks3/independentLocksShareToShares.feature:125](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L125) -- [apiWebdavLocksUnlock/unlock.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L30) -- [apiWebdavLocksUnlock/unlock.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L31) -- [apiWebdavLocksUnlock/unlock.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L46) -- [apiWebdavLocksUnlock/unlock.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L47) -- [apiWebdavLocksUnlock/unlock.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L52) -- [apiWebdavLocksUnlock/unlock.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L68) -- [apiWebdavLocksUnlock/unlock.feature:69](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L69) -- [apiWebdavLocksUnlock/unlock.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L70) -- [apiWebdavLocksUnlock/unlock.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L71) -- [apiWebdavLocksUnlock/unlock.feature:76](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L76) -- [apiWebdavLocksUnlock/unlock.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L77) -- [apiWebdavLocksUnlock/unlock.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L91) -- [apiWebdavLocksUnlock/unlock.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L92) -- [apiWebdavLocksUnlock/unlock.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L116) -- [apiWebdavLocksUnlock/unlock.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L117) -- [apiWebdavLocksUnlock/unlock.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L118) -- [apiWebdavLocksUnlock/unlock.feature:119](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L119) -- [apiWebdavLocksUnlock/unlock.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L124) -- [apiWebdavLocksUnlock/unlock.feature:125](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L125) -- [apiWebdavLocksUnlock/unlock.feature:147](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L147) -- [apiWebdavLocksUnlock/unlock.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L148) -- [apiWebdavLocksUnlock/unlock.feature:149](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L149) -- [apiWebdavLocksUnlock/unlock.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L150) -- [apiWebdavLocksUnlock/unlock.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L155) -- [apiWebdavLocksUnlock/unlock.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L156) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L28) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L29) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L30) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L31) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L44) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L45) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L60) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L61) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L62) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L63) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L68) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:69](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L69) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L90) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L91) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L92) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L93) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L98) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:99](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L99) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L115) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L116) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L117) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L118) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L131) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L132) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L148) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:149](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L149) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L150) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:151](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L151) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:164](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L164) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:165](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L165) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:180](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L180) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:181](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L181) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:182](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L182) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:183](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L183) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:188](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L188) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:189](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L189) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:210](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L210) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:211](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L211) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:212](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L212) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:213](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L213) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L218) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:219](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L219) - -### Share - -File and sync features in a shared scenario - -#### [Searching sharee with displayname](https://github.com/owncloud/ocis/issues/547) - -- [apiSharees/sharees.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L32) -- [apiSharees/sharees.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L33) -- [apiSharees/sharees.feature:54](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L54) -- [apiSharees/sharees.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L55) -- [apiSharees/sharees.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L100) -- [apiSharees/sharees.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L101) -- [apiSharees/sharees.feature:350](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L350) -- [apiSharees/sharees.feature:351](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L351) -- [apiSharees/sharees.feature:370](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L370) -- [apiSharees/sharees.feature:371](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L371) -- [apiSharees/sharees.feature:390](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L390) -- [apiSharees/sharees.feature:391](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L391) -- [apiSharees/sharees.feature:410](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L410) -- [apiSharees/sharees.feature:411](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L411) -- [apiSharees/sharees.feature:430](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L430) -- [apiSharees/sharees.feature:431](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L431) -- [apiSharees/sharees.feature:583](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L583) -- [apiSharees/sharees.feature:584](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L584) -- [apiSharees/sharees.feature:655](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L655) -- [apiSharees/sharees.feature:656](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L656) -- [apiSharees/sharees.feature:680](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L680) -- [apiSharees/sharees.feature:681](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L681) - -#### User cannot create a folder named Share - -- [apiShareManagementToShares/acceptShares.feature:366](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L366) -- [apiShareManagementToShares/acceptShares.feature:402](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L402) - -#### [cannot accept identical pending shares from different user serially](https://github.com/owncloud/ocis/issues/2131) - -- [apiShareManagementToShares/acceptShares.feature:304](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L304) -- [apiShareCreateSpecialToShares1/createShareUniqueReceivedNames.feature:15](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareUniqueReceivedNames.feature#L15) -- [apiShareManagementToShares/acceptShares.feature:599](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L599) -- [apiShareManagementToShares/acceptShares.feature:664](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L664) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L162) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L163) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:202](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L202) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:203](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L203) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L45) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L46) - -#### [file_target of a auto-renamed file is not correct directly after sharing](https://github.com/owncloud/core/issues/32322) - -- [apiShareManagementToShares/mergeShare.feature:105](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/mergeShare.feature#L105) - -#### [Cannot move a file to a shared folder](https://github.com/owncloud/ocis/issues/2146) - -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:500](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L500) - -#### [File deletion using dav gives unique string in filename in the trashbin](https://github.com/owncloud/product/issues/178) - -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L67) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L81) - -cannot share a folder with create permission - -#### [Listing shares via ocs API does not show path for parent folders](https://github.com/owncloud/ocis/issues/1231) - -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:139](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L139) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:151](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L151) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:185](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L185) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:186](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L186) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:187](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L187) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:188](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L188) - -#### [OCS error message for attempting to access share via share id as an unauthorized user is not informative](https://github.com/owncloud/ocis/issues/1233) - -- [apiShareOperationsToShares1/gettingShares.feature:184](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingShares.feature#L184) -- [apiShareOperationsToShares1/gettingShares.feature:185](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingShares.feature#L185) - -#### [Listing shares via ocs API does not show path for parent folders](https://github.com/owncloud/ocis/issues/1231) - -- [apiShareOperationsToShares1/gettingShares.feature:222](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingShares.feature#L222) -- [apiShareOperationsToShares1/gettingShares.feature:223](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/gettingShares.feature#L223) - -#### [Public link enforce permissions](https://github.com/owncloud/ocis/issues/1269) - -- [apiSharePublicLink1/accessToPublicLinkShare.feature:10](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/accessToPublicLinkShare.feature#L10) -- [apiSharePublicLink1/accessToPublicLinkShare.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/accessToPublicLinkShare.feature#L20) -- [apiSharePublicLink1/accessToPublicLinkShare.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/accessToPublicLinkShare.feature#L30) -- [apiSharePublicLink1/accessToPublicLinkShare.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/accessToPublicLinkShare.feature#L45) -- [apiSharePublicLink1/createPublicLinkShare.feature:587](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L587) -- [apiSharePublicLink1/createPublicLinkShare.feature:608](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L608) - -#### [download previews of other users file](https://github.com/owncloud/ocis/issues/2071) - -- [apiWebdavPreviews/previews.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavPreviews/previews.feature#L101) - -#### [different error message detail for previews of folder](https://github.com/owncloud/ocis/issues/2064) - -- [apiWebdavPreviews/previews.feature:110](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavPreviews/previews.feature#L110) - -#### [Requesting a file preview when it is disabled by the administrator](https://github.com/owncloud/ocis/issues/192) - -- [apiWebdavPreviews/previews.feature:125](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavPreviews/previews.feature#L125) - -#### [Cannot set/unset maximum and minimum preview dimensions](https://github.com/owncloud/ocis/issues/2070) - -- [apiWebdavPreviews/previews.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavPreviews/previews.feature#L133) -- [apiWebdavPreviews/previews.feature:161](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavPreviews/previews.feature#L161) -- [apiWebdavPreviews/previews.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavPreviews/previews.feature#L162) -- [apiWebdavPreviews/previews.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavPreviews/previews.feature#L163) -- [apiWebdavPreviews/previews.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavPreviews/previews.feature#L175) -- [apiWebdavPreviews/previews.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavPreviews/previews.feature#L176) - -#### [creating public links with permissions fails](https://github.com/owncloud/product/issues/252) - -- [apiSharePublicLink1/changingPublicLinkShare.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/changingPublicLinkShare.feature#L30) -- [apiSharePublicLink1/changingPublicLinkShare.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/changingPublicLinkShare.feature#L51) -- [apiSharePublicLink1/changingPublicLinkShare.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/changingPublicLinkShare.feature#L90) - -#### [various sharing settings cannot be set](https://github.com/owncloud/ocis/issues/1328) - -- [apiSharePublicLink1/createPublicLinkShare.feature:375](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L375) -- [apiSharePublicLink1/createPublicLinkShare.feature:376](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L376) -- [apiSharePublicLink1/createPublicLinkShare.feature:566](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L566) -- [apiSharePublicLink1/createPublicLinkShare.feature:567](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink1/createPublicLinkShare.feature#L567) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:212](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L212) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:213](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L213) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:214](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L214) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L215) -- [apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature#L44) -- [apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature#L45) -- [apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:74](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature#L74) -- [apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature#L75) -- [apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:104](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature#L104) -- [apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:105](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature#L105) - -#### [copying a folder within a public link folder to folder with same name as an already existing file overwrites the parent file](https://github.com/owncloud/ocis/issues/1232) - -- [apiSharePublicLink2/copyFromPublicLink.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/copyFromPublicLink.feature#L59) -- [apiSharePublicLink2/copyFromPublicLink.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/copyFromPublicLink.feature#L84) -- [apiSharePublicLink2/copyFromPublicLink.feature:165](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/copyFromPublicLink.feature#L165) -- [apiSharePublicLink2/copyFromPublicLink.feature:166](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/copyFromPublicLink.feature#L166) -- [apiSharePublicLink2/copyFromPublicLink.feature:181](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/copyFromPublicLink.feature#L181) -- [apiSharePublicLink2/copyFromPublicLink.feature:182](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/copyFromPublicLink.feature#L182) -- [apiSharePublicLink2/updatePublicLinkShare.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/updatePublicLinkShare.feature#L45) -- [apiSharePublicLink2/updatePublicLinkShare.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/updatePublicLinkShare.feature#L46) - -#### [OCIS share permissions not enforced](https://github.com/owncloud/product/issues/270) - -- [apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:159](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature#L159) -- [apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:160](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature#L160) -- [apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:181](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature#L181) -- [apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature:182](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature#L182) - -#### [Adding public upload to a read only shared folder as a recipient is allowed ](https://github.com/owncloud/ocis/issues/2164) - -- [apiSharePublicLink2/updatePublicLinkShare.feature:338](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/updatePublicLinkShare.feature#L338) -- [apiSharePublicLink2/updatePublicLinkShare.feature:339](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/updatePublicLinkShare.feature#L339) -- [apiSharePublicLink2/updatePublicLinkShare.feature:398](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/updatePublicLinkShare.feature#L398) -- [apiSharePublicLink2/updatePublicLinkShare.feature:399](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/updatePublicLinkShare.feature#L399) - -#### [Upload-only shares must not overwrite but create a separate file](https://github.com/owncloud/ocis-reva/issues/286) - -- [apiSharePublicLink2/uploadToPublicLinkShare.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/uploadToPublicLinkShare.feature#L24) -- [apiSharePublicLink2/uploadToPublicLinkShare.feature:277](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/uploadToPublicLinkShare.feature#L277) - -#### [Set quota over settings](https://github.com/owncloud/ocis/issues/1290) - -- [apiSharePublicLink2/uploadToPublicLinkShare.feature:160](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/uploadToPublicLinkShare.feature#L160) -- [apiSharePublicLink2/uploadToPublicLinkShare.feature:179](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/uploadToPublicLinkShare.feature#L179) - -#### [various sharing settings cannot be set](https://github.com/owncloud/ocis/issues/1328) - -- [apiSharePublicLink2/uploadToPublicLinkShare.feature:198](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/uploadToPublicLinkShare.feature#L198) - -#### [various sharing settings cannot be set](https://github.com/owncloud/ocis/issues/1328) - -- [apiShareReshareToShares2/reShareWhenShareWithOnlyMembershipGroups.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares2/reShareWhenShareWithOnlyMembershipGroups.feature#L27) -- [apiShareReshareToShares2/reShareWhenShareWithOnlyMembershipGroups.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares2/reShareWhenShareWithOnlyMembershipGroups.feature#L28) -- [apiShareReshareToShares2/reShareWhenShareWithOnlyMembershipGroups.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares2/reShareWhenShareWithOnlyMembershipGroups.feature#L45) -- [apiShareReshareToShares2/reShareWhenShareWithOnlyMembershipGroups.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares2/reShareWhenShareWithOnlyMembershipGroups.feature#L46) -- [apiShareReshareToShares2/reShareDisabled.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares2/reShareDisabled.feature#L27) -- [apiShareReshareToShares2/reShareDisabled.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares2/reShareDisabled.feature#L28) - -#### [share permissions are not enforced](https://github.com/owncloud/product/issues/270) - -- [apiShareManagementToShares/mergeShare.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/mergeShare.feature#L124) -- [apiShareReshareToShares3/reShareUpdate.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareUpdate.feature#L61) -- [apiShareReshareToShares3/reShareUpdate.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareUpdate.feature#L62) - -#### [500 status code on update share](https://github.com/owncloud/ocis/issues/2011) - -- [apiShareReshareToShares3/reShareUpdate.feature:152](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareUpdate.feature#L152) -- [apiShareReshareToShares3/reShareUpdate.feature:153](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareUpdate.feature#L153) - -#### [deleting a file inside a received shared folder is moved to the trash-bin of the sharer not the receiver](https://github.com/owncloud/ocis/issues/1124) - -- [apiTrashbin/trashbinSharingToShares.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L29) -- [apiTrashbin/trashbinSharingToShares.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L46) -- [apiTrashbin/trashbinSharingToShares.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L51) -- [apiTrashbin/trashbinSharingToShares.feature:73](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L73) -- [apiTrashbin/trashbinSharingToShares.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L78) -- [apiTrashbin/trashbinSharingToShares.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L100) -- [apiTrashbin/trashbinSharingToShares.feature:105](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L105) -- [apiTrashbin/trashbinSharingToShares.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L128) -- [apiTrashbin/trashbinSharingToShares.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L133) -- [apiTrashbin/trashbinSharingToShares.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L156) -- [apiTrashbin/trashbinSharingToShares.feature:161](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L161) -- [apiTrashbin/trashbinSharingToShares.feature:184](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L184) -- [apiTrashbin/trashbinSharingToShares.feature:189](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L189) -- [apiTrashbin/trashbinSharingToShares.feature:212](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L212) -- [apiTrashbin/trashbinSharingToShares.feature:236](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinSharingToShares.feature#L236) - -#### [Private link support](https://github.com/owncloud/product/issues/201) - -#### [oc:privatelink property not returned in webdav responses](https://github.com/owncloud/product/issues/262) - -- [apiWebdavProperties2/getFileProperties.feature:306](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L306) -- [apiWebdavProperties2/getFileProperties.feature:307](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L307) -- [apiWebdavProperties2/getFileProperties.feature:312](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L312) - -#### [changing user quota gives ocs status 103 / Cannot set quota](https://github.com/owncloud/product/issues/247) - -- [apiShareOperationsToShares2/uploadToShare.feature:245](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L245) -- [apiShareOperationsToShares2/uploadToShare.feature:246](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L246) -- [apiShareOperationsToShares2/uploadToShare.feature:251](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L251) - -#### [not possible to move file into a received folder](https://github.com/owncloud/ocis/issues/764) - -- [apiShareOperationsToShares1/changingFilesShare.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L25) -- [apiShareOperationsToShares1/changingFilesShare.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L26) -- [apiShareOperationsToShares1/changingFilesShare.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L31) -- [apiShareOperationsToShares1/changingFilesShare.feature:119](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L119) -- [apiShareOperationsToShares1/changingFilesShare.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L120) -- [apiShareOperationsToShares1/changingFilesShare.feature:125](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L125) -- [apiShareOperationsToShares1/changingFilesShare.feature:146](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L146) -- [apiShareOperationsToShares1/changingFilesShare.feature:147](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L147) -- [apiShareOperationsToShares1/changingFilesShare.feature:152](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L152) - -Scenario Outline: Moving a file into a shared folder as the sharee and as the sharer - -- [apiWebdavMove2/moveShareOnOcis.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L30) -- [apiWebdavMove2/moveShareOnOcis.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L32) - Scenario Outline: Moving a file out of a shared folder as the sharee and as the sharer -- [apiWebdavMove2/moveShareOnOcis.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L60) -- [apiWebdavMove2/moveShareOnOcis.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L62) - Scenario Outline: Moving a folder into a shared folder as the sharee and as the sharer -- [apiWebdavMove2/moveShareOnOcis.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L91) -- [apiWebdavMove2/moveShareOnOcis.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L93) - Scenario Outline: Moving a folder out of a shared folder as the sharee and as the sharer -- [apiWebdavMove2/moveShareOnOcis.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L124) -- [apiWebdavMove2/moveShareOnOcis.feature:126](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L126) - Scenario Outline: Moving a file to a shared folder with no permissions -- [apiWebdavMove2/moveShareOnOcis.feature:152](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L152) -- [apiWebdavMove2/moveShareOnOcis.feature:153](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L153) - -#### [restoring an older version of a shared file deletes the share](https://github.com/owncloud/ocis/issues/765) - -- [apiShareManagementToShares/acceptShares.feature:588](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L588) - -#### [not possible to move file into a received folder](https://github.com/owncloud/ocis/issues/764) - -- [apiVersions/fileVersionsSharingToShares.feature:220](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L220) -- [apiVersions/fileVersionsSharingToShares.feature:221](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L221) - -#### [Expiration date for shares is not implemented](https://github.com/owncloud/ocis/issues/1250) - -#### Expiration date of user shares - -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L52) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:53](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L53) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:76](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L76) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L77) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L102) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L103) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L128) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:129](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L129) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:279](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L279) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:280](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L280) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:301](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L301) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:302](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L302) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:323](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L323) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:324](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L324) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:346](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L346) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:347](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L347) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:363](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L363) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:364](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L364) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:380](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L380) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:381](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L381) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:576](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L576) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:577](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L577) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:599](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L599) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:600](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L600) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:601](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L601) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:602](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L602) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:603](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L603) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:624](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L624) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:625](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L625) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:626](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L626) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:627](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L627) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:628](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L628) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:629](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L629) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:630](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L630) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:631](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L631) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:632](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L632) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:633](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L633) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:634](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L634) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:635](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L635) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:656](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L656) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:657](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L657) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:658](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L658) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:659](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L659) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:660](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L660) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:661](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L661) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:682](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L682) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:683](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L683) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:684](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L684) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:685](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L685) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:686](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L686) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:687](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L687) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:708](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L708) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:709](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L709) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:732](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L732) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:733](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L733) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:756](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L756) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:757](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L757) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L34) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L35) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L86) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L87) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:143](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L143) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:144](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L144) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:201](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L201) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:202](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L202) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:203](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L203) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:204](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L204) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:287](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L287) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:288](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L288) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:318](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L318) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:319](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L319) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:320](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L320) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:321](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L321) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:379](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L379) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:380](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L380) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:381](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L381) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:382](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L382) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:383](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L383) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:384](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L384) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:413](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L413) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:414](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L414) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:415](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L415) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:416](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L416) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:444](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L444) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:445](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L445) - -#### Expiration date of group shares - -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L175) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L176) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:201](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L201) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:202](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L202) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:229](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L229) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:230](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L230) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:258](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L258) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:259](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L259) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:403](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L403) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:404](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L404) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:427](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L427) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:428](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L428) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:451](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L451) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:452](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L452) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:476](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L476) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:477](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L477) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:497](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L497) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:498](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L498) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:518](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L518) -- [apiShareCreateSpecialToShares1/createShareExpirationDate.feature:519](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareExpirationDate.feature#L519) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L60) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L61) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L116) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L117) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:172](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L172) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:173](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L173) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:232](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L232) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:233](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L233) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:234](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L234) -- [apiShareReshareToShares3/reShareWithExpiryDate.feature:235](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L235) - -#### [incorrect ocs(v2) status value when sharing to group that does not exist should be 404, gives 998](https://github.com/owncloud/product/issues/250) - -_ocs: api compatibility, return correct status code_ - -- [apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature#L85) -- [apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature#L86) -- [apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature#L87) -- [apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature#L88) -- [apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature#L89) -- [apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature#L90) - -#### [Share permissions can be updated to any value](https://github.com/owncloud/ocis/issues/2173) - -- [apiShareUpdateToShares/updateShare.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L131) -- [apiShareUpdateToShares/updateShare.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L132) -- [apiShareUpdateToShares/updateShare.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L133) -- [apiShareUpdateToShares/updateShare.feature:134](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L134) -- [apiShareUpdateToShares/updateShare.feature:135](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L135) -- [apiShareUpdateToShares/updateShare.feature:136](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L136) -- [apiShareUpdateToShares/updateShare.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L155) -- [apiShareUpdateToShares/updateShare.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L156) -- [apiShareUpdateToShares/updateShare.feature:157](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L157) -- [apiShareUpdateToShares/updateShare.feature:158](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L158) -- [apiShareUpdateToShares/updateShare.feature:159](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L159) -- [apiShareUpdateToShares/updateShare.feature:160](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L160) - -#### [Cannot move folder/file from one received share to another](https://github.com/owncloud/ocis/issues/2442) - -- [apiShareUpdateToShares/updateShare.feature:242](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L242) -- [apiShareUpdateToShares/updateShare.feature:196](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L196) - -#### [Sharing folder and sub-folder with same user but different permission,the permission of sub-folder is not obeyed ](https://github.com/owncloud/ocis/issues/2440) - -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:304](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L304) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:344](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L344) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:470](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L470) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:510](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L510) - -#### [Empty OCS response for a share create request using a disabled user](https://github.com/owncloud/ocis/issues/2212) - -- [apiShareCreateSpecialToShares2/createShareWithDisabledUser.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithDisabledUser.feature#L20) -- [apiShareCreateSpecialToShares2/createShareWithDisabledUser.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithDisabledUser.feature#L23) - -#### [Sharing seems to work but does not work](https://github.com/owncloud/ocis/issues/1303) - -- [apiShareCreateSpecialToShares1/createShareUniqueReceivedNames.feature:15](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareUniqueReceivedNames.feature#L15) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:727](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L727) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:728](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L728) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:746](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L746) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:747](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L747) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:762](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L762) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:763](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L763) - -#### [reshared resource is not listed for sharee after accepting share](https://github.com/owncloud/ocis/issues/2214) - -- [apiShareReshareToShares2/reShareSubfolder.feature:178](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares2/reShareSubfolder.feature#L178) -- [apiShareReshareToShares2/reShareSubfolder.feature:179](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares2/reShareSubfolder.feature#L179) - -#### [various sharing settings cannot be set](https://github.com/owncloud/ocis/issues/1328) - -- [apiShareUpdateToShares/updateShare.feature:325](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L325) -- [apiShareUpdateToShares/updateShare.feature:326](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L326) -- [apiShareUpdateToShares/updateShare.feature:350](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L350) -- [apiShareUpdateToShares/updateShare.feature:351](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L351) -- [apiShareUpdateToShares/updateShare.feature:369](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L369) -- [apiShareUpdateToShares/updateShare.feature:370](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L370) -- [apiShareUpdateToShares/updateShare.feature:396](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L396) -- [apiShareUpdateToShares/updateShare.feature:397](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L397) -- [apiShareUpdateToShares/updateShare.feature:426](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L426) -- [apiShareUpdateToShares/updateShare.feature:427](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L427) - -#### [Edit user share response has an "name" field](https://github.com/owncloud/ocis/issues/1225) - -- [apiShareUpdateToShares/updateShare.feature:288](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L288) -- [apiShareUpdateToShares/updateShare.feature:289](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L289) - -#### [user can access version metadata of a received share before accepting it](https://github.com/owncloud/ocis/issues/760) - -- [apiVersions/fileVersionsSharingToShares.feature:283](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L283) - -#### [Share lists deleted user as 'user'](https://github.com/owncloud/ocis/issues/903) - -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:662](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L662) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:663](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L663) - -#### [deleting a share with wrong authentication returns OCS status 996 / HTTP 500](https://github.com/owncloud/ocis/issues/1229) - -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:250](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L250) -- [apiShareManagementBasicToShares/deleteShareFromShares.feature:251](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/deleteShareFromShares.feature#L251) - -### User Management - -User and group management features - -#### [Cannot create user with different username and emails](https://github.com/owncloud/product/issues/187) - -special character username not valid - -- [apiTrashbin/trashbinFilesFolders.feature:306](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L306) -- [apiTrashbin/trashbinFilesFolders.feature:307](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L307) -- [apiTrashbin/trashbinFilesFolders.feature:308](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L308) -- [apiTrashbin/trashbinFilesFolders.feature:316](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L316) -- [apiTrashbin/trashbinFilesFolders.feature:317](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L317) -- [apiTrashbin/trashbinFilesFolders.feature:318](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L318) - -#### [incorrect ocs(v2) status value when getting info of share that does not exist should be 404, gives 998](https://github.com/owncloud/product/issues/250) - -_ocs: api compatibility, return correct status code_ - -- [apiShareOperationsToShares2/shareAccessByID.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L48) -- [apiShareOperationsToShares2/shareAccessByID.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L49) -- [apiShareOperationsToShares2/shareAccessByID.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L50) -- [apiShareOperationsToShares2/shareAccessByID.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L51) -- [apiShareOperationsToShares2/shareAccessByID.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L52) -- [apiShareOperationsToShares2/shareAccessByID.feature:53](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L53) -- [apiShareOperationsToShares2/shareAccessByID.feature:54](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L54) -- [apiShareOperationsToShares2/shareAccessByID.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L55) - -### Other - -API, search, favorites, config, capabilities, not existing endpoints, CORS and others - -#### [no robots.txt available](https://github.com/owncloud/ocis/issues/1314) - -- [apiMain/main.feature:5](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/main.feature#L5) Scenario: robots.txt file should be accessible - -#### [Ability to return error messages in Webdav response bodies](https://github.com/owncloud/ocis/issues/1293) - -- [apiAuthOcs/ocsDELETEAuth.feature:10](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsDELETEAuth.feature#L10) Scenario: send DELETE requests to OCS endpoints as admin with wrong password -- [apiAuthOcs/ocsGETAuth.feature:10](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L10) Scenario: using OCS anonymously -- [apiAuthOcs/ocsGETAuth.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L51) Scenario: using OCS with non-admin basic auth -- [apiAuthOcs/ocsGETAuth.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L84) Scenario: using OCS as normal user with wrong password -- [apiAuthOcs/ocsGETAuth.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L115) Scenario:using OCS with admin basic auth -- [apiAuthOcs/ocsGETAuth.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L133) Scenario: using OCS as admin user with wrong password -- [apiAuthOcs/ocsPOSTAuth.feature:10](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsPOSTAuth.feature#L10) Scenario: send POST requests to OCS endpoints as normal user with wrong password -- [apiAuthOcs/ocsPUTAuth.feature:10](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsPUTAuth.feature#L10) Scenario: send PUT request to OCS endpoints as admin with wrong password - -#### [Trying to access another user's file gives http 403 instead of 404](https://github.com/owncloud/ocis/issues/2175) - -_ocdav: api compatibility, return correct status code_ - -- [apiAuthWebDav/webDavMKCOLAuth.feature:54](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMKCOLAuth.feature#L54) Scenario: send MKCOL requests to another user's webDav endpoints as normal user -- [apiAuthWebDav/webDavMKCOLAuth.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMKCOLAuth.feature#L68) Scenario: send MKCOL requests to another user's webDav endpoints as normal user using the spaces WebDAV API - -#### [trying to lock file of another user gives http 200](https://github.com/owncloud/ocis/issues/2176) - -- [apiAuthWebDav/webDavLOCKAuth.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavLOCKAuth.feature#L58) Scenario: send LOCK requests to another user's webDav endpoints as normal user -- [apiAuthWebDav/webDavLOCKAuth.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavLOCKAuth.feature#L70) Scenario: send LOCK requests to another user's webDav endpoints as normal user using the spaces WebDAV API - -#### [Renaming a resource to banned name is allowed](https://github.com/owncloud/ocis/issues/1295) - -_ocdav: api compatibility, return correct status code_ - -- [apiAuthWebDav/webDavMOVEAuth.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMOVEAuth.feature#L57) Scenario: send MOVE requests to another user's webDav endpoints as normal user -- [apiAuthWebDav/webDavMOVEAuth.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMOVEAuth.feature#L66) Scenario: send MOVE requests to another user's webDav endpoints as normal user using the spaces WebDAV API - -#### [send POST requests to another user's webDav endpoints as normal user](https://github.com/owncloud/ocis/issues/1287) - -_ocdav: api compatibility, return correct status code_ - -- [apiAuthWebDav/webDavPOSTAuth.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPOSTAuth.feature#L58) Scenario: send POST requests to another user's webDav endpoints as normal user -- [apiAuthWebDav/webDavPOSTAuth.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPOSTAuth.feature#L67) Scenario: send POST requests to another user's webDav endpoints as normal user using the spaces WebDAV API - -#### Another users space literally does not exist because it is not listed as a space for him, 404 seems correct, expects 403 - -- [apiAuthWebDav/webDavPUTAuth.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPUTAuth.feature#L58) -- [apiAuthWebDav/webDavPUTAuth.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPUTAuth.feature#L70) - -#### [Using double slash in URL to access a folder gives 501 and other status codes](https://github.com/owncloud/ocis/issues/1667) - -- [apiAuthWebDav/webDavSpecialURLs.feature:13](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L13) -- [apiAuthWebDav/webDavSpecialURLs.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L24) -- [apiAuthWebDav/webDavSpecialURLs.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L34) -- [apiAuthWebDav/webDavSpecialURLs.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L45) -- [apiAuthWebDav/webDavSpecialURLs.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L55) -- [apiAuthWebDav/webDavSpecialURLs.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L66) -- [apiAuthWebDav/webDavSpecialURLs.feature:76](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L76) -- [apiAuthWebDav/webDavSpecialURLs.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L88) -- [apiAuthWebDav/webDavSpecialURLs.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L100) -- [apiAuthWebDav/webDavSpecialURLs.feature:111](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L111) -- [apiAuthWebDav/webDavSpecialURLs.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L121) -- [apiAuthWebDav/webDavSpecialURLs.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L132) -- [apiAuthWebDav/webDavSpecialURLs.feature:142](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L142) -- [apiAuthWebDav/webDavSpecialURLs.feature:153](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L153) -- [apiAuthWebDav/webDavSpecialURLs.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L163) -- [apiAuthWebDav/webDavSpecialURLs.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L174) -- [apiAuthWebDav/webDavSpecialURLs.feature:184](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L184) -- [apiAuthWebDav/webDavSpecialURLs.feature:195](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L195) - -#### [Difference in response content of status.php and default capabilities](https://github.com/owncloud/ocis/issues/1286) - -- [apiCapabilities/capabilitiesWithNormalUser.feature:11](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilitiesWithNormalUser.feature#L11) Scenario: getting default capabilities with normal user - -#### [REPORT request not implemented](https://github.com/owncloud/ocis/issues/1330) - -- [apiWebdavOperations/search.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L42) -- [apiWebdavOperations/search.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L43) -- [apiWebdavOperations/search.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L48) -- [apiWebdavOperations/search.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L64) -- [apiWebdavOperations/search.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L65) -- [apiWebdavOperations/search.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L70) -- [apiWebdavOperations/search.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L87) -- [apiWebdavOperations/search.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L88) -- [apiWebdavOperations/search.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L93) -- [apiWebdavOperations/search.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L102) -- [apiWebdavOperations/search.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L103) -- [apiWebdavOperations/search.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L108) -- [apiWebdavOperations/search.feature:126](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L126) -- [apiWebdavOperations/search.feature:127](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L127) -- [apiWebdavOperations/search.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L132) -- [apiWebdavOperations/search.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L150) -- [apiWebdavOperations/search.feature:151](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L151) -- [apiWebdavOperations/search.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L156) -- [apiWebdavOperations/search.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L174) -- [apiWebdavOperations/search.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L175) -- [apiWebdavOperations/search.feature:180](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L180) -- [apiWebdavOperations/search.feature:207](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L207) -- [apiWebdavOperations/search.feature:208](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L208) -- [apiWebdavOperations/search.feature:213](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L213) -- [apiWebdavOperations/search.feature:239](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L239) -- [apiWebdavOperations/search.feature:240](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L240) -- [apiWebdavOperations/search.feature:245](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L245) -- [apiWebdavOperations/search.feature:264](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L264) -- [apiWebdavOperations/search.feature:265](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L265) -- [apiWebdavOperations/search.feature:270](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L270) - -#### [Support for favorites](https://github.com/owncloud/ocis/issues/1228) - -- [apiFavorites/favorites.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L115) -- [apiFavorites/favorites.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L116) -- [apiFavorites/favorites.feature:141](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L141) -- [apiFavorites/favorites.feature:142](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L142) -- [apiFavorites/favorites.feature:267](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L267) -- [apiFavorites/favorites.feature:268](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L268) - -And other missing implementation of favorites - -- [apiFavorites/favorites.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L162) -- [apiFavorites/favorites.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L163) -- [apiFavorites/favorites.feature:187](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L187) -- [apiFavorites/favorites.feature:188](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L188) -- [apiFavorites/favorites.feature:193](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L193) -- [apiFavorites/favorites.feature:220](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L220) -- [apiFavorites/favorites.feature:221](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L221) -- [apiFavorites/favorites.feature:226](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L226) -- [apiFavorites/favoritesSharingToShares.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L82) -- [apiFavorites/favoritesSharingToShares.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L88) - -#### [resource inside Shares dir is not found using the spaces WebDAV API](https://github.com/owncloud/ocis/issues/2968) - -- [apiFavorites/favorites.feature:168](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L168) -- [apiFavorites/favoritesSharingToShares.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L28) -- [apiFavorites/favoritesSharingToShares.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L48) -- [apiFavorites/favoritesSharingToShares.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L67) -- [apiFavorites/favoritesSharingToShares.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L83) -- [apiFavorites/favoritesSharingToShares.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L108) -- [apiMain/checksums.feature:211](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L211) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L51) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L78) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L98) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:125](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L125) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:145](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L145) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:172](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L172) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:212](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L212) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:238](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L238) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:258](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L258) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:285](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L285) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:305](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L305) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:332](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L332) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:352](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L352) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:379](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L379) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:399](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L399) -- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:426](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L426) -- [apiShareOperationsToShares2/uploadToShare.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L47) -- [apiShareOperationsToShares2/uploadToShare.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L78) -- [apiShareOperationsToShares2/uploadToShare.feature:111](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L111) -- [apiShareOperationsToShares2/uploadToShare.feature:140](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L140) -- [apiShareOperationsToShares2/uploadToShare.feature:171](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L171) -- [apiShareOperationsToShares2/uploadToShare.feature:346](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L346) -- [apiShareOperationsToShares2/uploadToShare.feature:347](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L347) -- [apiWebdavProperties1/copyFile.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L89) -- [apiWebdavProperties1/copyFile.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L116) -- [apiWebdavProperties1/copyFile.feature:292](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L292) -- [apiWebdavProperties1/copyFile.feature:315](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L315) -- [apiWebdavProperties1/copyFile.feature:343](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L343) -- [apiWebdavProperties1/copyFile.feature:373](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L373) -- [apiWebdavProperties1/copyFile.feature:402](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L402) -- [apiWebdavProperties1/copyFile.feature:431](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L431) -- [apiWebdavProperties1/copyFile.feature:515](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L515) -- [apiWebdavProperties1/copyFile.feature:548](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L548) -- [apiWebdavProperties1/copyFile.feature:580](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L580) -- [apiWebdavProperties1/copyFile.feature:612](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L612) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L37) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L38) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L39) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L60) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L61) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L62) -- [apiWebdavUploadTUS/uploadToShare.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L31) -- [apiWebdavUploadTUS/uploadToShare.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L50) -- [apiWebdavUploadTUS/uploadToShare.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L72) -- [apiWebdavUploadTUS/uploadToShare.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L93) -- [apiWebdavUploadTUS/uploadToShare.feature:135](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L135) -- [apiWebdavUploadTUS/uploadToShare.feature:159](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L159) -- [apiWebdavUploadTUS/uploadToShare.feature:182](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L182) -- [apiWebdavUploadTUS/uploadToShare.feature:205](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L205) -- [apiWebdavUploadTUS/uploadToShare.feature:230](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L230) -- [apiWebdavUploadTUS/uploadToShare.feature:254](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L254) -- [apiWebdavUploadTUS/uploadToShare.feature:301](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L301) -- [apiWebdavUploadTUS/uploadToShare.feature:326](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L326) -- [apiWebdavEtagPropagation1/moveFileFolder.feature:189](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L189) -- [apiWebdavEtagPropagation1/moveFileFolder.feature:223](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L223) -- [apiWebdavEtagPropagation1/moveFileFolder.feature:264](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L264) -- [apiWebdavEtagPropagation1/moveFileFolder.feature:305](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L305) -- [apiWebdavEtagPropagation1/moveFileFolder.feature:346](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L346) -- [apiWebdavEtagPropagation1/moveFileFolder.feature:387](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L387) -- [apiWebdavEtagPropagation1/deleteFileFolder.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/deleteFileFolder.feature#L120) -- [apiWebdavEtagPropagation1/deleteFileFolder.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/deleteFileFolder.feature#L156) -- [apiWebdavEtagPropagation1/deleteFileFolder.feature:194](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/deleteFileFolder.feature#L194) -- [apiWebdavEtagPropagation1/deleteFileFolder.feature:232](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/deleteFileFolder.feature#L232) -- [apiWebdavEtagPropagation2/copyFileFolder.feature:194](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/copyFileFolder.feature#L194) -- [apiWebdavEtagPropagation2/copyFileFolder.feature:238](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/copyFileFolder.feature#L238) -- [apiWebdavEtagPropagation2/createFolder.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/createFolder.feature#L85) -- [apiWebdavEtagPropagation2/createFolder.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/createFolder.feature#L116) -- [apiWebdavEtagPropagation2/upload.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L83) -- [apiWebdavEtagPropagation2/upload.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L113) -- [apiWebdavEtagPropagation2/upload.feature:144](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L144) -- [apiWebdavEtagPropagation2/upload.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L175) -- [apiWebdavLocks2/resharedSharesToShares.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L117) -- [apiWebdavLocks2/resharedSharesToShares.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L118) -- [apiWebdavLocks2/resharedSharesToShares.feature:144](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L144) -- [apiWebdavLocks2/resharedSharesToShares.feature:145](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L145) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L46) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L47) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L48) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L49) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L50) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L80) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L81) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L82) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L83) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L84) -- [apiShareOperationsToShares1/changingFilesShare.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L98) -- [apiShareOperationsToShares1/changingFilesShare.feature:173](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L173) -- [apiWebdavMove2/moveShareOnOcis.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L38) -- [apiWebdavMove2/moveShareOnOcis.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L39) -- [apiWebdavMove2/moveShareOnOcis.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L68) -- [apiWebdavMove2/moveShareOnOcis.feature:99](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L99) -- [apiWebdavMove2/moveShareOnOcis.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L100) -- [apiWebdavMove2/moveShareOnOcis.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L132) -- [apiWebdavMove2/moveShareOnOcis.feature:158](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L158) -- [apiWebdavMove2/moveShareOnOcis.feature:185](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L185) -- [apiWebdavMove2/moveShareOnOcis.feature:220](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveShareOnOcis.feature#L220) - -#### [WWW-Authenticate header for unauthenticated requests is not clear](https://github.com/owncloud/ocis/issues/2285) - -- [apiWebdavOperations/refuseAccess.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L22) -- [apiWebdavOperations/refuseAccess.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L23) - -#### [wildcard Access-Control-Allow-Origin](https://github.com/owncloud/ocis/issues/1340) - -- [apiAuth/cors.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L24) -- [apiAuth/cors.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L25) -- [apiAuth/cors.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L26) -- [apiAuth/cors.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L27) -- [apiAuth/cors.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L28) -- [apiAuth/cors.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L29) -- [apiAuth/cors.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L30) -- [apiAuth/cors.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L31) -- [apiAuth/cors.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L32) -- [apiAuth/cors.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L33) -- [apiAuth/cors.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L44) -- [apiAuth/cors.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L45) -- [apiAuth/cors.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L46) -- [apiAuth/cors.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L47) -- [apiAuth/cors.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L48) -- [apiAuth/cors.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L49) -- [apiAuth/cors.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L68) -- [apiAuth/cors.feature:69](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L69) -- [apiAuth/cors.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L70) -- [apiAuth/cors.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L71) -- [apiAuth/cors.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L72) -- [apiAuth/cors.feature:73](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L73) -- [apiAuth/cors.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L92) -- [apiAuth/cors.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L93) -- [apiAuth/cors.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L94) -- [apiAuth/cors.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L95) -- [apiAuth/cors.feature:96](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L96) -- [apiAuth/cors.feature:97](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L97) -- [apiAuth/cors.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L98) -- [apiAuth/cors.feature:99](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L99) -- [apiAuth/cors.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L100) -- [apiAuth/cors.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L101) -- [apiAuth/cors.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L112) -- [apiAuth/cors.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L113) -- [apiAuth/cors.feature:114](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L114) -- [apiAuth/cors.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L115) -- [apiAuth/cors.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L116) -- [apiAuth/cors.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L117) -- [apiAuth/cors.feature:136](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L136) -- [apiAuth/cors.feature:137](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L137) -- [apiAuth/cors.feature:138](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L138) -- [apiAuth/cors.feature:139](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L139) -- [apiAuth/cors.feature:140](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L140) -- [apiAuth/cors.feature:141](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L141) -- [apiAuth/cors.feature:160](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L160) -- [apiAuth/cors.feature:161](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L161) -- [apiAuth/cors.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L162) -- [apiAuth/cors.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L163) -- [apiAuth/cors.feature:164](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L164) -- [apiAuth/cors.feature:165](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L165) -- [apiAuth/cors.feature:166](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L166) -- [apiAuth/cors.feature:167](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L167) -- [apiAuth/cors.feature:178](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L178) -- [apiAuth/cors.feature:179](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L179) -- [apiAuth/cors.feature:180](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L180) -- [apiAuth/cors.feature:181](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L181) -- [apiAuth/cors.feature:182](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L182) -- [apiAuth/cors.feature:183](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L183) -- [apiAuth/cors.feature:204](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L204) -- [apiAuth/cors.feature:205](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L205) -- [apiAuth/cors.feature:206](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L206) -- [apiAuth/cors.feature:207](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L207) -- [apiAuth/cors.feature:208](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L208) -- [apiAuth/cors.feature:209](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L209) - -#### [App Passwords/Tokens for legacy WebDAV clients](https://github.com/owncloud/ocis/issues/197) - -- [apiAuthWebDav/webDavDELETEAuth.feature:136](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L136) -- [apiAuthWebDav/webDavDELETEAuth.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L150) -- [apiAuthWebDav/webDavDELETEAuth.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L162) -- [apiAuthWebDav/webDavDELETEAuth.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L176) - -#### [various sharing settings cannot be set](https://github.com/owncloud/ocis/issues/1328) - -- [apiCapabilities/capabilities.feature:8](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L8) -- [apiCapabilities/capabilities.feature:15](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L15) -- [apiCapabilities/capabilities.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L22) -- [apiCapabilities/capabilities.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L29) -- [apiCapabilities/capabilities.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L36) -- [apiCapabilities/capabilities.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L41) -- [apiCapabilities/capabilities.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L85) -- [apiCapabilities/capabilities.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L100) -- [apiCapabilities/capabilities.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L116) -- [apiCapabilities/capabilities.feature:127](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L127) -- [apiCapabilities/capabilities.feature:139](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L139) -- [apiCapabilities/capabilities.feature:166](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L166) -- [apiCapabilities/capabilities.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L175) -- [apiCapabilities/capabilities.feature:185](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L185) -- [apiCapabilities/capabilities.feature:194](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L194) -- [apiCapabilities/capabilities.feature:205](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L205) -- [apiCapabilities/capabilities.feature:217](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L217) -- [apiCapabilities/capabilities.feature:230](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L230) -- [apiCapabilities/capabilities.feature:243](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L243) -- [apiCapabilities/capabilities.feature:255](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L255) -- [apiCapabilities/capabilities.feature:268](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L268) -- [apiCapabilities/capabilities.feature:282](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L282) -- [apiCapabilities/capabilities.feature:292](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L292) -- [apiCapabilities/capabilities.feature:310](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L310) -- [apiCapabilities/capabilities.feature:336](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L336) -- [apiCapabilities/capabilities.feature:356](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L356) -- [apiCapabilities/capabilities.feature:380](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L380) -- [apiCapabilities/capabilities.feature:405](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L405) -- [apiCapabilities/capabilities.feature:430](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L430) -- [apiCapabilities/capabilities.feature:455](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L455) -- [apiCapabilities/capabilities.feature:483](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L483) -- [apiCapabilities/capabilities.feature:511](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L511) -- [apiCapabilities/capabilities.feature:539](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L539) -- [apiCapabilities/capabilities.feature:564](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L564) -- [apiCapabilities/capabilities.feature:589](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L589) -- [apiCapabilities/capabilities.feature:615](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L615) -- [apiCapabilities/capabilities.feature:643](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L643) -- [apiCapabilities/capabilities.feature:668](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L668) -- [apiCapabilities/capabilities.feature:693](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L693) -- [apiCapabilities/capabilities.feature:719](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L719) -- [apiCapabilities/capabilities.feature:746](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L746) -- [apiCapabilities/capabilities.feature:770](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L770) -- [apiCapabilities/capabilities.feature:795](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L795) -- [apiCapabilities/capabilities.feature:821](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L821) -- [apiCapabilities/capabilities.feature:882](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L882) -- [apiCapabilities/capabilities.feature:850](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L850) -- [apiCapabilities/capabilities.feature:914](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L914) -- [apiCapabilities/capabilities.feature:948](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L948) -- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L25) -- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L26) -- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L44) -- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L45) -- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L60) -- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L61) -- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L78) -- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L79) -- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L27) -- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L28) -- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L87) -- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L88) - -- [apiMain/caldav.feature:8](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/caldav.feature#L8) -- [apiMain/caldav.feature:15](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/caldav.feature#L15) -- [apiMain/caldav.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/caldav.feature#L23) -- [apiMain/caldav.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/caldav.feature#L31) -- [apiMain/carddav.feature:8](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/carddav.feature#L8) -- [apiMain/carddav.feature:15](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/carddav.feature#L15) -- [apiMain/carddav.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/carddav.feature#L23) -- [apiMain/carddav.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/carddav.feature#L31) - -- [apiTranslation/translation.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L21) -- [apiTranslation/translation.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L22) -- [apiTranslation/translation.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L23) -- [apiTranslation/translation.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L24) -- [apiTranslation/translation.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L25) -- [apiTranslation/translation.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L26) -- [apiTranslation/translation.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L27) -- [apiTranslation/translation.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L28) -- [apiTranslation/translation.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L33) -- [apiTranslation/translation.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L34) -- [apiTranslation/translation.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L35) -- [apiTranslation/translation.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L36) - -#### [Request to edit non-existing user by authorized admin gets unauthorized in http response](https://github.com/owncloud/core/issues/38423) - -- [apiAuthOcs/ocsPUTAuth.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsPUTAuth.feature#L26) - -#### [Sharing a same file twice to the same group](https://github.com/owncloud/ocis/issues/1710) - -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:710](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L710) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:711](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L711) - -#### [PATCH request for TUS upload with wrong checksum gives incorrect response](https://github.com/owncloud/ocis/issues/1755) - -- [apiWebdavUploadTUS/checksums.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L83) -- [apiWebdavUploadTUS/checksums.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L84) -- [apiWebdavUploadTUS/checksums.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L85) -- [apiWebdavUploadTUS/checksums.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L86) -- [apiWebdavUploadTUS/checksums.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L91) -- [apiWebdavUploadTUS/checksums.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L92) -- [apiWebdavUploadTUS/checksums.feature:172](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L172) -- [apiWebdavUploadTUS/checksums.feature:173](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L173) -- [apiWebdavUploadTUS/checksums.feature:178](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L178) -- [apiWebdavUploadTUS/checksums.feature:224](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L224) -- [apiWebdavUploadTUS/checksums.feature:225](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L225) -- [apiWebdavUploadTUS/checksums.feature:226](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L226) -- [apiWebdavUploadTUS/checksums.feature:227](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L227) -- [apiWebdavUploadTUS/checksums.feature:232](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L232) -- [apiWebdavUploadTUS/checksums.feature:233](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L233) -- [apiWebdavUploadTUS/checksums.feature:280](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L280) -- [apiWebdavUploadTUS/checksums.feature:281](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L281) -- [apiWebdavUploadTUS/checksums.feature:282](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L282) -- [apiWebdavUploadTUS/checksums.feature:283](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L283) -- [apiWebdavUploadTUS/checksums.feature:288](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L288) -- [apiWebdavUploadTUS/checksums.feature:289](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L289) -- [apiWebdavUploadTUS/optionsRequest.feature:7](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L7) -- [apiWebdavUploadTUS/optionsRequest.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L21) -- [apiWebdavUploadTUS/optionsRequest.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L33) -- [apiWebdavUploadTUS/optionsRequest.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L47) -- [apiWebdavUploadTUS/uploadToShare.feature:224](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L224) -- [apiWebdavUploadTUS/uploadToShare.feature:225](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L225) -- [apiWebdavUploadTUS/uploadToShare.feature:248](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L248) -- [apiWebdavUploadTUS/uploadToShare.feature:249](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L249) -- [apiWebdavUploadTUS/uploadToShare.feature:272](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L272) -- [apiWebdavUploadTUS/uploadToShare.feature:273](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L273) -- [apiWebdavUploadTUS/uploadToShare.feature:278](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L278) -- [apiWebdavUploadTUS/uploadToShare.feature:320](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L320) -- [apiWebdavUploadTUS/uploadToShare.feature:321](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L321) -- [apiWebdavUploadTUS/uploadToShare.feature:372](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L372) -- [apiWebdavUploadTUS/uploadToShare.feature:373](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L373) -- [apiWebdavUploadTUS/uploadToShare.feature:378](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L378) - -#### [TUS OPTIONS requests do not reply with TUS headers when invalid password](https://github.com/owncloud/ocis/issues/1012) - -- [apiWebdavUploadTUS/optionsRequest.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L59) -- [apiWebdavUploadTUS/optionsRequest.feature:73](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L73) -- [apiWebdavUploadTUS/optionsRequest.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L85) -- [apiWebdavUploadTUS/optionsRequest.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L100) - -#### [Share inaccessible if folder with same name was deleted and recreated](https://github.com/owncloud/ocis/issues/1787) - -- [apiShareReshareToShares1/reShare.feature:259](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares1/reShare.feature#L259) -- [apiShareReshareToShares1/reShare.feature:260](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares1/reShare.feature#L260) - -#### [Trying to accept a share with invalid ID gives incorrect OCS and HTTP status](https://github.com/owncloud/ocis/issues/2111) - -- [apiShareOperationsToShares2/shareAccessByID.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L85) -- [apiShareOperationsToShares2/shareAccessByID.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L86) -- [apiShareOperationsToShares2/shareAccessByID.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L87) -- [apiShareOperationsToShares2/shareAccessByID.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L88) -- [apiShareOperationsToShares2/shareAccessByID.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L89) -- [apiShareOperationsToShares2/shareAccessByID.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L90) -- [apiShareOperationsToShares2/shareAccessByID.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L91) -- [apiShareOperationsToShares2/shareAccessByID.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L92) -- [apiShareOperationsToShares2/shareAccessByID.feature:104](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L104) -- [apiShareOperationsToShares2/shareAccessByID.feature:105](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L105) -- [apiShareOperationsToShares2/shareAccessByID.feature:143](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L143) -- [apiShareOperationsToShares2/shareAccessByID.feature:144](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L144) -- [apiShareOperationsToShares2/shareAccessByID.feature:145](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L145) -- [apiShareOperationsToShares2/shareAccessByID.feature:146](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L146) -- [apiShareOperationsToShares2/shareAccessByID.feature:147](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L147) -- [apiShareOperationsToShares2/shareAccessByID.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L148) -- [apiShareOperationsToShares2/shareAccessByID.feature:149](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L149) -- [apiShareOperationsToShares2/shareAccessByID.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L150) -- [apiShareOperationsToShares2/shareAccessByID.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L162) -- [apiShareOperationsToShares2/shareAccessByID.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L163) - -#### [[OC-storage] share-types field empty for shared file folder in webdav response](https://github.com/owncloud/ocis/issues/2144) - -- [apiWebdavProperties2/getFileProperties.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L215) -- [apiWebdavProperties2/getFileProperties.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L216) -- [apiWebdavProperties2/getFileProperties.feature:221](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L221) - -#### [Creating a public link with all permissions(31) fails](https://github.com/owncloud/ocis/issues/2145) - -- [apiWebdavProperties2/getFileProperties.feature:275](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L275) -- [apiWebdavProperties2/getFileProperties.feature:276](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L276) -- [apiWebdavProperties2/getFileProperties.feature:281](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L281) - -#### [Shares to deleted group listed in the response](https://github.com/owncloud/ocis/issues/2441) - -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:495](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L495) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:496](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L496) - -#### [Trying to copy a file into a readonly share gives HTTP 500 error](https://github.com/owncloud/ocis/issues/2166) - -- [apiWebdavProperties1/copyFile.feature:452](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L452) -- [apiWebdavProperties1/copyFile.feature:453](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L453) -- [apiWebdavProperties1/copyFile.feature:458](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L458) -- [apiWebdavProperties1/copyFile.feature:478](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L478) -- [apiWebdavProperties1/copyFile.feature:479](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L479) -- [apiWebdavProperties1/copyFile.feature:484](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L484) - -### Won't fix - -Not everything needs to be implemented for ocis. While the oc10 testsuite covers these things we are not looking at them right now. - -- _The `OC-LazyOps` header is [no longer supported by the client](https://github.com/owncloud/client/pull/8398), implementing this is not necessary for a first production release. We plan to have an upload state machine to visualize the state of a file, see https://github.com/owncloud/ocis/issues/214_ -- _Blacklisted ignored files are no longer required because ocis can handle `.htaccess` files without security implications introduced by serving user provided files with apache._ - -#### [uploading with old-chunking does not work](https://github.com/owncloud/ocis/issues/1343) - -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L20) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L21) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L26) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L39) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L40) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L45) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L81) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L82) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L87) - -#### [Blacklist files extensions](https://github.com/owncloud/ocis/issues/2177) - -- [apiWebdavProperties1/copyFile.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L132) -- [apiWebdavProperties1/copyFile.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L133) -- [apiWebdavProperties1/copyFile.feature:138](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L138) -- [apiWebdavProperties1/createFolder.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L95) -- [apiWebdavProperties1/createFolder.feature:96](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L96) -- [apiWebdavProperties1/createFolder.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L101) -- [apiWebdavUpload1/uploadFile.feature:181](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFile.feature#L181) -- [apiWebdavUpload1/uploadFile.feature:182](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFile.feature#L182) -- [apiWebdavUpload1/uploadFile.feature:187](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFile.feature#L187) -- [apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature#L19) -- [apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature#L35) -- [apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature#L36) -- [apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature#L37) -- [apiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature:13](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature#L13) -- [apiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature#L20) -- [apiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature#L38) -- [apiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature#L39) -- [apiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToExcludedDirectoryUsingOldChunking.feature#L40) -- [apiWebdavMove2/moveFile.feature:287](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFile.feature#L287) -- [apiWebdavMove2/moveFile.feature:288](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFile.feature#L288) -- [apiWebdavMove2/moveFile.feature:293](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFile.feature#L293) - -#### [cannot set blacklisted file names](https://github.com/owncloud/product/issues/260) - -- [apiWebdavMove1/moveFolderToBlacklistedName.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L21) -- [apiWebdavMove1/moveFolderToBlacklistedName.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L22) -- [apiWebdavMove1/moveFolderToBlacklistedName.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L27) -- [apiWebdavMove1/moveFolderToBlacklistedName.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L40) -- [apiWebdavMove1/moveFolderToBlacklistedName.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L41) -- [apiWebdavMove1/moveFolderToBlacklistedName.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L46) -- [apiWebdavMove1/moveFolderToBlacklistedName.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L81) -- [apiWebdavMove1/moveFolderToBlacklistedName.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L82) -- [apiWebdavMove1/moveFolderToBlacklistedName.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L87) -- [apiWebdavMove2/moveFileToBlacklistedName.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToBlacklistedName.feature#L19) -- [apiWebdavMove2/moveFileToBlacklistedName.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToBlacklistedName.feature#L20) -- [apiWebdavMove2/moveFileToBlacklistedName.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToBlacklistedName.feature#L35) -- [apiWebdavMove2/moveFileToBlacklistedName.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToBlacklistedName.feature#L36) -- [apiWebdavMove2/moveFileToBlacklistedName.feature:74](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToBlacklistedName.feature#L74) -- [apiWebdavMove2/moveFileToBlacklistedName.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToBlacklistedName.feature#L75) - -#### [cannot set excluded directories](https://github.com/owncloud/product/issues/261) - -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L22) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L23) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L28) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L42) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L43) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L48) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L84) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L85) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L90) -- [apiWebdavMove2/moveFileToExcludedDirectory.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToExcludedDirectory.feature#L20) -- [apiWebdavMove2/moveFileToExcludedDirectory.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToExcludedDirectory.feature#L21) -- [apiWebdavMove2/moveFileToExcludedDirectory.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToExcludedDirectory.feature#L37) -- [apiWebdavMove2/moveFileToExcludedDirectory.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToExcludedDirectory.feature#L38) -- [apiWebdavMove2/moveFileToExcludedDirectory.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToExcludedDirectory.feature#L78) -- [apiWebdavMove2/moveFileToExcludedDirectory.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToExcludedDirectory.feature#L79) - -#### [system configuration options missing](https://github.com/owncloud/ocis/issues/1323) - -- [apiWebdavUpload1/uploadFileToBlacklistedName.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L31) -- [apiWebdavUpload1/uploadFileToBlacklistedName.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L32) -- [apiWebdavUpload1/uploadFileToBlacklistedName.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L37) -- [apiWebdavUpload1/uploadFileToBlacklistedName.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L71) -- [apiWebdavUpload1/uploadFileToBlacklistedName.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L72) -- [apiWebdavUpload1/uploadFileToBlacklistedName.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L77) - -#### [Allow public link sharing only for certain groups feature not implemented] - -- [apiSharePublicLink2/allowGroupToCreatePublicLinks.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/allowGroupToCreatePublicLinks.feature#L35) -- [apiSharePublicLink2/allowGroupToCreatePublicLinks.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/allowGroupToCreatePublicLinks.feature#L91) - -#### [Cannot download preview of shared received file after the shareowner has changed the file content](https://github.com/owncloud/ocis/issues/2538) - -- [apiWebdavPreviews/previews.feature:219](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavPreviews/previews.feature#L219) -- [apiWebdavPreviews/previews.feature:237](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavPreviews/previews.feature#L237) - -#### [Preview of text file with UTF content does not render correctly](https://github.com/owncloud/ocis/issues/2570) - -- [apiWebdavPreviews/previews.feature:210](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavPreviews/previews.feature#L210) - -#### [Share path in the response is different between share states](https://github.com/owncloud/ocis/issues/2540) - -- [apiShareManagementToShares/acceptShares.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L65) -- [apiShareManagementToShares/acceptShares.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L93) -- [apiShareManagementToShares/acceptShares.feature:224](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L224) -- [apiShareManagementToShares/acceptShares.feature:252](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L252) -- [apiShareManagementToShares/acceptShares.feature:295](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L295) -- [apiShareManagementToShares/acceptShares.feature:335](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L335) -- [apiShareManagementToShares/acceptShares.feature:577](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L577) -- [apiShareManagementToShares/acceptShares.feature:578](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L578) -- [apiShareOperationsToShares2/shareAccessByID.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L124) -- [apiShareOperationsToShares2/shareAccessByID.feature:125](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L125) - -#### [Content-type is not multipart/byteranges when downloading file with Range Header](https://github.com/owncloud/ocis/issues/2677) - -- [apiWebdavOperations/downloadFile.feature:229](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/downloadFile.feature#L229) -- [apiWebdavOperations/downloadFile.feature:230](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/downloadFile.feature#L230) -- [apiWebdavOperations/downloadFile.feature:235](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/downloadFile.feature#L235) - -#### [Creating a new folder which is a substring of Shares leads to Unknown Error](https://github.com/owncloud/ocis/issues/3033) - -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L79) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:96](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L96) - -#### [moveShareInsideAnotherShare behaves differently on oCIS than oC10](https://github.com/owncloud/ocis/issues/3047) - -- [apiShareManagementToShares/moveShareInsideAnotherShare.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L25) -- [apiShareManagementToShares/moveShareInsideAnotherShare.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L86) -- [apiShareManagementToShares/moveShareInsideAnotherShare.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L100) - -#### [TUS upload file with invalid name sends false response](https://github.com/owncloud/ocis/issues/3050) - -- [apiWebdavUploadTUS/uploadFile.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFile.feature#L215) -- [apiWebdavUploadTUS/uploadFile.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFile.feature#L216) -- [apiWebdavUploadTUS/uploadFile.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFile.feature#L218) - -#### [unable to create resource using TUS inside Shares dir](https://github.com/owncloud/ocis/issues/3048) - -- [apiWebdavUploadTUS/uploadFileMtimeShares.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFileMtimeShares.feature#L33) -- [apiWebdavUploadTUS/uploadFileMtimeShares.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFileMtimeShares.feature#L52) -- [apiWebdavUploadTUS/uploadFileMtimeShares.feature:73](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFileMtimeShares.feature#L73) -- [apiWebdavUploadTUS/uploadFileMtimeShares.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFileMtimeShares.feature#L94) -- [apiWebdavUploadTUS/uploadToShare.feature:352](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L352) - -#### [Renaming resource to banned name is allowed in spaces webdav](https://github.com/owncloud/ocis/issues/3099) - -- [apiWebdavMove1/moveFolder.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolder.feature#L27) -- [apiWebdavMove1/moveFolder.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolder.feature#L45) -- [apiWebdavMove1/moveFolder.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolder.feature#L63) -- [apiWebdavMove2/moveFile.feature:224](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFile.feature#L224) -- [apiWebdavMove2/moveFileToBlacklistedName.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToBlacklistedName.feature#L25) -- [apiWebdavMove2/moveFileToBlacklistedName.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToBlacklistedName.feature#L41) -- [apiWebdavMove2/moveFileToBlacklistedName.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToBlacklistedName.feature#L80) - -#### [REPORT method on spaces returns an incorrect d:href response](https://github.com/owncloud/ocis/issues/3111) - -- [apiFavorites/favorites.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L121) -- [apiFavorites/favorites.feature:147](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L147) -- [apiFavorites/favorites.feature:273](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L273) - -#### [could not create system tag](https://github.com/owncloud/ocis/issues/3092) - -- [apiWebdavOperations/search.feature:273](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L273) -- [apiWebdavOperations/search.feature:289](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L289) -- [apiWebdavOperations/search.feature:314](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L314) - -#### [Incorrect response while listing resources of a folder with depth infinity](https://github.com/owncloud/ocis/issues/3073) - -- [apiWebdavOperations/listFiles.feature:182](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/listFiles.feature#L182) - -### [[spaces webdav] upload to a share that was locked by owner ends with status code 409](https://github.com/owncloud/ocis/issues/3128) - -- [apiWebdavLocks2/resharedSharesToShares.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L39) -- [apiWebdavLocks2/resharedSharesToShares.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L40) -- [apiWebdavLocks2/resharedSharesToShares.feature:69](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L69) -- [apiWebdavLocks2/resharedSharesToShares.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L70) - -#### [Renaming resource to excluded directory name is allowed in spaces webdav](https://github.com/owncloud/ocis/issues/3102) - -- [apiWebdavMove2/moveFileToExcludedDirectory.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToExcludedDirectory.feature#L26) -- [apiWebdavMove2/moveFileToExcludedDirectory.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToExcludedDirectory.feature#L43) -- [apiWebdavMove2/moveFileToExcludedDirectory.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToExcludedDirectory.feature#L84) - -#### [can't access public link resources with spaces webdav API](https://github.com/owncloud/ocis/issues/3085) - -- [apiWebdavOperations/listFiles.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/listFiles.feature#L218) -- [apiWebdavOperations/listFiles.feature:256](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/listFiles.feature#L256) -- [apiWebdavOperations/listFiles.feature:294](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/listFiles.feature#L294) - -#### [Trying to modify a shared file using spaces end-point returns 409 HTTP status code](https://github.com/owncloud/ocis/issues/3241) - -- [apiMain/checksums.feature:233](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L233) - -#### [status does not have new product data item](https://github.com/owncloud/ocis/issues/3317) - -- [apiCapabilities/capabilities.feature:959](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L959) - -#### public links without permission are allowed now - -- [apiShareUpdateToShares/updateShare.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L113) -- [apiShareUpdateToShares/updateShare.feature:114](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L114) -- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L26) -- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L27) -- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L28) -- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L29) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L117) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L118) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L132) -- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L133) - -### [graph/users: enable/disable users](https://github.com/owncloud/ocis/issues/3064) - -- [apiWebdavOperations/refuseAccess.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L35) -- [apiWebdavOperations/refuseAccess.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L36) -- [apiWebdavOperations/refuseAccess.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L41) - -### [Group name is case-insensitive](https://github.com/owncloud/ocis/issues/3167#issuecomment-1090045359) - -- [apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature#L49) -- [apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature#L50) -- [apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature#L51) -- [apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature#L52) -- [apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature:53](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature#L53) -- [apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature:54](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareGroupCaseSensitive.feature#L54) - -#### [OCS status code zero](https://github.com/owncloud/ocis/issues/3621) -- [apiShareManagementToShares/moveReceivedShare.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveReceivedShare.feature#L32) - -#### [HTTP status code differ while listing the contents of another user's trash bin](https://github.com/owncloud/ocis/issues/3561) -- [apiTrashbin/trashbinFilesFolders.feature:199](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L199) -- [apiTrashbin/trashbinFilesFolders.feature:223](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L223) -- [apiTrashbin/trashbinFilesFolders.feature:253](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L253) - -#### [HTTP status code differ while deleting file of another user's trash bin](https://github.com/owncloud/ocis/issues/3544) -- [apiTrashbin/trashbinDelete.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L108) - -#### [Problem accessing trashbin with personal space id](https://github.com/owncloud/ocis/issues/3639) -- [apiTrashbin/trashbinDelete.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L35) -- [apiTrashbin/trashbinDelete.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L36) -- [apiTrashbin/trashbinDelete.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L58) -- [apiTrashbin/trashbinDelete.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L85) -- [apiTrashbin/trashbinDelete.feature:130](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L130) -- [apiTrashbin/trashbinDelete.feature:152](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L152) -- [apiTrashbin/trashbinDelete.feature:177](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L177) -- [apiTrashbin/trashbinDelete.feature:202](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L202) -- [apiTrashbin/trashbinDelete.feature:239](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L239) -- [apiTrashbin/trashbinDelete.feature:276](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L276) -- [apiTrashbin/trashbinDelete.feature:324](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L324) -- [apiTrashbin/trashbinFilesFolders.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L25) -- [apiTrashbin/trashbinFilesFolders.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L41) -- [apiTrashbin/trashbinFilesFolders.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L59) -- [apiTrashbin/trashbinFilesFolders.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L80) -- [apiTrashbin/trashbinFilesFolders.feature:99](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L99) -- [apiTrashbin/trashbinFilesFolders.feature:135](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L135) -- [apiTrashbin/trashbinFilesFolders.feature:158](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L158) -- [apiTrashbin/trashbinFilesFolders.feature:313](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L313) -- [apiTrashbin/trashbinFilesFolders.feature:314](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L314) -- [apiTrashbin/trashbinFilesFolders.feature:315](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L315) -- [apiTrashbin/trashbinFilesFolders.feature:334](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L334) -- [apiTrashbin/trashbinFilesFolders.feature:354](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L354) -- [apiTrashbin/trashbinFilesFolders.feature:408](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L408) -- [apiTrashbin/trashbinFilesFolders.feature:445](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L445) - -Note: always have an empty line at the end of this file. -The bash script that processes this file requires that the last line has a newline on the end. diff --git a/tests/acceptance/expected-failures-webUI-on-OCIS-storage.md b/tests/acceptance/expected-failures-webUI-on-OCIS-storage.md index a0b9e089e9..788d9505f3 100644 --- a/tests/acceptance/expected-failures-webUI-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-webUI-on-OCIS-storage.md @@ -172,10 +172,10 @@ Other free text and markdown formatting can be used elsewhere in the document if - [webUISharingPublicBasic/publicLinkEdit.feature:31](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUISharingPublicBasic/publicLinkEdit.feature#L31) - [webUISharingPublicBasic/publicLinkEdit.feature:32](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUISharingPublicBasic/publicLinkEdit.feature#L32) - [webUISharingPublicBasic/publicLinkEdit.feature:33](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUISharingPublicBasic/publicLinkEdit.feature#L33) -- [webUISharingPublicBasic/publicLinkEdit.feature:56](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUISharingPublicBasic/publicLinkEdit.feature#L56) - [webUISharingPublicBasic/publicLinkEdit.feature:57](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUISharingPublicBasic/publicLinkEdit.feature#L57) - [webUISharingPublicBasic/publicLinkEdit.feature:58](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUISharingPublicBasic/publicLinkEdit.feature#L58) - [webUISharingPublicBasic/publicLinkEdit.feature:59](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUISharingPublicBasic/publicLinkEdit.feature#L59) +- [webUISharingPublicBasic/publicLinkEdit.feature:60](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUISharingPublicBasic/publicLinkEdit.feature#L60) - [webUISharingPublicExpire/shareByPublicLinkExpiringLinks.feature:51](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUISharingPublicExpire/shareByPublicLinkExpiringLinks.feature#L51) - [webUISharingPublicExpire/shareByPublicLinkExpiringLinks.feature:52](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUISharingPublicExpire/shareByPublicLinkExpiringLinks.feature#L52) - [webUISharingPublicExpire/shareByPublicLinkExpiringLinks.feature:71](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUISharingPublicExpire/shareByPublicLinkExpiringLinks.feature#L71) @@ -470,14 +470,10 @@ Other free text and markdown formatting can be used elsewhere in the document if ### [empty subfolder inside a folder to be uploaded is not created on the server](https://github.com/owncloud/web/issues/6348) - [webUIUpload/upload.feature:42](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUIUpload/upload.feature#L42) -### [publicLinkCreate.feature:172 is failing](https://github.com/owncloud/ocis/issues/3581) -- [webUISharingPublicBasic/publicLinkCreate.feature:172](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUISharingPublicBasic/publicLinkCreate.feature#172) - ### [Creating group with problematic group name via graph api gives 500 error](https://github.com/owncloud/ocis/issues/3631) -- [webUISharingInternalGroupsEdgeCases/shareWithGroupsEdgeCases.feature:41](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUISharingInternalGroupsEdgeCases/shareWithGroupsEdgeCases.feature:41) +- [webUISharingInternalGroupsEdgeCases/shareWithGroupsEdgeCases.feature:41](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUISharingInternalGroupsEdgeCases/shareWithGroupsEdgeCases.feature#L41) ### [Favorites deactivated in ocis temporarily](https://github.com/owncloud/ocis/issues/1228) - [webUIFilesDetails/fileDetails.feature:42](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUIFilesDetails/fileDetails.feature#42) - [webUIFilesDetails/fileDetails.feature:57](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUIFilesDetails/fileDetails.feature#57) - [webUIRenameFiles/renameFiles.feature:246](https://github.com/owncloud/web/blob/master/tests/acceptance/features/webUIRenameFiles/renameFiles.feature#246) - diff --git a/tests/parallelDeployAcceptance/features/apiWebdavOperations/downloadFile.feature b/tests/parallelDeployAcceptance/features/apiWebdavOperations/downloadFile.feature index 1db4f76b9c..acd824aa02 100644 --- a/tests/parallelDeployAcceptance/features/apiWebdavOperations/downloadFile.feature +++ b/tests/parallelDeployAcceptance/features/apiWebdavOperations/downloadFile.feature @@ -2,7 +2,7 @@ Feature: download file As a user I want to be able to download files - So that I can work wih local copies of files on my client system + So that I can work with local copies of files on my client system Background: Given using "oc10" as owncloud selector