diff --git a/.drone.star b/.drone.star index 20e2a19369e..01d5b77086e 100644 --- a/.drone.star +++ b/.drone.star @@ -44,35 +44,35 @@ DEFAULT_NODEJS_VERSION = "14" config = { "modules": [ # if you add a module here please also add it to the root level Makefile - "extensions/app-provider", - "extensions/app-registry", - "extensions/audit", - "extensions/auth-basic", - "extensions/auth-bearer", - "extensions/auth-machine", - "extensions/frontend", - "extensions/gateway", - "extensions/graph-explorer", - "extensions/graph", - "extensions/groups", - "extensions/idm", - "extensions/idp", - "extensions/nats", - "extensions/notifications", - "extensions/ocdav", - "extensions/ocs", - "extensions/proxy", - "extensions/settings", - "extensions/sharing", - "extensions/storage-system", - "extensions/storage-publiclink", - "extensions/storage-shares", - "extensions/storage-users", - "extensions/store", - "extensions/thumbnails", - "extensions/users", - "extensions/web", - "extensions/webdav", + "services/app-provider", + "services/app-registry", + "services/audit", + "services/auth-basic", + "services/auth-bearer", + "services/auth-machine", + "services/frontend", + "services/gateway", + "services/graph-explorer", + "services/graph", + "services/groups", + "services/idm", + "services/idp", + "services/nats", + "services/notifications", + "services/ocdav", + "services/ocs", + "services/proxy", + "services/settings", + "services/sharing", + "services/storage-system", + "services/storage-publiclink", + "services/storage-shares", + "services/storage-users", + "services/store", + "services/thumbnails", + "services/users", + "services/web", + "services/webdav", "ocis-pkg", "ocis", ], @@ -777,7 +777,7 @@ def settingsUITests(ctx, storage = "ocis", accounts_hash_difficulty = 4): "LOCAL_UPLOAD_DIR": "/uploads", "NODE_TLS_REJECT_UNAUTHORIZED": 0, "WEB_PATH": "/srv/app/web", - "FEATURE_PATH": "/drone/src/extensions/settings/ui/tests/acceptance/features", + "FEATURE_PATH": "/drone/src/services/settings/ui/tests/acceptance/features", "MIDDLEWARE_HOST": "http://middleware:3000", }, "commands": [ @@ -788,7 +788,7 @@ def settingsUITests(ctx, storage = "ocis", accounts_hash_difficulty = 4): "git checkout $WEB_COMMITID", # TODO: settings/package.json has all the acceptance test dependencies # they shouldn't be needed since we could also use them from web:/tests/acceptance/package.json - "cd /drone/src/extensions/settings", + "cd /drone/src/services/settings", "yarn install --immutable", "make test-acceptance-webui", ], @@ -1640,8 +1640,8 @@ def ocisServer(storage, accounts_hash_difficulty = 4, volumes = [], depends_on = "SHARING_USER_OWNCLOUDSQL_DB_PORT": 3306, "SHARING_USER_OWNCLOUDSQL_DB_NAME": "owncloud", # General oCIS config - # OCIS_RUN_EXTENSIONS specifies to start all fullstack extensions except idm and idp. These are replaced by external services - "OCIS_RUN_EXTENSIONS": "app-registry,app-provider,auth-basic,auth-bearer,auth-machine,frontend,gateway,graph,graph-explorer,groups,nats,notifications,ocdav,ocs,proxy,search,settings,sharing,storage-system,storage-publiclink,storage-shares,storage-users,store,thumbnails,users,web,webdav", + # OCIS_RUN_SERVICES specifies to start all fullstack services except idm and idp. These are replaced by external services + "OCIS_RUN_SERVICES": "app-registry,app-provider,auth-basic,auth-bearer,auth-machine,frontend,gateway,graph,graph-explorer,groups,nats,notifications,ocdav,ocs,proxy,search,settings,sharing,storage-system,storage-publiclink,storage-shares,storage-users,store,thumbnails,users,web,webdav", "OCIS_LOG_LEVEL": "info", "OCIS_URL": OCIS_URL, "FRONTEND_ENABLE_RESHARING": "true", diff --git a/.gitignore b/.gitignore index 9cce3ac8ac9..f794a47409e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,10 @@ # coverage reports */coverage.out -extensions/*/coverage.out +services/*/coverage.out # unit test reports */checkstyle.xml -extensions/*/checkstyle.xml +services/*/checkstyle.xml # nodejs / yarn */package-lock.json @@ -14,9 +14,9 @@ yarn.lock # build artifacts */bin -extensions/*/bin +services/*/bin dist/ -extensions/*/assets +services/*/assets ocis/ocis ocis/cmd/ocis/__debug_bin ocis/cmd/ocis/config/ diff --git a/.make/docs.mk b/.make/docs.mk index b7adfd43731..d8a771c7a61 100644 --- a/.make/docs.mk +++ b/.make/docs.mk @@ -1,6 +1,6 @@ SKIP_CONFIG_DOCS_GENERATE ?= 0 -CONFIG_DOCS_BASE_PATH ?= ../../docs/extensions +CONFIG_DOCS_BASE_PATH ?= ../../docs/services .PHONY: config-docs-generate config-docs-generate: #$(FLAEX) diff --git a/.vscode/launch.json b/.vscode/launch.json index 32492d9cab7..0ecba45fa3f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -21,8 +21,8 @@ "PROXY_ENABLE_BASIC_AUTH": "true", // demo users "IDM_CREATE_DEMO_USERS": "true", - // OCIS_RUN_EXTENSIONS allows to start a subset of extensions even in the supervised mode - //"OCIS_RUN_EXTENSIONS": "settings,storage-system,graph,graph-explorer,idp,idm,ocs,store,thumbnails,web,webdav,frontend,gateway,users,groups,auth-basic,auth-bearer,storage-authmachine,storage-users,storage-shares,storage-publiclink,storage-system,app-provider,sharing,proxy,ocdav", + // OCIS_RUN_SERVICES allows to start a subset of services even in the supervised mode + //"OCIS_RUN_SERVICES": "settings,storage-system,graph,graph-explorer,idp,idm,ocs,store,thumbnails,web,webdav,frontend,gateway,users,groups,auth-basic,auth-bearer,storage-authmachine,storage-users,storage-shares,storage-publiclink,storage-system,app-provider,sharing,proxy,ocdav", /* * Keep secrets and passwords in one block to allow easy uncommenting diff --git a/Makefile b/Makefile index 6b8059ea2fd..0635429d86c 100644 --- a/Makefile +++ b/Makefile @@ -16,35 +16,35 @@ 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/app-provider \ - extensions/app-registry \ - extensions/audit \ - extensions/auth-basic \ - extensions/auth-bearer \ - extensions/auth-machine \ - extensions/frontend \ - extensions/gateway \ - extensions/graph \ - extensions/graph-explorer \ - extensions/groups \ - extensions/idm \ - extensions/idp \ - extensions/nats \ - extensions/notifications \ - extensions/ocdav \ - extensions/ocs \ - extensions/proxy \ - extensions/settings \ - extensions/sharing \ - extensions/storage-system \ - extensions/storage-publiclink \ - extensions/storage-shares \ - extensions/storage-users \ - extensions/store \ - extensions/thumbnails \ - extensions/users \ - extensions/web \ - extensions/webdav\ + services/app-provider \ + services/app-registry \ + services/audit \ + services/auth-basic \ + services/auth-bearer \ + services/auth-machine \ + services/frontend \ + services/gateway \ + services/graph \ + services/graph-explorer \ + services/groups \ + services/idm \ + services/idp \ + services/nats \ + services/notifications \ + services/ocdav \ + services/ocs \ + services/proxy \ + services/settings \ + services/sharing \ + services/storage-system \ + services/storage-publiclink \ + services/storage-shares \ + services/storage-users \ + services/store \ + services/thumbnails \ + services/users \ + services/web \ + services/webdav\ ocis \ ocis-pkg diff --git a/changelog/unreleased/refactor-extensions-to-services.md b/changelog/unreleased/refactor-extensions-to-services.md new file mode 100644 index 00000000000..ebe84055e35 --- /dev/null +++ b/changelog/unreleased/refactor-extensions-to-services.md @@ -0,0 +1,7 @@ +Enhancement: Refactor extensions to services + +We have decided to name all extensions, we maintain and provide with ocis, +services from here on to avoid confusion between external extensions and code +we provide and maintain. + +https://github.com/owncloud/ocis/pull/3980 diff --git a/deployments/examples/oc10_ocis_parallel/docker-compose.yml b/deployments/examples/oc10_ocis_parallel/docker-compose.yml index 60cb9095cb4..114398b744d 100644 --- a/deployments/examples/oc10_ocis_parallel/docker-compose.yml +++ b/deployments/examples/oc10_ocis_parallel/docker-compose.yml @@ -109,8 +109,8 @@ services: # ownCloud storage readonly 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 fullstack extensions except idm and idp. These are replaced by external services - OCIS_RUN_EXTENSIONS: app-registry,app-provider,auth-basic,auth-bearer,auth-machine,frontend,gateway,graph,graph-explorer,groups,nats,notifications,ocdav,ocs,proxy,search,settings,sharing,storage-system,storage-publiclink,storage-shares,storage-users,store,thumbnails,users,web,webdav + # OCIS_RUN_SERVICES specifies to start all fullstack services except idm and idp. These are replaced by external services + OCIS_RUN_SERVICES: app-registry,app-provider,auth-basic,auth-bearer,auth-machine,frontend,gateway,graph,graph-explorer,groups,nats,notifications,ocdav,ocs,proxy,search,settings,sharing,storage-system,storage-publiclink,storage-shares,storage-users,store,thumbnails,users,web,webdav OCIS_LOG_LEVEL: ${OCIS_LOG_LEVEL:-error} # make oCIS less verbose OCIS_LOG_COLOR: "${OCIS_LOG_COLOR:-false}" OCIS_URL: https://${CLOUD_DOMAIN:-cloud.owncloud.test} diff --git a/deployments/examples/ocis_hello/config/ocis/proxy.yaml b/deployments/examples/ocis_hello/config/ocis/proxy.yaml index 7f19ee79615..e7db5e37a8f 100644 --- a/deployments/examples/ocis_hello/config/ocis/proxy.yaml +++ b/deployments/examples/ocis_hello/config/ocis/proxy.yaml @@ -5,7 +5,7 @@ policy_selector: policies: - name: ocis routes: - # defaults, taken from https://owncloud.dev/extensions/proxy/configuration/ + # defaults, taken from https://owncloud.dev/services/proxy/configuration/ - endpoint: / backend: http://localhost:9100 - endpoint: /.well-known/ diff --git a/deployments/examples/ocis_ldap/docker-compose.yml b/deployments/examples/ocis_ldap/docker-compose.yml index 13f2b2c957d..6adf5522f1e 100644 --- a/deployments/examples/ocis_ldap/docker-compose.yml +++ b/deployments/examples/ocis_ldap/docker-compose.yml @@ -72,8 +72,8 @@ services: IDP_LDAP_UUID_ATTRIBUTE: "ownclouduuid" IDP_LDAP_UUID_ATTRIBUTE_TYPE: binary GRAPH_LDAP_SERVER_WRITE_ENABLED: "false" # assuming the external ldap is readonly - # OCIS_RUN_EXTENSIONS specifies to start all extensions except glauth, idm and accounts. These are replaced by external services - OCIS_RUN_EXTENSIONS: app-registry,app-provider,audit,auth-basic,auth-bearer,auth-machine,frontend,gateway,graph,graph-explorer,groups,idp,nats,notifications,ocdav,ocs,proxy,search,settings,sharing,storage-system,storage-publiclink,storage-shares,storage-users,store,thumbnails,users,web,webdav + # OCIS_RUN_SERVICES specifies to start all services except glauth, idm and accounts. These are replaced by external services + OCIS_RUN_SERVICES: app-registry,app-provider,audit,auth-basic,auth-bearer,auth-machine,frontend,gateway,graph,graph-explorer,groups,idp,nats,notifications,ocdav,ocs,proxy,search,settings,sharing,storage-system,storage-publiclink,storage-shares,storage-users,store,thumbnails,users,web,webdav # General oCIS config OCIS_URL: https://${OCIS_DOMAIN:-ocis.owncloud.test} OCIS_LOG_LEVEL: ${OCIS_LOG_LEVEL:-error} # make oCIS less verbose diff --git a/docs/Makefile b/docs/Makefile index 12121bd84e0..0de6a1b5378 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -7,7 +7,7 @@ help: @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' .PHONY: docs-generate -docs-generate: ## run docs-generate for all oCIS extensions +docs-generate: ## run docs-generate for all oCIS services @pushd helpers && go run configenvextractor.go; popd @$(MAKE) --no-print-directory -C ../ docs-generate diff --git a/docs/extensions/app-provider/_index.md b/docs/extensions/app-provider/_index.md deleted file mode 100644 index 39221058a07..00000000000 --- a/docs/extensions/app-provider/_index.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -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 deleted file mode 100644 index 6f4d3e066d5..00000000000 --- a/docs/extensions/app-provider/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -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 deleted file mode 100644 index 233279020b6..00000000000 --- a/docs/extensions/app-registry/_index.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -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/app-registry/configuration.md b/docs/extensions/app-registry/configuration.md deleted file mode 100644 index f1a18da072c..00000000000 --- a/docs/extensions/app-registry/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -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/audit/_index.md b/docs/extensions/audit/_index.md deleted file mode 100644 index 530a637cb1a..00000000000 --- a/docs/extensions/audit/_index.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: Audit -date: 2022-03-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/audit -geekdocFilePath: _index.md -geekdocCollapseSection: true ---- - -## Abstract - - -## Table of Contents - -{{< toc-tree >}} diff --git a/docs/extensions/audit/configuration.md b/docs/extensions/audit/configuration.md deleted file mode 100644 index 2619a3ece07..00000000000 --- a/docs/extensions/audit/configuration.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: Service Configuration -date: 2018-05-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/audit -geekdocFilePath: configuration.md -geekdocCollapseSection: true ---- - - -## Example YAML Config - -{{< include file="extensions/_includes/audit-config-example.yaml" language="yaml" >}} - -{{< include file="extensions/_includes/audit_configvars.md" >}} diff --git a/docs/extensions/auth-basic/_index.md b/docs/extensions/auth-basic/_index.md deleted file mode 100644 index 0f47e22979a..00000000000 --- a/docs/extensions/auth-basic/_index.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -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 deleted file mode 100644 index 4fdd2f9225e..00000000000 --- a/docs/extensions/auth-basic/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -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 deleted file mode 100644 index 853f8938fa1..00000000000 --- a/docs/extensions/auth-bearer/_index.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -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 deleted file mode 100644 index 853ef9acc74..00000000000 --- a/docs/extensions/auth-bearer/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -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 deleted file mode 100644 index cba78fad0d1..00000000000 --- a/docs/extensions/auth-machine/_index.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -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 deleted file mode 100644 index 96fb78c4b8b..00000000000 --- a/docs/extensions/auth-machine/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -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 deleted file mode 100644 index 3e6321e9dd9..00000000000 --- a/docs/extensions/frontend/_index.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -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 deleted file mode 100644 index fb164e6bab1..00000000000 --- a/docs/extensions/frontend/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -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 deleted file mode 100644 index 4a89602b6c2..00000000000 --- a/docs/extensions/gateway/_index.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -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 deleted file mode 100644 index 3ff17a8e8f5..00000000000 --- a/docs/extensions/gateway/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -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/graph-explorer/_index.md b/docs/extensions/graph-explorer/_index.md deleted file mode 100644 index 6e97f68a3ec..00000000000 --- a/docs/extensions/graph-explorer/_index.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: "Graph-Explorer" -date: 2018-05-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/graph-explorer -geekdocFilePath: _index.md -geekdocCollapseSection: true ---- - -This service embeds [Graph-Explorer](https://github.com/owncloud/ocis/tree/master/graph-explorer) to provide a UI for ownCloud Infinite Scale. diff --git a/docs/extensions/graph-explorer/configuration.md b/docs/extensions/graph-explorer/configuration.md deleted file mode 100644 index a88cabd2d6d..00000000000 --- a/docs/extensions/graph-explorer/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Service Configuration -date: 2018-05-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/graph-explorer -geekdocFilePath: configuration.md -geekdocCollapseSection: true ---- - -## Example YAML Config - -{{< include file="extensions/_includes/graph-explorer-config-example.yaml" language="yaml" >}} - -{{< include file="extensions/_includes/graph-explorer_configvars.md" >}} \ No newline at end of file diff --git a/docs/extensions/graph/_index.md b/docs/extensions/graph/_index.md deleted file mode 100644 index 7ff7bc687c4..00000000000 --- a/docs/extensions/graph/_index.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: "Graph" -date: 2018-05-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/graph -geekdocFilePath: _index.md -geekdocCollapseSection: true ---- - -This service provides a simple graph world API which can be used by clients or other extensions. - -{{< toc-tree >}} diff --git a/docs/extensions/graph/configuration.md b/docs/extensions/graph/configuration.md deleted file mode 100644 index dbd212255cb..00000000000 --- a/docs/extensions/graph/configuration.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -title: Service Configuration -date: 2018-05-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/graph -geekdocFilePath: configuration.md -geekdocCollapseSection: true ---- -## Example YAML Config - -{{< include file="extensions/_includes/graph-config-example.yaml" language="yaml" >}} - -{{< include file="extensions/_includes/graph_configvars.md" >}} \ No newline at end of file diff --git a/docs/extensions/groups/_index.md b/docs/extensions/groups/_index.md deleted file mode 100644 index 5308c51bd6f..00000000000 --- a/docs/extensions/groups/_index.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: Groups -date: 2022-03-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/groups -geekdocFilePath: _index.md -geekdocCollapseSection: true ---- - -## Abstract - - -## Table of Contents - -{{< toc-tree >}} diff --git a/docs/extensions/groups/configuration.md b/docs/extensions/groups/configuration.md deleted file mode 100644 index ae71b8bf27d..00000000000 --- a/docs/extensions/groups/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Service Configuration -date: 2018-05-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/groups -geekdocFilePath: configuration.md -geekdocCollapseSection: true ---- - -## Example YAML Config - -{{< include file="extensions/_includes/groups-config-example.yaml" language="yaml" >}} - -{{< include file="extensions/_includes/groups_configvars.md" >}} diff --git a/docs/extensions/idm/_index.md b/docs/extensions/idm/_index.md deleted file mode 100644 index 0a21994c827..00000000000 --- a/docs/extensions/idm/_index.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: IDM -date: 2022-03-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/idm -geekdocFilePath: _index.md -geekdocCollapseSection: true ---- - -## Abstract - -The IDM service provides a minimal LDAP Service (based on https://github.com/libregraph/idm) for oCIS. It is started as part of -the default configuration and serves as a central place for storing user and group information. - -It is mainly targeted at small oCIS installations. For larger setups it is recommended to replace IDM with a "real" LDAP server -or to switch to an external Identity Management Solution. - -IDM listens on port 9325 by default. In the default configuration it only accepts TLS protected connections (LDAPS). The BaseDN -of the LDAP tree is `o=libregraph-idm`. IDM gives LDAP write permissions to a single user -(DN: `uid=libregraph,ou=sysusers,o=libregraph-idm`). Any other authenticated user has read-only access. IDM stores its data in a -[boltdb](https://github.com/etcd-io/bbolt) file `idm/ocis.boltdb` inside the oCIS base data directory. - -Note: IDM is limited in its functionality. It only supports a subset of the LDAP operations (namely BIND, SEARCH, ADD, MODIFY, DELETE). -Also IDM currently does not do any schema verification (e.g. structural vs. auxiliary object classes, require and option attributes, -syntax checks, ...). So it's not meant as a general purpose LDAP server. - -## Table of Contents - -{{< toc-tree >}} diff --git a/docs/extensions/idm/configuration.md b/docs/extensions/idm/configuration.md deleted file mode 100644 index 1595260c784..00000000000 --- a/docs/extensions/idm/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Service Configuration -date: 2018-05-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/idm -geekdocFilePath: configuration.md -geekdocCollapseSection: true ---- - -## Example YAML Config - -{{< include file="extensions/_includes/idm-config-example.yaml" language="yaml" >}} - -{{< include file="extensions/_includes/idm_configvars.md" >}} \ No newline at end of file diff --git a/docs/extensions/idp/_index.md b/docs/extensions/idp/_index.md deleted file mode 100644 index e4ab82677c4..00000000000 --- a/docs/extensions/idp/_index.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: IDP -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/idp -geekdocFilePath: _index.md -geekdocCollapseSection: true ---- - -This service provides an OpenID Connect provider which is the default way to authenticate in oCIS. diff --git a/docs/extensions/idp/configuration.md b/docs/extensions/idp/configuration.md deleted file mode 100644 index 8aa88d203bc..00000000000 --- a/docs/extensions/idp/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Service Configuration -date: 2018-05-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/idp -geekdocFilePath: configuration.md -geekdocCollapseSection: true ---- - -## Example YAML Config - -{{< include file="extensions/_includes/idp-config-example.yaml" language="yaml" >}} - -{{< include file="extensions/_includes/idp_configvars.md" >}} \ No newline at end of file diff --git a/docs/extensions/nats/_index.md b/docs/extensions/nats/_index.md deleted file mode 100644 index bd785e22233..00000000000 --- a/docs/extensions/nats/_index.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: NATS -date: 2022-03-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/nats -geekdocFilePath: _index.md -geekdocCollapseSection: true ---- - -## Abstract - - -## Table of Contents - -{{< toc-tree >}} \ No newline at end of file diff --git a/docs/extensions/nats/configuration.md b/docs/extensions/nats/configuration.md deleted file mode 100644 index 4d414c90dee..00000000000 --- a/docs/extensions/nats/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Service Configuration -date: 2018-05-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/nats -geekdocFilePath: configuration.md -geekdocCollapseSection: true ---- - -## Example YAML Config - -{{< include file="extensions/_includes/nats-config-example.yaml" language="yaml" >}} - -{{< include file="extensions/_includes/nats_configvars.md" >}} \ No newline at end of file diff --git a/docs/extensions/notifications/_index.md b/docs/extensions/notifications/_index.md deleted file mode 100644 index 0cc731856eb..00000000000 --- a/docs/extensions/notifications/_index.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: Notifications -date: 2022-03-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/notifications -geekdocFilePath: _index.md -geekdocCollapseSection: true ---- - -## Abstract - -The notifications extension is responsible for making users aware of changes. It listens on the event bus, filters relevant events, looks up the recipients email address and then queues an email with an external MTA. - -## Table of Contents - -{{< toc-tree >}} \ No newline at end of file diff --git a/docs/extensions/notifications/configuration.md b/docs/extensions/notifications/configuration.md deleted file mode 100644 index 7e260efbd5b..00000000000 --- a/docs/extensions/notifications/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Service Configuration -date: 2018-05-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/notifications -geekdocFilePath: configuration.md -geekdocCollapseSection: true ---- - -## Example YAML Config - -{{< include file="extensions/_includes/notifications-config-example.yaml" language="yaml" >}} - -{{< include file="extensions/_includes/notifications_configvars.md" >}} \ No newline at end of file diff --git a/docs/extensions/ocdav/_index.md b/docs/extensions/ocdav/_index.md deleted file mode 100644 index 62f55ff1b51..00000000000 --- a/docs/extensions/ocdav/_index.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -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 deleted file mode 100644 index 9ca88c1de0a..00000000000 --- a/docs/extensions/ocdav/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -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/ocs/_index.md b/docs/extensions/ocs/_index.md deleted file mode 100644 index 70796fe3c4b..00000000000 --- a/docs/extensions/ocs/_index.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: "Ocs" -date: 2018-05-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/ocs -geekdocFilePath: _index.md -geekdocCollapseSection: true ---- - -This service provides the OCS API which is required by some ownCloud clients. diff --git a/docs/extensions/ocs/configuration.md b/docs/extensions/ocs/configuration.md deleted file mode 100644 index af9bd28c453..00000000000 --- a/docs/extensions/ocs/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Service Configuration -date: 2018-05-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/ocs -geekdocFilePath: configuration.md -geekdocCollapseSection: true ---- - -## Example YAML Config - -{{< include file="extensions/_includes/ocs-config-example.yaml" language="yaml" >}} - -{{< include file="extensions/_includes/ocs_configvars.md" >}} \ No newline at end of file diff --git a/docs/extensions/proxy/_index.md b/docs/extensions/proxy/_index.md deleted file mode 100644 index b7aaaef7144..00000000000 --- a/docs/extensions/proxy/_index.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: Proxy -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/proxy -geekdocFilePath: _index.md -geekdocCollapseSection: true ---- - -This service provides a proxy service that routes requests to the correct extensions. diff --git a/docs/extensions/proxy/configuration.md b/docs/extensions/proxy/configuration.md deleted file mode 100644 index e127a101553..00000000000 --- a/docs/extensions/proxy/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Service Configuration -date: 2018-05-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/proxy -geekdocFilePath: configuration.md -geekdocCollapseSection: true ---- - -## Example YAML Config - -{{< include file="extensions/_includes/proxy-config-example.yaml" language="yaml" >}} - -{{< include file="extensions/_includes/proxy_configvars.md" >}} \ No newline at end of file diff --git a/docs/extensions/search/_index.md b/docs/extensions/search/_index.md deleted file mode 100644 index b00af59fc7c..00000000000 --- a/docs/extensions/search/_index.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: Search -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/search -geekdocFilePath: _index.md -geekdocCollapseSection: true ---- - -This service provides search functionality. diff --git a/docs/extensions/search/configuration.md b/docs/extensions/search/configuration.md deleted file mode 100644 index a99f2f87f79..00000000000 --- a/docs/extensions/search/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Service Configuration -date: 2018-05-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/search -geekdocFilePath: configuration.md -geekdocCollapseSection: true ---- - -## Example YAML Config - -{{< include file="extensions/_includes/search-config-example.yaml" language="yaml" >}} - -{{< include file="extensions/_includes/search_configvars.md" >}} diff --git a/docs/extensions/settings/_index.md b/docs/extensions/settings/_index.md deleted file mode 100644 index fabaf141d0e..00000000000 --- a/docs/extensions/settings/_index.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: "Settings" -date: 2018-05-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/settings -geekdocFilePath: _index.md -geekdocCollapseSection: true ---- - -## Abstract - -When using oCIS, the requirement to store settings arises. This extension provides functionality -for other extensions to register new settings within oCIS. It is responsible for storing the respective -settings values as well. - -For ease of use, this extension provides an ocis-web extension which allows users to change their settings values. -Please refer to the [ocis-web extension docs]({{< ref "../../ocis/development/extensions/#external-ownCloud-Web-apps" >}}) -for running ocis-web extensions. - -{{< mermaid class="text-center">}} -graph TD - subgraph ow[ocis-web] - ows[ocis-web-settings] - owc[ocis-web-core] - end - ows ---|"listSettingsBundles(),
saveSettingsValue(value)"| os[ocis-settings] - owc ---|"listSettingsValues()"| sdk[oC SDK] - sdk --- sdks{ocis-settings
available?} - sdks ---|"yes"| os - sdks ---|"no"| defaults[Use set of
default values] - oa[oCIS extensions
e.g. ocis-accounts] ---|"saveSettingsBundle(bundle)"| os -{{< /mermaid >}} - -The diagram shows how the settings service integrates into oCIS: - -**Settings management:** -- oCIS extensions can register *settings bundles* with the ocis-settings service. -- The settings frontend can be plugged into ocis-web, showing forms for changing *settings values* as a user. -The forms are generated from the registered *settings bundles*. - -**Settings usage:** -- Extensions can query ocis-settings for *settings values* of a user. -- The ownCloud SDK, used as a data abstraction layer for ocis-web, will query ocis-settings for *settings values* of a user, -if it's available. The SDK uses sensible defaults when ocis-settings is not part of the setup. - -For compatibility with ownCloud 10, a migration of ownCloud 10 settings into the storage of ocis-settings will be available. diff --git a/docs/extensions/settings/configuration.md b/docs/extensions/settings/configuration.md deleted file mode 100644 index ac61a75705e..00000000000 --- a/docs/extensions/settings/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Service Configuration -date: 2018-05-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/settings -geekdocFilePath: configuration.md -geekdocCollapseSection: true ---- - -## Example YAML Config - -{{< include file="extensions/_includes/settings-config-example.yaml" language="yaml" >}} - -{{< include file="extensions/_includes/settings_configvars.md" >}} \ No newline at end of file diff --git a/docs/extensions/settings/releasing.md b/docs/extensions/settings/releasing.md deleted file mode 100644 index f67e27a3eb4..00000000000 --- a/docs/extensions/settings/releasing.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: "Releasing" -weight: 70 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/settings -geekdocFilePath: releasing.md ---- - -{{< toc >}} - -## Requirements - -You need a working installation of [the Go programming language](https://golang.org/), [the Node runtime](https://nodejs.org/) and [the Yarn package manager](https://yarnpkg.com/) installed to build the assets for a working release. - -## Releasing - -The settings service doesn't have a dedicated release process. Simply commit your changes, make sure linting and unit tests pass locally and open a pull request. - -### Package Hierarchy - -- [ocis](https://github.com/owncloud/ocis) - - [ocis-settings](https://github.com/owncloud/ocis/tree/master/settings) diff --git a/docs/extensions/sharing/_index.md b/docs/extensions/sharing/_index.md deleted file mode 100644 index e96801c9e44..00000000000 --- a/docs/extensions/sharing/_index.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: Sharing -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/sharing -geekdocFilePath: _index.md -geekdocCollapseSection: true ---- - -This service provides sharing functionality. diff --git a/docs/extensions/sharing/configuration.md b/docs/extensions/sharing/configuration.md deleted file mode 100644 index 199dd30ec8c..00000000000 --- a/docs/extensions/sharing/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Service Configuration -date: 2018-05-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/sharing -geekdocFilePath: configuration.md -geekdocCollapseSection: true ---- - -## Example YAML Config - -{{< include file="extensions/_includes/sharing-config-example.yaml" language="yaml" >}} - -{{< include file="extensions/_includes/sharing_configvars.md" >}} diff --git a/docs/extensions/storage-publiclink/_index.md b/docs/extensions/storage-publiclink/_index.md deleted file mode 100644 index e5ad8de52e3..00000000000 --- a/docs/extensions/storage-publiclink/_index.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: Storage-Publiclink -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 deleted file mode 100644 index 6b741c9c155..00000000000 --- a/docs/extensions/storage-publiclink/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -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 deleted file mode 100644 index 0f8b2f6013b..00000000000 --- a/docs/extensions/storage-shares/_index.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: Storage-Shares -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 deleted file mode 100644 index 5a63a77c451..00000000000 --- a/docs/extensions/storage-shares/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -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-system/_index.md b/docs/extensions/storage-system/_index.md deleted file mode 100644 index 2556908abe0..00000000000 --- a/docs/extensions/storage-system/_index.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: Storage-System -date: 2022-03-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/storage-system -geekdocFilePath: _index.md -geekdocCollapseSection: true ---- - -## Abstract - - -## Table of Contents - -{{< toc-tree >}} diff --git a/docs/extensions/storage-system/configuration.md b/docs/extensions/storage-system/configuration.md deleted file mode 100644 index ebc68c19be7..00000000000 --- a/docs/extensions/storage-system/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -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-system -geekdocFilePath: configuration.md -geekdocCollapseSection: true ---- - -## Example YAML Config - -{{< include file="extensions/_includes/storage-system-config-example.yaml" language="yaml" >}} - -{{< include file="extensions/_includes/storage-system_configvars.md" >}} diff --git a/docs/extensions/storage-users/_index.md b/docs/extensions/storage-users/_index.md deleted file mode 100644 index dbddd5532ab..00000000000 --- a/docs/extensions/storage-users/_index.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: Storage-Users -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 deleted file mode 100644 index 0b121cfdcd1..00000000000 --- a/docs/extensions/storage-users/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -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/store/_index.md b/docs/extensions/store/_index.md deleted file mode 100644 index 3638f96524b..00000000000 --- a/docs/extensions/store/_index.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: "Store" -date: 2018-05-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/store -geekdocFilePath: _index.md -geekdocCollapseSection: true ---- - -This service provides ... diff --git a/docs/extensions/store/configuration.md b/docs/extensions/store/configuration.md deleted file mode 100644 index d556218cf14..00000000000 --- a/docs/extensions/store/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Service Configuration -date: 2018-05-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/store -geekdocFilePath: configuration.md -geekdocCollapseSection: true ---- - -## Example YAML Config - -{{< include file="extensions/_includes/store-config-example.yaml" language="yaml" >}} - -{{< include file="extensions/_includes/store_configvars.md" >}} \ No newline at end of file diff --git a/docs/extensions/thumbnails/_index.md b/docs/extensions/thumbnails/_index.md deleted file mode 100644 index 30c3f3fa939..00000000000 --- a/docs/extensions/thumbnails/_index.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: "Thumbnails" -date: 2018-05-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/thumbnails -geekdocFilePath: _index.md -geekdocCollapseSection: true ---- - -This service provides an ocis extensions which generates thumbnails for image files. diff --git a/docs/extensions/thumbnails/configuration.md b/docs/extensions/thumbnails/configuration.md deleted file mode 100644 index 1f10b136676..00000000000 --- a/docs/extensions/thumbnails/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Service Configuration -date: 2018-05-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/thumbnails -geekdocFilePath: configuration.md -geekdocCollapseSection: true ---- - -## Example YAML Config - -{{< include file="extensions/_includes/thumbnails-config-example.yaml" language="yaml" >}} - -{{< include file="extensions/_includes/thumbnails_configvars.md" >}} \ No newline at end of file diff --git a/docs/extensions/users/_index.md b/docs/extensions/users/_index.md deleted file mode 100644 index 1c90dbf56c4..00000000000 --- a/docs/extensions/users/_index.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: Users -date: 2022-03-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/users -geekdocFilePath: _index.md -geekdocCollapseSection: true ---- - -## Abstract - - -## Table of Contents - -{{< toc-tree >}} diff --git a/docs/extensions/users/configuration.md b/docs/extensions/users/configuration.md deleted file mode 100644 index a8aa67a9b36..00000000000 --- a/docs/extensions/users/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Service Configuration -date: 2018-05-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/users -geekdocFilePath: configuration.md -geekdocCollapseSection: true ---- - -## Example YAML Config - -{{< include file="extensions/_includes/users-config-example.yaml" language="yaml" >}} - -{{< include file="extensions/_includes/users_configvars.md" >}} diff --git a/docs/extensions/web/_index.md b/docs/extensions/web/_index.md deleted file mode 100644 index cb8c7900b30..00000000000 --- a/docs/extensions/web/_index.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: "Web" -date: 2018-05-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/web -geekdocFilePath: _index.md -geekdocCollapseSection: true ---- - -This service embeds [ownCloud Web](https://github.com/owncloud/web) to provide a UI for ownCloud Infinite Scale. diff --git a/docs/extensions/web/configuration.md b/docs/extensions/web/configuration.md deleted file mode 100644 index ee3e60d7d8f..00000000000 --- a/docs/extensions/web/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Service Configuration -date: 2018-05-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/web -geekdocFilePath: configuration.md -geekdocCollapseSection: true ---- - -## Example YAML Config - -{{< include file="extensions/_includes/web-config-example.yaml" language="yaml" >}} - -{{< include file="extensions/_includes/web_configvars.md" >}} \ No newline at end of file diff --git a/docs/extensions/web/releasing.md b/docs/extensions/web/releasing.md deleted file mode 100644 index 75f2130ccdc..00000000000 --- a/docs/extensions/web/releasing.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: "Releasing" -weight: 40 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/web -geekdocFilePath: releasing.md ---- - -{{< toc >}} - -## Releasing - -The next generation Web Frontend is shipped as an oCIS Extension. The `ocis-web` extension is also embedded in the single binary and part of the `ocis server` command. - -To update this package within all the deliveries, we need to update the package in the following chain from the bottom to the top. - -### Package Hierarchy - -- [ocis](https://github.com/owncloud/ocis) - - [ocis-web](https://github.com/owncloud/ocis/tree/master/web) - - [ocis-pkg](https://github.com/owncloud/ocis/tree/master/ocis-pkg) - - [ownCloud Web](https://github.com/owncloud/web) - -#### Prerequisites - -Before updating the assets, make sure that [ownCloud Web](https://github.com/owncloud/web) has been released first -and take note of its release tag name. - -#### Updating ocis-web - -1. Create a branch `update-web-$version` in the [ocis repository](https://github.com/owncloud/ocis) -2. Change into web package folder via `cd web` -3. Inside `web/`, update the `Makefile` so that the WEB_ASSETS_VERSION variable references the currently released version of https://github.com/owncloud/web -4. Move to the changelog (`cd ../changelog/`) and add a changelog file to the `unreleased/` folder (You can copy an old web release changelog item as a template) -5. Move to the repo root (`cd ..`)and update the WEB_COMMITID in the `/.drone.env` file to the commit id from the released version (unless the existing commit id is already newer) -6. **Optional:** Test the changes locally by running `cd ocis && go run cmd/ocis/main.go server`, visiting [https://localhost:9200](https://localhost:9200) and confirming everything renders correctly -7. Commit your changes, push them and [create a PR](https://github.com/owncloud/ocis/pulls) diff --git a/docs/extensions/webdav/_index.md b/docs/extensions/webdav/_index.md deleted file mode 100644 index dbf0f98b6c8..00000000000 --- a/docs/extensions/webdav/_index.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: WebDaV -date: 2018-05-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/webdav -geekdocFilePath: _index.md -geekdocCollapseSection: true ---- - -This service provides the WebDAV API which is required by some ownCloud clients. diff --git a/docs/extensions/webdav/configuration.md b/docs/extensions/webdav/configuration.md deleted file mode 100644 index 53c2820012f..00000000000 --- a/docs/extensions/webdav/configuration.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Service Configuration -date: 2018-05-02T00:00:00+00:00 -weight: 20 -geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/webdav -geekdocFilePath: configuration.md -geekdocCollapseSection: true ---- - -## Example YAML Config - -{{< include file="extensions/_includes/webdav-config-example.yaml" language="yaml" >}} - -{{< include file="extensions/_includes/webdav_configvars.md" >}} \ No newline at end of file diff --git a/docs/helpers/adoc-generator.go.tmpl b/docs/helpers/adoc-generator.go.tmpl index 3e64df09ff3..775c17ec724 100644 --- a/docs/helpers/adoc-generator.go.tmpl +++ b/docs/helpers/adoc-generator.go.tmpl @@ -35,7 +35,7 @@ if err != nil { log.Fatal(err) } replacer := strings.NewReplacer( - "github.com/owncloud/ocis/v2/extensions/", "", + "github.com/owncloud/ocis/v2/services/", "", "/pkg/config/defaults", "", ) var fields []ConfigField @@ -48,7 +48,7 @@ m := map[string]interface{}{ {{- end }} } - targetFolder := "../../docs/extensions/_includes/adoc/" + targetFolder := "../../docs/services/_includes/adoc/" for pkg, conf := range m { fields = GetAnnotatedVariables(conf) if len(fields) > 0 { diff --git a/docs/helpers/configenvextractor.go b/docs/helpers/configenvextractor.go index df174223fca..b3fcbffd6db 100644 --- a/docs/helpers/configenvextractor.go +++ b/docs/helpers/configenvextractor.go @@ -20,7 +20,7 @@ var targets = map[string]string{ func main() { fmt.Println("Getting relevant packages") - paths, err := filepath.Glob("../../extensions/*/pkg/config/defaults/defaultconfig.go") + paths, err := filepath.Glob("../../services/*/pkg/config/defaults/defaultconfig.go") if err != nil { log.Fatal(err) } diff --git a/docs/helpers/environment-variable-docs-generator.go.tmpl b/docs/helpers/environment-variable-docs-generator.go.tmpl index c2cac9c7f84..63dc7e56005 100644 --- a/docs/helpers/environment-variable-docs-generator.go.tmpl +++ b/docs/helpers/environment-variable-docs-generator.go.tmpl @@ -29,7 +29,7 @@ if err != nil { log.Fatal(err) } replacer := strings.NewReplacer( - "github.com/owncloud/ocis/v2/extensions/", "", + "github.com/owncloud/ocis/v2/services/", "", "/pkg/config/defaults", "", ) var fields []ConfigField @@ -42,7 +42,7 @@ m := map[string]interface{}{ {{- end }} } - targetFolder := "../../docs/extensions/_includes/" + targetFolder := "../../docs/services/_includes/" for pkg, conf := range m { fields = GetAnnotatedVariables(conf) if len(fields) > 0 { diff --git a/docs/helpers/example-config-generator.go.tmpl b/docs/helpers/example-config-generator.go.tmpl index 8be5b742957..c678a46ff05 100644 --- a/docs/helpers/example-config-generator.go.tmpl +++ b/docs/helpers/example-config-generator.go.tmpl @@ -15,7 +15,7 @@ import ( func main() { replacer := strings.NewReplacer( - "github.com/owncloud/ocis/v2/extensions/", "", + "github.com/owncloud/ocis/v2/services/", "", "/pkg/config/defaults", "", ) cfg := map[string]string{ @@ -35,7 +35,7 @@ func main() { targetFolders := []string{ // TODO: comment in when it is clear how to commit this to the structure of the master|main branch // filepath.Join("../../", pkg, "/config"), - "../../docs/extensions/_includes/", + "../../docs/services/_includes/", } for _, targetFolder := range targetFolders { os.MkdirAll(targetFolder, 0700) diff --git a/docs/ocis/deployment/ocis_individual_services.md b/docs/ocis/deployment/ocis_individual_services.md index 4eb9af586c4..09a64b1857a 100644 --- a/docs/ocis/deployment/ocis_individual_services.md +++ b/docs/ocis/deployment/ocis_individual_services.md @@ -19,7 +19,7 @@ geekdocFilePath: ocis_individual_services.md The docker stack consists of at least 24 containers. One of them is Traefik, a proxy which is terminating ssl and forwards the requests to oCIS in the internal docker network. -The other containers are oCIS extensions, running each one in a separate container. In this example, oCIS uses its internal IDP [LibreGraph Connect]({{< ref "../../extensions/idp" >}}) and the [oCIS storage driver]({{< ref "../storage/storagedrivers" >}}). You also can start more than one container of each service by setting `OCIS_SCALE` to a number greater than 1. Currently this won't scale all services, but we are working on making all service easily scalable. +The other containers are oCIS services, running each one in a separate container. In this example, oCIS uses its internal IDP [LibreGraph Connect]({{< ref "../../services/idp" >}}) and the [oCIS storage driver]({{< ref "../storage/storagedrivers" >}}). You also can start more than one container of each service by setting `OCIS_SCALE` to a number greater than 1. Currently this won't scale all services, but we are working on making all service easily scalable. ## Server Deployment @@ -76,7 +76,7 @@ See also [example server setup]({{< ref "preparing_server" >}}) STORAGE_TRANSFER_SECRET= # Machine auth api key secret. Must be changed in order to have a secure oCIS. Defaults to "change-me-please" OCIS_MACHINE_AUTH_API_KEY= - # Number of services to run for extensions, that currently can be easily scaled. Defaults to 1. + # Number of services to run for services, that currently can be easily scaled. Defaults to 1. OCIS_SCALE= ``` diff --git a/docs/ocis/deployment/ocis_keycloak.md b/docs/ocis/deployment/ocis_keycloak.md index cbeb4934bc2..5fcc955b343 100644 --- a/docs/ocis/deployment/ocis_keycloak.md +++ b/docs/ocis/deployment/ocis_keycloak.md @@ -20,9 +20,9 @@ geekdocFilePath: ocis_keycloak.md The docker stack consists 4 containers. One of them is Traefik, a proxy which is terminating ssl and forwards the requests to oCIS in the internal docker network. It is also responsible for redirecting requests on the OIDC discovery endpoints (e.g. `.well-known/openid-configuration`) to the correct destination in Keycloak. -Keycloak add two containers: Keycloak itself and a PostgreSQL as database. Keycloak will be configured as oCIS' IDP instead of the internal IDP [LibreGraph Connect]({{< ref "../../extensions/idp" >}}) +Keycloak add two containers: Keycloak itself and a PostgreSQL as database. Keycloak will be configured as oCIS' IDP instead of the internal IDP [LibreGraph Connect]({{< ref "../../services/idp" >}}) -The other container is oCIS itself, running all extensions in one container. In this example oCIS uses the [oCIS storage driver]({{< ref "../storage/storagedrivers" >}}) +The other container is oCIS itself, running all services in one container. In this example oCIS uses the [oCIS storage driver]({{< ref "../storage/storagedrivers" >}}) ## Server Deployment diff --git a/docs/ocis/deployment/ocis_traefik.md b/docs/ocis/deployment/ocis_traefik.md index 3c6d56c1b7c..e8651c9ad1a 100644 --- a/docs/ocis/deployment/ocis_traefik.md +++ b/docs/ocis/deployment/ocis_traefik.md @@ -18,7 +18,7 @@ geekdocFilePath: ocis_traefik.md The docker stack consists of two containers. One of them is Traefik, a proxy which is terminating ssl and forwards the requests to oCIS in the internal docker network. -The other one is oCIS itself running all extensions in one container. In this example, oCIS uses its internal IDP [LibreGraph Connect]({{< ref "../../extensions/idp" >}}) and the [oCIS storage driver]({{< ref "../storage/storagedrivers" >}}) +The other one is oCIS itself running all extensions in one container. In this example, oCIS uses its internal IDP [LibreGraph Connect]({{< ref "../../services/idp" >}}) and the [oCIS storage driver]({{< ref "../storage/storagedrivers" >}}) ## Server Deployment diff --git a/docs/ocis/development/extensions.md b/docs/ocis/development/extensions.md index 9eaf3219763..9c07fa97756 100644 --- a/docs/ocis/development/extensions.md +++ b/docs/ocis/development/extensions.md @@ -46,7 +46,7 @@ For a consistent look and feel, ownCloud Web uses an external design library, th ### Settings -An extension likely has some behaviour which the user can configure. Fundamental configuration will often be done by administrators during deployment, via configuration files or by setting environment variables. But for other settings, which are supposed to change more often or which are even user specific, this is not a viable way. Therefore you need to offer the users a UI where they can configure your extension to their liking. Because implementing something like this is a repetitive task among extensions, oCIS already offers the settings extensions which does that for your extension. Your extension just needs to register settings bundles, respective permissions and finally read the current values from the settings service. You can read more on that on the [settings extension]({{< ref "../../extensions/settings" >}}) and see how [oCIS Hello uses these settings](https://owncloud.dev/extensions/ocis_hello/settings/). +An extension likely has some behaviour which the user can configure. Fundamental configuration will often be done by administrators during deployment, via configuration files or by setting environment variables. But for other settings, which are supposed to change more often or which are even user specific, this is not a viable way. Therefore you need to offer the users a UI where they can configure your extension to their liking. Because implementing something like this is a repetitive task among extensions, oCIS already offers the settings extensions which does that for your extension. Your extension just needs to register settings bundles, respective permissions and finally read the current values from the settings service. You can read more on that on the [settings extension]({{< ref "../../services/settings" >}}) and see how [oCIS Hello uses these settings](https://owncloud.dev/extensions/ocis_hello/settings/). ### Proxy diff --git a/docs/extensions/_includes/.gitignore b/docs/services/_includes/.gitignore similarity index 100% rename from docs/extensions/_includes/.gitignore rename to docs/services/_includes/.gitignore diff --git a/docs/extensions/_includes/_index.md b/docs/services/_includes/_index.md similarity index 100% rename from docs/extensions/_includes/_index.md rename to docs/services/_includes/_index.md diff --git a/docs/extensions/_includes/adoc/.gitkeep b/docs/services/_includes/adoc/.gitkeep similarity index 100% rename from docs/extensions/_includes/adoc/.gitkeep rename to docs/services/_includes/adoc/.gitkeep diff --git a/docs/services/app-provider/_index.md b/docs/services/app-provider/_index.md new file mode 100644 index 00000000000..c26192066ec --- /dev/null +++ b/docs/services/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/services/app-provider +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/services/app-provider/configuration.md b/docs/services/app-provider/configuration.md new file mode 100644 index 00000000000..b215390a30a --- /dev/null +++ b/docs/services/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/services/app-provider +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/app-provider-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/app-provider_configvars.md" >}} diff --git a/docs/services/app-registry/_index.md b/docs/services/app-registry/_index.md new file mode 100644 index 00000000000..d3670e210ff --- /dev/null +++ b/docs/services/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/services/app-registry +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/extensions/app-registry/apps.md b/docs/services/app-registry/apps.md similarity index 99% rename from docs/extensions/app-registry/apps.md rename to docs/services/app-registry/apps.md index bfb67a80a22..39f01d65ccb 100644 --- a/docs/extensions/app-registry/apps.md +++ b/docs/services/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/app-registry +geekdocEditPath: edit/master/docs/services/app-registry geekdocFilePath: apps.md --- diff --git a/docs/services/app-registry/configuration.md b/docs/services/app-registry/configuration.md new file mode 100644 index 00000000000..ed96c59023a --- /dev/null +++ b/docs/services/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/services/app-registry +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/app-registry-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/app-registry_configvars.md" >}} diff --git a/docs/services/audit/_index.md b/docs/services/audit/_index.md new file mode 100644 index 00000000000..52abf0d54bd --- /dev/null +++ b/docs/services/audit/_index.md @@ -0,0 +1,16 @@ +--- +title: Audit +date: 2022-03-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/services/audit +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/services/audit/configuration.md b/docs/services/audit/configuration.md new file mode 100644 index 00000000000..fd8026dacd8 --- /dev/null +++ b/docs/services/audit/configuration.md @@ -0,0 +1,16 @@ +--- +title: Service Configuration +date: 2018-05-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/services/audit +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + + +## Example YAML Config + +{{< include file="services/_includes/audit-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/audit_configvars.md" >}} diff --git a/docs/services/auth-basic/_index.md b/docs/services/auth-basic/_index.md new file mode 100644 index 00000000000..9537249763e --- /dev/null +++ b/docs/services/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/services/auth-basic +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/services/auth-basic/configuration.md b/docs/services/auth-basic/configuration.md new file mode 100644 index 00000000000..672746f057c --- /dev/null +++ b/docs/services/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/services/auth-basic +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/auth-basic-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/auth-basic_configvars.md" >}} diff --git a/docs/services/auth-bearer/_index.md b/docs/services/auth-bearer/_index.md new file mode 100644 index 00000000000..0a867d48a3a --- /dev/null +++ b/docs/services/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/services/auth-bearer +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/services/auth-bearer/configuration.md b/docs/services/auth-bearer/configuration.md new file mode 100644 index 00000000000..5b3ef4ef6db --- /dev/null +++ b/docs/services/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/services/auth-bearer +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/auth-bearer-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/auth-bearer_configvars.md" >}} diff --git a/docs/services/auth-machine/_index.md b/docs/services/auth-machine/_index.md new file mode 100644 index 00000000000..edb7c4afd26 --- /dev/null +++ b/docs/services/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/services/auth-machine +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/services/auth-machine/configuration.md b/docs/services/auth-machine/configuration.md new file mode 100644 index 00000000000..000c6bae317 --- /dev/null +++ b/docs/services/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/services/auth-machine +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/auth-machine-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/auth-machine_configvars.md" >}} diff --git a/docs/services/frontend/_index.md b/docs/services/frontend/_index.md new file mode 100644 index 00000000000..097fde9320d --- /dev/null +++ b/docs/services/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/services/frontend +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/services/frontend/configuration.md b/docs/services/frontend/configuration.md new file mode 100644 index 00000000000..5822f76c9e3 --- /dev/null +++ b/docs/services/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/services/frontend +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/frontend-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/frontend_configvars.md" >}} diff --git a/docs/services/gateway/_index.md b/docs/services/gateway/_index.md new file mode 100644 index 00000000000..90d1e88ce24 --- /dev/null +++ b/docs/services/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/services/gateway +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/services/gateway/configuration.md b/docs/services/gateway/configuration.md new file mode 100644 index 00000000000..ff90bf0b4ee --- /dev/null +++ b/docs/services/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/services/gateway +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/gateway-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/gateway_configvars.md" >}} diff --git a/docs/extensions/graph-explorer/.gitignore b/docs/services/graph-explorer/.gitignore similarity index 100% rename from docs/extensions/graph-explorer/.gitignore rename to docs/services/graph-explorer/.gitignore diff --git a/docs/services/graph-explorer/_index.md b/docs/services/graph-explorer/_index.md new file mode 100644 index 00000000000..917b059f1a2 --- /dev/null +++ b/docs/services/graph-explorer/_index.md @@ -0,0 +1,11 @@ +--- +title: "Graph-Explorer" +date: 2018-05-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/services/graph-explorer +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +This service embeds [Graph-Explorer](https://github.com/owncloud/ocis/tree/master/graph-explorer) to provide a UI for ownCloud Infinite Scale. diff --git a/docs/services/graph-explorer/configuration.md b/docs/services/graph-explorer/configuration.md new file mode 100644 index 00000000000..910c9ed678e --- /dev/null +++ b/docs/services/graph-explorer/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/services/graph-explorer +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/graph-explorer-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/graph-explorer_configvars.md" >}} \ No newline at end of file diff --git a/docs/extensions/graph/.gitignore b/docs/services/graph/.gitignore similarity index 100% rename from docs/extensions/graph/.gitignore rename to docs/services/graph/.gitignore diff --git a/docs/services/graph/_index.md b/docs/services/graph/_index.md new file mode 100644 index 00000000000..8344431b3d3 --- /dev/null +++ b/docs/services/graph/_index.md @@ -0,0 +1,13 @@ +--- +title: "Graph" +date: 2018-05-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/services/graph +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +This service provides a simple graph world API which can be used by clients or other extensions. + +{{< toc-tree >}} diff --git a/docs/services/graph/configuration.md b/docs/services/graph/configuration.md new file mode 100644 index 00000000000..2ca0cc0a1cf --- /dev/null +++ b/docs/services/graph/configuration.md @@ -0,0 +1,14 @@ +--- +title: Service Configuration +date: 2018-05-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/services/graph +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- +## Example YAML Config + +{{< include file="services/_includes/graph-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/graph_configvars.md" >}} \ No newline at end of file diff --git a/docs/extensions/graph/groups.md b/docs/services/graph/groups.md similarity index 99% rename from docs/extensions/graph/groups.md rename to docs/services/graph/groups.md index 74b42c95d39..0a94422888a 100644 --- a/docs/extensions/graph/groups.md +++ b/docs/services/graph/groups.md @@ -2,7 +2,7 @@ title: Groups weight: 40 geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/graph +geekdocEditPath: edit/master/docs/services/graph geekdocFilePath: users.md --- diff --git a/docs/extensions/graph/spaces.md b/docs/services/graph/spaces.md similarity index 99% rename from docs/extensions/graph/spaces.md rename to docs/services/graph/spaces.md index f20db0e2e52..474045761e2 100644 --- a/docs/extensions/graph/spaces.md +++ b/docs/services/graph/spaces.md @@ -2,7 +2,7 @@ title: Spaces weight: 20 geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/graph +geekdocEditPath: edit/master/docs/services/graph geekdocFilePath: spaces.md --- diff --git a/docs/extensions/graph/users.md b/docs/services/graph/users.md similarity index 99% rename from docs/extensions/graph/users.md rename to docs/services/graph/users.md index e14c403f6d9..745b2d9be41 100644 --- a/docs/extensions/graph/users.md +++ b/docs/services/graph/users.md @@ -2,7 +2,7 @@ title: Users weight: 30 geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/graph +geekdocEditPath: edit/master/docs/services/graph geekdocFilePath: users.md --- diff --git a/docs/services/groups/_index.md b/docs/services/groups/_index.md new file mode 100644 index 00000000000..05816eff2ee --- /dev/null +++ b/docs/services/groups/_index.md @@ -0,0 +1,16 @@ +--- +title: Groups +date: 2022-03-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/services/groups +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/services/groups/configuration.md b/docs/services/groups/configuration.md new file mode 100644 index 00000000000..2e144539207 --- /dev/null +++ b/docs/services/groups/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/services/groups +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/groups-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/groups_configvars.md" >}} diff --git a/docs/services/idm/_index.md b/docs/services/idm/_index.md new file mode 100644 index 00000000000..47829d34ecc --- /dev/null +++ b/docs/services/idm/_index.md @@ -0,0 +1,30 @@ +--- +title: IDM +date: 2022-03-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/services/idm +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + +The IDM service provides a minimal LDAP Service (based on https://github.com/libregraph/idm) for oCIS. It is started as part of +the default configuration and serves as a central place for storing user and group information. + +It is mainly targeted at small oCIS installations. For larger setups it is recommended to replace IDM with a "real" LDAP server +or to switch to an external Identity Management Solution. + +IDM listens on port 9325 by default. In the default configuration it only accepts TLS protected connections (LDAPS). The BaseDN +of the LDAP tree is `o=libregraph-idm`. IDM gives LDAP write permissions to a single user +(DN: `uid=libregraph,ou=sysusers,o=libregraph-idm`). Any other authenticated user has read-only access. IDM stores its data in a +[boltdb](https://github.com/etcd-io/bbolt) file `idm/ocis.boltdb` inside the oCIS base data directory. + +Note: IDM is limited in its functionality. It only supports a subset of the LDAP operations (namely BIND, SEARCH, ADD, MODIFY, DELETE). +Also IDM currently does not do any schema verification (e.g. structural vs. auxiliary object classes, require and option attributes, +syntax checks, ...). So it's not meant as a general purpose LDAP server. + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/services/idm/configuration.md b/docs/services/idm/configuration.md new file mode 100644 index 00000000000..6bb9b556d64 --- /dev/null +++ b/docs/services/idm/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/services/idm +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/idm-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/idm_configvars.md" >}} \ No newline at end of file diff --git a/docs/extensions/idm/configuration_hints.md b/docs/services/idm/configuration_hints.md similarity index 97% rename from docs/extensions/idm/configuration_hints.md rename to docs/services/idm/configuration_hints.md index ced032337cf..5f6d8a5ac07 100644 --- a/docs/extensions/idm/configuration_hints.md +++ b/docs/services/idm/configuration_hints.md @@ -3,7 +3,7 @@ title: Configuration Hints date: 2022-04-27:00:00+00:00 weight: 20 geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/idm +geekdocEditPath: edit/master/docs/services/idm geekdocFilePath: configuration_hints.md geekdocCollapseSection: true --- diff --git a/docs/extensions/idp/.gitignore b/docs/services/idp/.gitignore similarity index 100% rename from docs/extensions/idp/.gitignore rename to docs/services/idp/.gitignore diff --git a/docs/services/idp/_index.md b/docs/services/idp/_index.md new file mode 100644 index 00000000000..5fc69e8552d --- /dev/null +++ b/docs/services/idp/_index.md @@ -0,0 +1,10 @@ +--- +title: IDP +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/services/idp +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +This service provides an OpenID Connect provider which is the default way to authenticate in oCIS. diff --git a/docs/services/idp/configuration.md b/docs/services/idp/configuration.md new file mode 100644 index 00000000000..cf999eabda3 --- /dev/null +++ b/docs/services/idp/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/services/idp +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/idp-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/idp_configvars.md" >}} \ No newline at end of file diff --git a/docs/extensions/idp/theming.md b/docs/services/idp/theming.md similarity index 98% rename from docs/extensions/idp/theming.md rename to docs/services/idp/theming.md index c5b2f37f488..f0b67bacf0e 100644 --- a/docs/extensions/idp/theming.md +++ b/docs/services/idp/theming.md @@ -2,7 +2,7 @@ title: Theming weight: 20 geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/theming +geekdocEditPath: edit/master/docs/services/theming geekdocFilePath: theming.md --- diff --git a/docs/services/nats/_index.md b/docs/services/nats/_index.md new file mode 100644 index 00000000000..789842d9a9c --- /dev/null +++ b/docs/services/nats/_index.md @@ -0,0 +1,16 @@ +--- +title: NATS +date: 2022-03-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/services/nats +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} \ No newline at end of file diff --git a/docs/services/nats/configuration.md b/docs/services/nats/configuration.md new file mode 100644 index 00000000000..9af5ae0b658 --- /dev/null +++ b/docs/services/nats/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/services/nats +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/nats-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/nats_configvars.md" >}} \ No newline at end of file diff --git a/docs/services/notifications/_index.md b/docs/services/notifications/_index.md new file mode 100644 index 00000000000..9d27724ceda --- /dev/null +++ b/docs/services/notifications/_index.md @@ -0,0 +1,17 @@ +--- +title: Notifications +date: 2022-03-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/services/notifications +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + +The notifications extension is responsible for making users aware of changes. It listens on the event bus, filters relevant events, looks up the recipients email address and then queues an email with an external MTA. + +## Table of Contents + +{{< toc-tree >}} \ No newline at end of file diff --git a/docs/services/notifications/configuration.md b/docs/services/notifications/configuration.md new file mode 100644 index 00000000000..de4f28af4ad --- /dev/null +++ b/docs/services/notifications/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/services/notifications +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/notifications-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/notifications_configvars.md" >}} \ No newline at end of file diff --git a/docs/services/ocdav/_index.md b/docs/services/ocdav/_index.md new file mode 100644 index 00000000000..24f9202bedb --- /dev/null +++ b/docs/services/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/services/ocdav +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/services/ocdav/configuration.md b/docs/services/ocdav/configuration.md new file mode 100644 index 00000000000..d9df0508717 --- /dev/null +++ b/docs/services/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/services/ocdav +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/ocdav-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/ocdav_configvars.md" >}} diff --git a/docs/extensions/ocs/.gitignore b/docs/services/ocs/.gitignore similarity index 100% rename from docs/extensions/ocs/.gitignore rename to docs/services/ocs/.gitignore diff --git a/docs/services/ocs/_index.md b/docs/services/ocs/_index.md new file mode 100644 index 00000000000..609a362d645 --- /dev/null +++ b/docs/services/ocs/_index.md @@ -0,0 +1,11 @@ +--- +title: "Ocs" +date: 2018-05-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/services/ocs +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +This service provides the OCS API which is required by some ownCloud clients. diff --git a/docs/services/ocs/configuration.md b/docs/services/ocs/configuration.md new file mode 100644 index 00000000000..cb8afa4319e --- /dev/null +++ b/docs/services/ocs/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/services/ocs +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/ocs-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/ocs_configvars.md" >}} \ No newline at end of file diff --git a/docs/extensions/port-ranges.md b/docs/services/port-ranges.md similarity index 100% rename from docs/extensions/port-ranges.md rename to docs/services/port-ranges.md diff --git a/docs/extensions/proxy/.gitignore b/docs/services/proxy/.gitignore similarity index 100% rename from docs/extensions/proxy/.gitignore rename to docs/services/proxy/.gitignore diff --git a/docs/services/proxy/_index.md b/docs/services/proxy/_index.md new file mode 100644 index 00000000000..cad3836ad12 --- /dev/null +++ b/docs/services/proxy/_index.md @@ -0,0 +1,10 @@ +--- +title: Proxy +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/services/proxy +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +This service provides a proxy service that routes requests to the correct extensions. diff --git a/docs/services/proxy/configuration.md b/docs/services/proxy/configuration.md new file mode 100644 index 00000000000..d8502d70cc8 --- /dev/null +++ b/docs/services/proxy/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/services/proxy +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/proxy-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/proxy_configvars.md" >}} \ No newline at end of file diff --git a/docs/services/search/_index.md b/docs/services/search/_index.md new file mode 100644 index 00000000000..01768a83269 --- /dev/null +++ b/docs/services/search/_index.md @@ -0,0 +1,10 @@ +--- +title: Search +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/services/search +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +This service provides search functionality. diff --git a/docs/services/search/configuration.md b/docs/services/search/configuration.md new file mode 100644 index 00000000000..1f3e17fc7db --- /dev/null +++ b/docs/services/search/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/services/search +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/search-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/search_configvars.md" >}} diff --git a/docs/extensions/settings/.gitignore b/docs/services/settings/.gitignore similarity index 100% rename from docs/extensions/settings/.gitignore rename to docs/services/settings/.gitignore diff --git a/docs/services/settings/_index.md b/docs/services/settings/_index.md new file mode 100644 index 00000000000..a08d972c0ae --- /dev/null +++ b/docs/services/settings/_index.md @@ -0,0 +1,47 @@ +--- +title: "Settings" +date: 2018-05-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/services/settings +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + +When using oCIS, the requirement to store settings arises. This extension provides functionality +for other extensions to register new settings within oCIS. It is responsible for storing the respective +settings values as well. + +For ease of use, this extension provides an ocis-web extension which allows users to change their settings values. +Please refer to the [ocis-web extension docs]({{< ref "../../ocis/development/extensions/#external-ownCloud-Web-apps" >}}) +for running ocis-web extensions. + +{{< mermaid class="text-center">}} +graph TD + subgraph ow[ocis-web] + ows[ocis-web-settings] + owc[ocis-web-core] + end + ows ---|"listSettingsBundles(),
saveSettingsValue(value)"| os[ocis-settings] + owc ---|"listSettingsValues()"| sdk[oC SDK] + sdk --- sdks{ocis-settings
available?} + sdks ---|"yes"| os + sdks ---|"no"| defaults[Use set of
default values] + oa[oCIS extensions
e.g. ocis-accounts] ---|"saveSettingsBundle(bundle)"| os +{{< /mermaid >}} + +The diagram shows how the settings service integrates into oCIS: + +**Settings management:** +- oCIS extensions can register *settings bundles* with the ocis-settings service. +- The settings frontend can be plugged into ocis-web, showing forms for changing *settings values* as a user. +The forms are generated from the registered *settings bundles*. + +**Settings usage:** +- Extensions can query ocis-settings for *settings values* of a user. +- The ownCloud SDK, used as a data abstraction layer for ocis-web, will query ocis-settings for *settings values* of a user, +if it's available. The SDK uses sensible defaults when ocis-settings is not part of the setup. + +For compatibility with ownCloud 10, a migration of ownCloud 10 settings into the storage of ocis-settings will be available. diff --git a/docs/extensions/settings/bundles.md b/docs/services/settings/bundles.md similarity index 97% rename from docs/extensions/settings/bundles.md rename to docs/services/settings/bundles.md index 6ee245d617c..e9cd6137f98 100644 --- a/docs/extensions/settings/bundles.md +++ b/docs/services/settings/bundles.md @@ -3,7 +3,7 @@ title: "Settings Bundles" date: 2020-05-04T00:00:00+00:00 weight: 50 geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/settings +geekdocEditPath: edit/master/docs/services/settings geekdocFilePath: bundles.md --- diff --git a/docs/services/settings/configuration.md b/docs/services/settings/configuration.md new file mode 100644 index 00000000000..8fb1b92b471 --- /dev/null +++ b/docs/services/settings/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/services/settings +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/settings-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/settings_configvars.md" >}} \ No newline at end of file diff --git a/docs/extensions/settings/glossary.md b/docs/services/settings/glossary.md similarity index 94% rename from docs/extensions/settings/glossary.md rename to docs/services/settings/glossary.md index 6c7153f6098..50fd82be771 100644 --- a/docs/extensions/settings/glossary.md +++ b/docs/services/settings/glossary.md @@ -3,7 +3,7 @@ title: "Glossary" date: 2020-05-04T12:35:00+01:00 weight: 80 geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/settings +geekdocEditPath: edit/master/docs/services/settings geekdocFilePath: glossary.md --- diff --git a/docs/services/settings/releasing.md b/docs/services/settings/releasing.md new file mode 100644 index 00000000000..1fa7cca59db --- /dev/null +++ b/docs/services/settings/releasing.md @@ -0,0 +1,22 @@ +--- +title: "Releasing" +weight: 70 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/services/settings +geekdocFilePath: releasing.md +--- + +{{< toc >}} + +## Requirements + +You need a working installation of [the Go programming language](https://golang.org/), [the Node runtime](https://nodejs.org/) and [the Yarn package manager](https://yarnpkg.com/) installed to build the assets for a working release. + +## Releasing + +The settings service doesn't have a dedicated release process. Simply commit your changes, make sure linting and unit tests pass locally and open a pull request. + +### Package Hierarchy + +- [ocis](https://github.com/owncloud/ocis) + - [ocis-settings](https://github.com/owncloud/ocis/tree/master/settings) diff --git a/docs/extensions/settings/tests.md b/docs/services/settings/tests.md similarity index 98% rename from docs/extensions/settings/tests.md rename to docs/services/settings/tests.md index 5d7d243d9ba..c367552b874 100644 --- a/docs/extensions/settings/tests.md +++ b/docs/services/settings/tests.md @@ -2,7 +2,7 @@ title: "Tests" weight: 90 geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/settings +geekdocEditPath: edit/master/docs/services/settings geekdocFilePath: tests.md --- diff --git a/docs/extensions/settings/values.md b/docs/services/settings/values.md similarity index 97% rename from docs/extensions/settings/values.md rename to docs/services/settings/values.md index 545e3451341..5cf0240fc5f 100644 --- a/docs/extensions/settings/values.md +++ b/docs/services/settings/values.md @@ -3,7 +3,7 @@ title: "Settings Values" date: 2020-05-04T00:00:00+00:00 weight: 51 geekdocRepo: https://github.com/owncloud/ocis -geekdocEditPath: edit/master/docs/extensions/settings +geekdocEditPath: edit/master/docs/services/settings geekdocFilePath: values.md --- diff --git a/docs/services/sharing/_index.md b/docs/services/sharing/_index.md new file mode 100644 index 00000000000..86eb1fed2f6 --- /dev/null +++ b/docs/services/sharing/_index.md @@ -0,0 +1,10 @@ +--- +title: Sharing +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/services/sharing +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +This service provides sharing functionality. diff --git a/docs/services/sharing/configuration.md b/docs/services/sharing/configuration.md new file mode 100644 index 00000000000..21f7638805d --- /dev/null +++ b/docs/services/sharing/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/services/sharing +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/sharing-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/sharing_configvars.md" >}} diff --git a/docs/services/storage-publiclink/_index.md b/docs/services/storage-publiclink/_index.md new file mode 100644 index 00000000000..1a87d00b437 --- /dev/null +++ b/docs/services/storage-publiclink/_index.md @@ -0,0 +1,16 @@ +--- +title: Storage-Publiclink +date: 2022-03-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/services/storage-publiclink +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/services/storage-publiclink/configuration.md b/docs/services/storage-publiclink/configuration.md new file mode 100644 index 00000000000..9c34ac21bdd --- /dev/null +++ b/docs/services/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/services/storage-publiclink +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/storage-publiclink-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/storage-publiclink_configvars.md" >}} diff --git a/docs/services/storage-shares/_index.md b/docs/services/storage-shares/_index.md new file mode 100644 index 00000000000..223ce0f6a3d --- /dev/null +++ b/docs/services/storage-shares/_index.md @@ -0,0 +1,16 @@ +--- +title: Storage-Shares +date: 2022-03-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/services/storage-shares +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/services/storage-shares/configuration.md b/docs/services/storage-shares/configuration.md new file mode 100644 index 00000000000..068af4d7a57 --- /dev/null +++ b/docs/services/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/services/storage-shares +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/storage-shares-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/storage-shares_configvars.md" >}} diff --git a/docs/services/storage-system/_index.md b/docs/services/storage-system/_index.md new file mode 100644 index 00000000000..b79c493097a --- /dev/null +++ b/docs/services/storage-system/_index.md @@ -0,0 +1,16 @@ +--- +title: Storage-System +date: 2022-03-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/services/storage-system +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/services/storage-system/configuration.md b/docs/services/storage-system/configuration.md new file mode 100644 index 00000000000..4e58119d613 --- /dev/null +++ b/docs/services/storage-system/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/services/storage-system +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/storage-system-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/storage-system_configvars.md" >}} diff --git a/docs/services/storage-users/_index.md b/docs/services/storage-users/_index.md new file mode 100644 index 00000000000..2dcb7544864 --- /dev/null +++ b/docs/services/storage-users/_index.md @@ -0,0 +1,16 @@ +--- +title: Storage-Users +date: 2022-03-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/services/storage-users +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/services/storage-users/configuration.md b/docs/services/storage-users/configuration.md new file mode 100644 index 00000000000..156fb3fda78 --- /dev/null +++ b/docs/services/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/services/storage-users +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/storage-users-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/storage-users_configvars.md" >}} diff --git a/docs/extensions/store/.gitignore b/docs/services/store/.gitignore similarity index 100% rename from docs/extensions/store/.gitignore rename to docs/services/store/.gitignore diff --git a/docs/services/store/_index.md b/docs/services/store/_index.md new file mode 100644 index 00000000000..0dc1c2e8e4a --- /dev/null +++ b/docs/services/store/_index.md @@ -0,0 +1,11 @@ +--- +title: "Store" +date: 2018-05-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/services/store +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +This service provides ... diff --git a/docs/services/store/configuration.md b/docs/services/store/configuration.md new file mode 100644 index 00000000000..c0070e39fe8 --- /dev/null +++ b/docs/services/store/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/services/store +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/store-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/store_configvars.md" >}} \ No newline at end of file diff --git a/docs/extensions/thumbnails/.gitignore b/docs/services/thumbnails/.gitignore similarity index 100% rename from docs/extensions/thumbnails/.gitignore rename to docs/services/thumbnails/.gitignore diff --git a/docs/services/thumbnails/_index.md b/docs/services/thumbnails/_index.md new file mode 100644 index 00000000000..c90f2811f37 --- /dev/null +++ b/docs/services/thumbnails/_index.md @@ -0,0 +1,11 @@ +--- +title: "Thumbnails" +date: 2018-05-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/services/thumbnails +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +This service provides an ocis extensions which generates thumbnails for image files. diff --git a/docs/services/thumbnails/configuration.md b/docs/services/thumbnails/configuration.md new file mode 100644 index 00000000000..845255d096d --- /dev/null +++ b/docs/services/thumbnails/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/services/thumbnails +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/thumbnails-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/thumbnails_configvars.md" >}} \ No newline at end of file diff --git a/docs/services/users/_index.md b/docs/services/users/_index.md new file mode 100644 index 00000000000..91f38a49860 --- /dev/null +++ b/docs/services/users/_index.md @@ -0,0 +1,16 @@ +--- +title: Users +date: 2022-03-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/services/users +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +## Abstract + + +## Table of Contents + +{{< toc-tree >}} diff --git a/docs/services/users/configuration.md b/docs/services/users/configuration.md new file mode 100644 index 00000000000..1f621a58d0b --- /dev/null +++ b/docs/services/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/services/users +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/users-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/users_configvars.md" >}} diff --git a/docs/extensions/web/.gitignore b/docs/services/web/.gitignore similarity index 100% rename from docs/extensions/web/.gitignore rename to docs/services/web/.gitignore diff --git a/docs/services/web/_index.md b/docs/services/web/_index.md new file mode 100644 index 00000000000..e856f59442e --- /dev/null +++ b/docs/services/web/_index.md @@ -0,0 +1,11 @@ +--- +title: "Web" +date: 2018-05-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/services/web +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +This service embeds [ownCloud Web](https://github.com/owncloud/web) to provide a UI for ownCloud Infinite Scale. diff --git a/docs/services/web/configuration.md b/docs/services/web/configuration.md new file mode 100644 index 00000000000..e0034387190 --- /dev/null +++ b/docs/services/web/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/services/web +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/web-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/web_configvars.md" >}} \ No newline at end of file diff --git a/docs/services/web/releasing.md b/docs/services/web/releasing.md new file mode 100644 index 00000000000..ac9be9f12f1 --- /dev/null +++ b/docs/services/web/releasing.md @@ -0,0 +1,37 @@ +--- +title: "Releasing" +weight: 40 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/services/web +geekdocFilePath: releasing.md +--- + +{{< toc >}} + +## Releasing + +The next generation Web Frontend is shipped as an oCIS Extension. The `ocis-web` extension is also embedded in the single binary and part of the `ocis server` command. + +To update this package within all the deliveries, we need to update the package in the following chain from the bottom to the top. + +### Package Hierarchy + +- [ocis](https://github.com/owncloud/ocis) + - [ocis-web](https://github.com/owncloud/ocis/tree/master/web) + - [ocis-pkg](https://github.com/owncloud/ocis/tree/master/ocis-pkg) + - [ownCloud Web](https://github.com/owncloud/web) + +#### Prerequisites + +Before updating the assets, make sure that [ownCloud Web](https://github.com/owncloud/web) has been released first +and take note of its release tag name. + +#### Updating ocis-web + +1. Create a branch `update-web-$version` in the [ocis repository](https://github.com/owncloud/ocis) +2. Change into web package folder via `cd web` +3. Inside `web/`, update the `Makefile` so that the WEB_ASSETS_VERSION variable references the currently released version of https://github.com/owncloud/web +4. Move to the changelog (`cd ../changelog/`) and add a changelog file to the `unreleased/` folder (You can copy an old web release changelog item as a template) +5. Move to the repo root (`cd ..`)and update the WEB_COMMITID in the `/.drone.env` file to the commit id from the released version (unless the existing commit id is already newer) +6. **Optional:** Test the changes locally by running `cd ocis && go run cmd/ocis/main.go server`, visiting [https://localhost:9200](https://localhost:9200) and confirming everything renders correctly +7. Commit your changes, push them and [create a PR](https://github.com/owncloud/ocis/pulls) diff --git a/docs/extensions/webdav/.gitignore b/docs/services/webdav/.gitignore similarity index 100% rename from docs/extensions/webdav/.gitignore rename to docs/services/webdav/.gitignore diff --git a/docs/services/webdav/_index.md b/docs/services/webdav/_index.md new file mode 100644 index 00000000000..6dd82460e12 --- /dev/null +++ b/docs/services/webdav/_index.md @@ -0,0 +1,11 @@ +--- +title: WebDaV +date: 2018-05-02T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/ocis +geekdocEditPath: edit/master/docs/services/webdav +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +This service provides the WebDAV API which is required by some ownCloud clients. diff --git a/docs/services/webdav/configuration.md b/docs/services/webdav/configuration.md new file mode 100644 index 00000000000..7e0f765b4e0 --- /dev/null +++ b/docs/services/webdav/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/services/webdav +geekdocFilePath: configuration.md +geekdocCollapseSection: true +--- + +## Example YAML Config + +{{< include file="services/_includes/webdav-config-example.yaml" language="yaml" >}} + +{{< include file="services/_includes/webdav_configvars.md" >}} \ No newline at end of file diff --git a/extensions/app-provider/cmd/app-provider/main.go b/extensions/app-provider/cmd/app-provider/main.go deleted file mode 100644 index 43c7ac4783f..00000000000 --- a/extensions/app-provider/cmd/app-provider/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/app-provider/pkg/command" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index f57e9707b9f..00000000000 --- a/extensions/app-provider/pkg/command/health.go +++ /dev/null @@ -1,57 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/app-provider/pkg/config" - "github.com/owncloud/ocis/v2/extensions/app-provider/pkg/config/parser" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 829fb5d133a..00000000000 --- a/extensions/app-provider/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/app-provider/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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-provider 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 app-provider command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new app-provider.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 deleted file mode 100644 index 05acb038a9d..00000000000 --- a/extensions/app-provider/pkg/command/server.go +++ /dev/null @@ -1,108 +0,0 @@ -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/v2/extensions/app-provider/pkg/config" - "github.com/owncloud/ocis/v2/extensions/app-provider/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/app-provider/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/app-provider/pkg/revaconfig" - "github.com/owncloud/ocis/v2/extensions/app-provider/pkg/server/debug" - "github.com/owncloud/ocis/v2/extensions/app-provider/pkg/tracing" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" - "github.com/owncloud/ocis/v2/ocis-pkg/sync" - "github.com/owncloud/ocis/v2/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) - os.Exit(1) - } - 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.GetString(), - 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 deleted file mode 100644 index b65c39ee672..00000000000 --- a/extensions/app-provider/pkg/command/version.go +++ /dev/null @@ -1,50 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - tw "github.com/olekukonko/tablewriter" - "github.com/owncloud/ocis/v2/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.GetString()) - 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/defaults/defaultconfig.go b/extensions/app-provider/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index dc8fc2b0485..00000000000 --- a/extensions/app-provider/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,92 +0,0 @@ -package defaults - -import ( - "github.com/owncloud/ocis/v2/extensions/app-provider/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" -) - -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:9165", - Token: "", - Pprof: false, - Zpages: false, - }, - GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9164", - Namespace: "com.owncloud.api", - Protocol: "tcp", - }, - Service: config.Service{ - Name: "app-provider", - }, - Reva: &config.Reva{ - Address: "127.0.0.1:9142", - }, - Driver: "", - Drivers: config.Drivers{ - WOPI: config.WOPIDriver{}, - }, - } -} - -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 -} - -func Validate(cfg *config.Config) error { - if cfg.TokenManager.JWTSecret == "" { - return shared.MissingJWTTokenError(cfg.Service.Name) - } - - return nil -} diff --git a/extensions/app-provider/pkg/config/parser/parse.go b/extensions/app-provider/pkg/config/parser/parse.go deleted file mode 100644 index 737ce03237e..00000000000 --- a/extensions/app-provider/pkg/config/parser/parse.go +++ /dev/null @@ -1,42 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/app-provider/pkg/config" - "github.com/owncloud/ocis/v2/extensions/app-provider/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - if cfg.TokenManager.JWTSecret == "" { - return shared.MissingJWTTokenError(cfg.Service.Name) - } - - return nil -} diff --git a/extensions/app-provider/pkg/logging/logging.go b/extensions/app-provider/pkg/logging/logging.go deleted file mode 100644 index 5bd741e0638..00000000000 --- a/extensions/app-provider/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/app-provider/pkg/config" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index d8d4f51c8bf..00000000000 --- a/extensions/app-provider/pkg/revaconfig/config.go +++ /dev/null @@ -1,47 +0,0 @@ -package revaconfig - -import ( - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index b0ca9348acf..00000000000 --- a/extensions/app-provider/pkg/server/debug/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/app-provider/pkg/config" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index d54147b6c78..00000000000 --- a/extensions/app-provider/pkg/server/debug/server.go +++ /dev/null @@ -1,63 +0,0 @@ -package debug - -import ( - "io" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/app-provider/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/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.GetString()), - 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 deleted file mode 100644 index 0311f3b8925..00000000000 --- a/extensions/app-provider/pkg/tracing/tracing.go +++ /dev/null @@ -1,18 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/v2/extensions/app-provider/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/owncloud/ocis/v2/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/cmd/app-registry/main.go b/extensions/app-registry/cmd/app-registry/main.go deleted file mode 100644 index 023b61ff432..00000000000 --- a/extensions/app-registry/cmd/app-registry/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/app-registry/pkg/command" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 912ecdfb02b..00000000000 --- a/extensions/app-registry/pkg/command/health.go +++ /dev/null @@ -1,57 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/app-registry/pkg/config" - "github.com/owncloud/ocis/v2/extensions/app-registry/pkg/config/parser" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index b2d0fc4dbbc..00000000000 --- a/extensions/app-registry/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/app-registry/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 3dd5a8ffa80..00000000000 --- a/extensions/app-registry/pkg/command/server.go +++ /dev/null @@ -1,103 +0,0 @@ -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/v2/extensions/app-registry/pkg/config" - "github.com/owncloud/ocis/v2/extensions/app-registry/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/app-registry/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/app-registry/pkg/revaconfig" - "github.com/owncloud/ocis/v2/extensions/app-registry/pkg/server/debug" - "github.com/owncloud/ocis/v2/extensions/app-registry/pkg/tracing" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" - "github.com/owncloud/ocis/v2/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) - os.Exit(1) - } - 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.GetString(), - 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 deleted file mode 100644 index 491f02c2805..00000000000 --- a/extensions/app-registry/pkg/command/version.go +++ /dev/null @@ -1,50 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - tw "github.com/olekukonko/tablewriter" - "github.com/owncloud/ocis/v2/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.GetString()) - 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/defaults/defaultconfig.go b/extensions/app-registry/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index 1ff3f4c4dbd..00000000000 --- a/extensions/app-registry/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,155 +0,0 @@ -package defaults - -import ( - "github.com/owncloud/ocis/v2/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/app-registry/pkg/config/parser/parse.go b/extensions/app-registry/pkg/config/parser/parse.go deleted file mode 100644 index 96fd9792be3..00000000000 --- a/extensions/app-registry/pkg/config/parser/parse.go +++ /dev/null @@ -1,42 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/app-registry/pkg/config" - "github.com/owncloud/ocis/v2/extensions/app-registry/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - if cfg.TokenManager.JWTSecret == "" { - return shared.MissingJWTTokenError(cfg.Service.Name) - } - - return nil -} diff --git a/extensions/app-registry/pkg/logging/logging.go b/extensions/app-registry/pkg/logging/logging.go deleted file mode 100644 index 9776ff7481b..00000000000 --- a/extensions/app-registry/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/app-registry/pkg/config" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 7dba83405bb..00000000000 --- a/extensions/app-registry/pkg/revaconfig/config.go +++ /dev/null @@ -1,48 +0,0 @@ -package revaconfig - -import ( - "github.com/owncloud/ocis/v2/ocis-pkg/log" - - "github.com/mitchellh/mapstructure" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 043e3fefe9f..00000000000 --- a/extensions/app-registry/pkg/server/debug/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/app-registry/pkg/config" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 82ffa38add3..00000000000 --- a/extensions/app-registry/pkg/server/debug/server.go +++ /dev/null @@ -1,63 +0,0 @@ -package debug - -import ( - "io" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/app-registry/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/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.GetString()), - 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 deleted file mode 100644 index 2c975afc520..00000000000 --- a/extensions/app-registry/pkg/tracing/tracing.go +++ /dev/null @@ -1,18 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/v2/extensions/app-registry/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/owncloud/ocis/v2/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/audit/cmd/audit/main.go b/extensions/audit/cmd/audit/main.go deleted file mode 100644 index 3263360b48c..00000000000 --- a/extensions/audit/cmd/audit/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/audit/pkg/command" - "github.com/owncloud/ocis/v2/extensions/audit/pkg/config/defaults" -) - -func main() { - if err := command.Execute(defaults.DefaultConfig()); err != nil { - os.Exit(1) - } -} diff --git a/extensions/audit/pkg/command/health.go b/extensions/audit/pkg/command/health.go deleted file mode 100644 index 530bf24cb0a..00000000000 --- a/extensions/audit/pkg/command/health.go +++ /dev/null @@ -1,18 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/extensions/audit/pkg/config" - "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", - Action: func(c *cli.Context) error { - // Not implemented - return nil - }, - } -} diff --git a/extensions/audit/pkg/command/root.go b/extensions/audit/pkg/command/root.go deleted file mode 100644 index 043bf29ef82..00000000000 --- a/extensions/audit/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/audit/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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 audit command. -func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "audit", - Usage: "starts audit service", - Commands: GetCommands(cfg), - }) - - cli.HelpFlag = &cli.BoolFlag{ - Name: "help,h", - Usage: "Show the help", - } - - return app.Run(os.Args) -} - -// SutureService allows for the audit command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new audit.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Audit.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Audit, - } -} - -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/audit/pkg/command/server.go b/extensions/audit/pkg/command/server.go deleted file mode 100644 index 24ea141b42b..00000000000 --- a/extensions/audit/pkg/command/server.go +++ /dev/null @@ -1,60 +0,0 @@ -package command - -import ( - "context" - "fmt" - "os" - - "github.com/cs3org/reva/v2/pkg/events" - "github.com/cs3org/reva/v2/pkg/events/server" - "github.com/go-micro/plugins/v4/events/natsjs" - "github.com/owncloud/ocis/v2/extensions/audit/pkg/config" - "github.com/owncloud/ocis/v2/extensions/audit/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/audit/pkg/logging" - svc "github.com/owncloud/ocis/v2/extensions/audit/pkg/service" - "github.com/owncloud/ocis/v2/extensions/audit/pkg/types" - "github.com/urfave/cli/v2" -) - -// Server is the entrypoint 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) - os.Exit(1) - } - return err - }, - Action: func(c *cli.Context) error { - logger := logging.Configure(cfg.Service.Name, cfg.Log) - - ctx := cfg.Context - if ctx == nil { - ctx = context.Background() - } - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - evtsCfg := cfg.Events - client, err := server.NewNatsStream( - natsjs.Address(evtsCfg.Endpoint), - natsjs.ClusterID(evtsCfg.Cluster), - ) - if err != nil { - return err - } - evts, err := events.Consume(client, evtsCfg.ConsumerGroup, types.RegisteredEvents()...) - if err != nil { - return err - } - - svc.AuditLoggerFromConfig(ctx, cfg.Auditlog, evts, logger) - return nil - }, - } -} diff --git a/extensions/audit/pkg/command/version.go b/extensions/audit/pkg/command/version.go deleted file mode 100644 index 0a90a7e1bbc..00000000000 --- a/extensions/audit/pkg/command/version.go +++ /dev/null @@ -1,19 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/extensions/audit/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 { - // not implemented - return nil - }, - } -} diff --git a/extensions/audit/pkg/config/defaults/defaultconfig.go b/extensions/audit/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index d2db3f7f403..00000000000 --- a/extensions/audit/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,50 +0,0 @@ -package defaults - -import ( - "github.com/owncloud/ocis/v2/extensions/audit/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:9234", - }, - Service: config.Service{ - Name: "audit", - }, - Events: config.Events{ - Endpoint: "127.0.0.1:9233", - Cluster: "ocis-cluster", - ConsumerGroup: "audit", - }, - Auditlog: config.Auditlog{ - LogToConsole: true, - Format: "json", - }, - } -} - -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{} - } -} - -func Sanitize(cfg *config.Config) { - // sanitize config -} diff --git a/extensions/audit/pkg/config/parser/parse.go b/extensions/audit/pkg/config/parser/parse.go deleted file mode 100644 index 677e5906caa..00000000000 --- a/extensions/audit/pkg/config/parser/parse.go +++ /dev/null @@ -1,37 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/audit/pkg/config" - "github.com/owncloud/ocis/v2/extensions/audit/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - return nil -} diff --git a/extensions/audit/pkg/logging/logging.go b/extensions/audit/pkg/logging/logging.go deleted file mode 100644 index 17ab56c5c35..00000000000 --- a/extensions/audit/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/audit/pkg/config" - "github.com/owncloud/ocis/v2/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/audit/pkg/service/service.go b/extensions/audit/pkg/service/service.go deleted file mode 100644 index 1e42a80caa6..00000000000 --- a/extensions/audit/pkg/service/service.go +++ /dev/null @@ -1,170 +0,0 @@ -package svc - -import ( - "context" - "encoding/json" - "fmt" - "os" - - "github.com/cs3org/reva/v2/pkg/events" - "github.com/owncloud/ocis/v2/extensions/audit/pkg/config" - "github.com/owncloud/ocis/v2/extensions/audit/pkg/types" - "github.com/owncloud/ocis/v2/ocis-pkg/log" -) - -// Log is used to log to different outputs -type Log func([]byte) - -// Marshaller is used to marshal events -type Marshaller func(interface{}) ([]byte, error) - -// AuditLoggerFromConfig will start a new AuditLogger generated from the config -func AuditLoggerFromConfig(ctx context.Context, cfg config.Auditlog, ch <-chan interface{}, log log.Logger) { - var logs []Log - - if cfg.LogToConsole { - logs = append(logs, WriteToStdout()) - } - - if cfg.LogToFile { - logs = append(logs, WriteToFile(cfg.FilePath, log)) - } - - StartAuditLogger(ctx, ch, log, Marshal(cfg.Format, log), logs...) - -} - -// StartAuditLogger will block. run in seperate go routine -func StartAuditLogger(ctx context.Context, ch <-chan interface{}, log log.Logger, marshaller Marshaller, logto ...Log) { - for { - select { - case <-ctx.Done(): - return - case i := <-ch: - var auditEvent interface{} - switch ev := i.(type) { - case events.ShareCreated: - auditEvent = types.ShareCreated(ev) - case events.LinkCreated: - auditEvent = types.LinkCreated(ev) - case events.ShareUpdated: - auditEvent = types.ShareUpdated(ev) - case events.LinkUpdated: - auditEvent = types.LinkUpdated(ev) - case events.ShareRemoved: - auditEvent = types.ShareRemoved(ev) - case events.LinkRemoved: - auditEvent = types.LinkRemoved(ev) - case events.ReceivedShareUpdated: - auditEvent = types.ReceivedShareUpdated(ev) - case events.LinkAccessed: - auditEvent = types.LinkAccessed(ev) - case events.LinkAccessFailed: - auditEvent = types.LinkAccessFailed(ev) - case events.ContainerCreated: - auditEvent = types.ContainerCreated(ev) - case events.FileUploaded: - auditEvent = types.FileUploaded(ev) - case events.FileDownloaded: - auditEvent = types.FileDownloaded(ev) - case events.ItemMoved: - auditEvent = types.ItemMoved(ev) - case events.ItemTrashed: - auditEvent = types.ItemTrashed(ev) - case events.ItemPurged: - auditEvent = types.ItemPurged(ev) - case events.ItemRestored: - auditEvent = types.ItemRestored(ev) - case events.FileVersionRestored: - auditEvent = types.FileVersionRestored(ev) - case events.SpaceCreated: - auditEvent = types.SpaceCreated(ev) - case events.SpaceRenamed: - auditEvent = types.SpaceRenamed(ev) - case events.SpaceDisabled: - auditEvent = types.SpaceDisabled(ev) - case events.SpaceEnabled: - auditEvent = types.SpaceEnabled(ev) - case events.SpaceDeleted: - auditEvent = types.SpaceDeleted(ev) - case events.UserCreated: - auditEvent = types.UserCreated(ev) - case events.UserDeleted: - auditEvent = types.UserDeleted(ev) - case events.UserFeatureChanged: - auditEvent = types.UserFeatureChanged(ev) - case events.GroupCreated: - auditEvent = types.GroupCreated(ev) - case events.GroupDeleted: - auditEvent = types.GroupDeleted(ev) - case events.GroupMemberAdded: - auditEvent = types.GroupMemberAdded(ev) - case events.GroupMemberRemoved: - auditEvent = types.GroupMemberRemoved(ev) - default: - log.Error().Interface("event", ev).Msg(fmt.Sprintf("can't handle event of type '%T'", ev)) - continue - - } - - b, err := marshaller(auditEvent) - if err != nil { - log.Error().Err(err).Msg("error marshaling the event") - continue - } - - for _, l := range logto { - l(b) - } - } - } - -} - -// WriteToFile returns a Log function writing to a file -func WriteToFile(path string, log log.Logger) Log { - return func(content []byte) { - file, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) - if err != nil { - log.Error().Err(err).Msgf("error opening file '%s'", path) - return - } - defer file.Close() - if _, err := fmt.Fprintln(file, string(content)); err != nil { - log.Error().Err(err).Msgf("error writing to file '%s'", path) - } - } -} - -// WriteToStdout return a Log function writing to Stdout -func WriteToStdout() Log { - return func(content []byte) { - fmt.Println(string(content)) - } -} - -// Marshal returns a Marshaller from the `format` string -func Marshal(format string, log log.Logger) Marshaller { - switch format { - default: - log.Error().Msgf("unknown format '%s'", format) - return nil - case "json": - return json.Marshal - case "minimal": - return func(ev interface{}) ([]byte, error) { - b, err := json.Marshal(ev) - if err != nil { - return nil, err - } - - m := make(map[string]interface{}) - if err := json.Unmarshal(b, &m); err != nil { - return nil, err - } - - format := fmt.Sprintf("%s)\n %s", m["Action"], m["Message"]) - return []byte(format), nil - } - } -} diff --git a/extensions/auth-basic/cmd/auth-basic/main.go b/extensions/auth-basic/cmd/auth-basic/main.go deleted file mode 100644 index 9aef09d3f72..00000000000 --- a/extensions/auth-basic/cmd/auth-basic/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/auth-basic/pkg/command" - "github.com/owncloud/ocis/v2/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/health.go b/extensions/auth-basic/pkg/command/health.go deleted file mode 100644 index bb4ac624a2f..00000000000 --- a/extensions/auth-basic/pkg/command/health.go +++ /dev/null @@ -1,57 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/auth-basic/pkg/config" - "github.com/owncloud/ocis/v2/extensions/auth-basic/pkg/config/parser" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index c7f7dff3b56..00000000000 --- a/extensions/auth-basic/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/auth-basic/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index c1a1e15fb79..00000000000 --- a/extensions/auth-basic/pkg/command/server.go +++ /dev/null @@ -1,121 +0,0 @@ -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/v2/extensions/auth-basic/pkg/config" - "github.com/owncloud/ocis/v2/extensions/auth-basic/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/auth-basic/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/auth-basic/pkg/revaconfig" - "github.com/owncloud/ocis/v2/extensions/auth-basic/pkg/server/debug" - "github.com/owncloud/ocis/v2/extensions/auth-basic/pkg/tracing" - "github.com/owncloud/ocis/v2/ocis-pkg/ldap" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" - "github.com/owncloud/ocis/v2/ocis-pkg/sync" - "github.com/owncloud/ocis/v2/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) - os.Exit(1) - } - 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.GetString(), - 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 deleted file mode 100644 index c0138a7b4ca..00000000000 --- a/extensions/auth-basic/pkg/command/version.go +++ /dev/null @@ -1,50 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - tw "github.com/olekukonko/tablewriter" - "github.com/owncloud/ocis/v2/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.GetString()) - 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/defaults/defaultconfig.go b/extensions/auth-basic/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index 113e1b11597..00000000000 --- a/extensions/auth-basic/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,126 +0,0 @@ -package defaults - -import ( - "path/filepath" - - "github.com/owncloud/ocis/v2/extensions/auth-basic/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" -) - -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:9147", - Token: "", - Pprof: false, - Zpages: false, - }, - GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9146", - Namespace: "com.owncloud.api", - Protocol: "tcp", - }, - Service: config.Service{ - Name: "auth-basic", - }, - Reva: &config.Reva{ - Address: "127.0.0.1:9142", - }, - AuthProvider: "ldap", - AuthProviders: config.AuthProviders{ - LDAP: config.LDAPProvider{ - URI: "ldaps://localhost:9235", - CACert: filepath.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: "https://localhost:9200", - UserSchema: config.LDAPUserSchema{ - ID: "ownclouduuid", - Mail: "mail", - DisplayName: "displayname", - Username: "uid", - }, - GroupSchema: config.LDAPGroupSchema{ - ID: "ownclouduuid", - Mail: "mail", - DisplayName: "cn", - Groupname: "cn", - Member: "member", - }, - }, - JSON: config.JSONProvider{}, - OwnCloudSQL: config.OwnCloudSQLProvider{ - DBUsername: "owncloud", - DBHost: "mysql", - DBPort: 3306, - DBName: "owncloud", - IDP: "https://localhost:9200", - Nobody: 90, - JoinUsername: false, - JoinOwnCloudUUID: false, - }, - }, - } -} - -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/auth-basic/pkg/config/parser/parse.go b/extensions/auth-basic/pkg/config/parser/parse.go deleted file mode 100644 index e202c43beed..00000000000 --- a/extensions/auth-basic/pkg/config/parser/parse.go +++ /dev/null @@ -1,46 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/auth-basic/pkg/config" - "github.com/owncloud/ocis/v2/extensions/auth-basic/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - if cfg.TokenManager.JWTSecret == "" { - return shared.MissingJWTTokenError(cfg.Service.Name) - } - - if cfg.AuthProviders.LDAP.BindPassword == "" && cfg.AuthProvider == "ldap" { - return shared.MissingLDAPBindPassword(cfg.Service.Name) - } - - return nil -} diff --git a/extensions/auth-basic/pkg/logging/logging.go b/extensions/auth-basic/pkg/logging/logging.go deleted file mode 100644 index 686e809b634..00000000000 --- a/extensions/auth-basic/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/auth-basic/pkg/config" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index cba78917880..00000000000 --- a/extensions/auth-basic/pkg/revaconfig/config.go +++ /dev/null @@ -1,83 +0,0 @@ -package revaconfig - -import "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 0af0e350b06..00000000000 --- a/extensions/auth-basic/pkg/server/debug/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/auth-basic/pkg/config" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index ed1ecb480c7..00000000000 --- a/extensions/auth-basic/pkg/server/debug/server.go +++ /dev/null @@ -1,63 +0,0 @@ -package debug - -import ( - "io" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/auth-basic/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/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.GetString()), - 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 deleted file mode 100644 index bcac0cd9dab..00000000000 --- a/extensions/auth-basic/pkg/tracing/tracing.go +++ /dev/null @@ -1,18 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/v2/extensions/auth-basic/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/owncloud/ocis/v2/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/cmd/auth-bearer/main.go b/extensions/auth-bearer/cmd/auth-bearer/main.go deleted file mode 100644 index 0a6ebecb979..00000000000 --- a/extensions/auth-bearer/cmd/auth-bearer/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/auth-bearer/pkg/command" - "github.com/owncloud/ocis/v2/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/health.go b/extensions/auth-bearer/pkg/command/health.go deleted file mode 100644 index 87cb0991376..00000000000 --- a/extensions/auth-bearer/pkg/command/health.go +++ /dev/null @@ -1,57 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/auth-bearer/pkg/config" - "github.com/owncloud/ocis/v2/extensions/auth-bearer/pkg/config/parser" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index bb78f2efeca..00000000000 --- a/extensions/auth-bearer/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/auth-bearer/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index bbf13f1fc33..00000000000 --- a/extensions/auth-bearer/pkg/command/server.go +++ /dev/null @@ -1,108 +0,0 @@ -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/v2/extensions/auth-bearer/pkg/config" - "github.com/owncloud/ocis/v2/extensions/auth-bearer/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/auth-bearer/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/auth-bearer/pkg/revaconfig" - "github.com/owncloud/ocis/v2/extensions/auth-bearer/pkg/server/debug" - "github.com/owncloud/ocis/v2/extensions/auth-bearer/pkg/tracing" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" - "github.com/owncloud/ocis/v2/ocis-pkg/sync" - "github.com/owncloud/ocis/v2/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) - os.Exit(1) - } - 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.GetString(), - 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 deleted file mode 100644 index ff482957f6a..00000000000 --- a/extensions/auth-bearer/pkg/command/version.go +++ /dev/null @@ -1,50 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - tw "github.com/olekukonko/tablewriter" - "github.com/owncloud/ocis/v2/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.GetString()) - 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/defaults/defaultconfig.go b/extensions/auth-bearer/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index 2a225d96bc4..00000000000 --- a/extensions/auth-bearer/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,84 +0,0 @@ -package defaults - -import ( - "github.com/owncloud/ocis/v2/extensions/auth-bearer/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:9149", - Token: "", - Pprof: false, - Zpages: false, - }, - GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9148", - Namespace: "com.owncloud.api", - Protocol: "tcp", - }, - Service: config.Service{ - Name: "auth-bearer", - }, - Reva: &config.Reva{ - Address: "127.0.0.1:9142", - }, - 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.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/auth-bearer/pkg/config/parser/parse.go b/extensions/auth-bearer/pkg/config/parser/parse.go deleted file mode 100644 index 7ee80bb04e4..00000000000 --- a/extensions/auth-bearer/pkg/config/parser/parse.go +++ /dev/null @@ -1,42 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/auth-bearer/pkg/config" - "github.com/owncloud/ocis/v2/extensions/auth-bearer/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - if cfg.TokenManager.JWTSecret == "" { - return shared.MissingJWTTokenError(cfg.Service.Name) - } - - return nil -} diff --git a/extensions/auth-bearer/pkg/logging/logging.go b/extensions/auth-bearer/pkg/logging/logging.go deleted file mode 100644 index 70706b74a4a..00000000000 --- a/extensions/auth-bearer/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/auth-bearer/pkg/config" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 4925ee993b8..00000000000 --- a/extensions/auth-bearer/pkg/revaconfig/config.go +++ /dev/null @@ -1,38 +0,0 @@ -package revaconfig - -import "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 07ec936357f..00000000000 --- a/extensions/auth-bearer/pkg/server/debug/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/auth-bearer/pkg/config" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 32d5140738f..00000000000 --- a/extensions/auth-bearer/pkg/server/debug/server.go +++ /dev/null @@ -1,63 +0,0 @@ -package debug - -import ( - "io" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/auth-bearer/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/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.GetString()), - 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 deleted file mode 100644 index 67a0dc3f04a..00000000000 --- a/extensions/auth-bearer/pkg/tracing/tracing.go +++ /dev/null @@ -1,18 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/v2/extensions/auth-bearer/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/owncloud/ocis/v2/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/cmd/auth-machine/main.go b/extensions/auth-machine/cmd/auth-machine/main.go deleted file mode 100644 index 5ae469c71c8..00000000000 --- a/extensions/auth-machine/cmd/auth-machine/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/auth-machine/pkg/command" - "github.com/owncloud/ocis/v2/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/health.go b/extensions/auth-machine/pkg/command/health.go deleted file mode 100644 index 50b886f724a..00000000000 --- a/extensions/auth-machine/pkg/command/health.go +++ /dev/null @@ -1,57 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/auth-machine/pkg/config" - "github.com/owncloud/ocis/v2/extensions/auth-machine/pkg/config/parser" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 26576299469..00000000000 --- a/extensions/auth-machine/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/auth-machine/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 5ec422e3ee3..00000000000 --- a/extensions/auth-machine/pkg/command/server.go +++ /dev/null @@ -1,108 +0,0 @@ -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/v2/extensions/auth-machine/pkg/config" - "github.com/owncloud/ocis/v2/extensions/auth-machine/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/auth-machine/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/auth-machine/pkg/revaconfig" - "github.com/owncloud/ocis/v2/extensions/auth-machine/pkg/server/debug" - "github.com/owncloud/ocis/v2/extensions/auth-machine/pkg/tracing" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" - "github.com/owncloud/ocis/v2/ocis-pkg/sync" - "github.com/owncloud/ocis/v2/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) - os.Exit(1) - } - 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.GetString(), - 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 deleted file mode 100644 index a687bbde999..00000000000 --- a/extensions/auth-machine/pkg/command/version.go +++ /dev/null @@ -1,50 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - tw "github.com/olekukonko/tablewriter" - "github.com/owncloud/ocis/v2/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.GetString()) - 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/defaults/defaultconfig.go b/extensions/auth-machine/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index b472bba1522..00000000000 --- a/extensions/auth-machine/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,83 +0,0 @@ -package defaults - -import ( - "github.com/owncloud/ocis/v2/extensions/auth-machine/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:9167", - Token: "", - Pprof: false, - Zpages: false, - }, - GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9166", - Namespace: "com.owncloud.api", - Protocol: "tcp", - }, - Service: config.Service{ - Name: "auth-machine", - }, - Reva: &config.Reva{ - Address: "127.0.0.1:9142", - }, - } -} - -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{} - } - - if cfg.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" { - cfg.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey - } -} - -func Sanitize(cfg *config.Config) { - // nothing to sanitize here atm -} diff --git a/extensions/auth-machine/pkg/config/parser/parse.go b/extensions/auth-machine/pkg/config/parser/parse.go deleted file mode 100644 index e5493ffc570..00000000000 --- a/extensions/auth-machine/pkg/config/parser/parse.go +++ /dev/null @@ -1,45 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/auth-machine/pkg/config" - "github.com/owncloud/ocis/v2/extensions/auth-machine/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - if cfg.TokenManager.JWTSecret == "" { - return shared.MissingJWTTokenError(cfg.Service.Name) - } - - 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 deleted file mode 100644 index 84c3be9c634..00000000000 --- a/extensions/auth-machine/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/auth-machine/pkg/config" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 1c9acbacf00..00000000000 --- a/extensions/auth-machine/pkg/revaconfig/config.go +++ /dev/null @@ -1,37 +0,0 @@ -package revaconfig - -import ( - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 057a52990d1..00000000000 --- a/extensions/auth-machine/pkg/server/debug/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/auth-machine/pkg/config" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 7812ed6dee6..00000000000 --- a/extensions/auth-machine/pkg/server/debug/server.go +++ /dev/null @@ -1,63 +0,0 @@ -package debug - -import ( - "io" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/auth-machine/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/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.GetString()), - 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 deleted file mode 100644 index 49a7a96af68..00000000000 --- a/extensions/auth-machine/pkg/tracing/tracing.go +++ /dev/null @@ -1,18 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/v2/extensions/auth-machine/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/owncloud/ocis/v2/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/cmd/frontend/main.go b/extensions/frontend/cmd/frontend/main.go deleted file mode 100644 index 9fdc691924c..00000000000 --- a/extensions/frontend/cmd/frontend/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/frontend/pkg/command" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index ac1e02c8881..00000000000 --- a/extensions/frontend/pkg/command/health.go +++ /dev/null @@ -1,57 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/frontend/pkg/config" - "github.com/owncloud/ocis/v2/extensions/frontend/pkg/config/parser" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index c5eca20b607..00000000000 --- a/extensions/frontend/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/frontend/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 90d395ac474..00000000000 --- a/extensions/frontend/pkg/command/server.go +++ /dev/null @@ -1,108 +0,0 @@ -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/v2/extensions/frontend/pkg/config" - "github.com/owncloud/ocis/v2/extensions/frontend/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/frontend/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/frontend/pkg/revaconfig" - "github.com/owncloud/ocis/v2/extensions/frontend/pkg/server/debug" - "github.com/owncloud/ocis/v2/extensions/frontend/pkg/tracing" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" - "github.com/owncloud/ocis/v2/ocis-pkg/sync" - "github.com/owncloud/ocis/v2/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) - os.Exit(1) - } - 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.GetString(), - 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 deleted file mode 100644 index 33e305f4be7..00000000000 --- a/extensions/frontend/pkg/command/version.go +++ /dev/null @@ -1,50 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - tw "github.com/olekukonko/tablewriter" - "github.com/owncloud/ocis/v2/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.GetString()) - 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/defaults/defaultconfig.go b/extensions/frontend/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index 3dea23c38a2..00000000000 --- a/extensions/frontend/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,125 +0,0 @@ -package defaults - -import ( - "github.com/owncloud/ocis/v2/extensions/frontend/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:9141", - Token: "", - Pprof: false, - Zpages: false, - }, - HTTP: config.HTTPConfig{ - Addr: "127.0.0.1:9140", - Namespace: "com.owncloud.web", - Protocol: "tcp", - Prefix: "", - }, - Service: config.Service{ - Name: "frontend", - }, - Reva: &config.Reva{ - Address: "127.0.0.1:9142", - }, - PublicURL: "https://localhost:9200", - EnableFavorites: false, - EnableProjectSpaces: true, - EnableShareJail: true, - UploadMaxChunkSize: 1e+8, - UploadHTTPMethodOverride: "", - DefaultUploadProtocol: "tus", - EnableResharing: false, - Checksums: config.Checksums{ - SupportedTypes: []string{"sha1", "md5", "adler32"}, - PreferredUploadType: "", - }, - AppHandler: config.AppHandler{ - Prefix: "app", - }, - Archiver: config.Archiver{ - Insecure: false, - Prefix: "archiver", - MaxNumFiles: 10000, - MaxSize: 1073741824, - }, - DataGateway: config.DataGateway{ - Prefix: "data", - }, - OCS: config.OCS{ - Prefix: "ocs", - SharePrefix: "/Shares", - HomeNamespace: "/users/{{.Id.OpaqueId}}", - AdditionalInfoAttribute: "{{.Mail}}", - ResourceInfoCacheTTL: 0, - }, - Middleware: config.Middleware{ - Auth: config.Auth{ - CredentialsByUserAgent: map[string]string{}, - }, - }, - } -} - -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{} - } - - if cfg.TransferSecret == "" && cfg.Commons != nil && cfg.Commons.TransferSecret != "" { - cfg.TransferSecret = cfg.Commons.TransferSecret - } - - if cfg.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" { - cfg.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey - } - -} - -func Sanitize(cfg *config.Config) { - // nothing to sanitize here atm -} diff --git a/extensions/frontend/pkg/config/parser/parse.go b/extensions/frontend/pkg/config/parser/parse.go deleted file mode 100644 index 6577e7b2306..00000000000 --- a/extensions/frontend/pkg/config/parser/parse.go +++ /dev/null @@ -1,50 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/frontend/pkg/config" - "github.com/owncloud/ocis/v2/extensions/frontend/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -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) - } - - if cfg.MachineAuthAPIKey == "" { - return shared.MissingMachineAuthApiKeyError(cfg.Service.Name) - } - - return nil -} diff --git a/extensions/frontend/pkg/logging/logging.go b/extensions/frontend/pkg/logging/logging.go deleted file mode 100644 index 20dc29549f4..00000000000 --- a/extensions/frontend/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/frontend/pkg/config" - "github.com/owncloud/ocis/v2/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/revaconfig/config.go b/extensions/frontend/pkg/revaconfig/config.go deleted file mode 100644 index aa0ad164305..00000000000 --- a/extensions/frontend/pkg/revaconfig/config.go +++ /dev/null @@ -1,230 +0,0 @@ -package revaconfig - -import ( - "path" - "strconv" - - "github.com/owncloud/ocis/v2/extensions/frontend/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/version" -) - -// 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), - }, - } - - 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": cfg.Service.Name, - }, - "shared": map[string]interface{}{ - "jwt_secret": cfg.TokenManager.JWTSecret, - "gatewaysvc": cfg.Reva.Address, // Todo or address? - "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, - }, - "http": map[string]interface{}{ - "network": cfg.HTTP.Protocol, - "address": cfg.HTTP.Addr, - "middlewares": map[string]interface{}{ - "cors": map[string]interface{}{ - "allow_credentials": true, - }, - "auth": map[string]interface{}{ - "credentials_by_user_agent": cfg.Middleware.Auth.CredentialsByUserAgent, - "credential_chain": []string{"bearer"}, - }, - }, - // TODO build services dynamically - "services": map[string]interface{}{ - // this reva service called "appprovider" comes from - // `internal/http/services/appprovider` and is a translation - // layer from the grpc app registry to http, used by eg. ownCloud Web - // It should not be confused with `internal/grpc/services/appprovider` - // which is currently only has only the driver for the CS3org WOPI server - "appprovider": map[string]interface{}{ - "prefix": cfg.AppHandler.Prefix, - "transfer_shared_secret": cfg.TransferSecret, - "timeout": 86400, - "insecure": cfg.AppHandler.Insecure, - }, - "archiver": map[string]interface{}{ - "prefix": cfg.Archiver.Prefix, - "timeout": 86400, - "insecure": cfg.Archiver.Insecure, - "max_num_files": cfg.Archiver.MaxNumFiles, - "max_size": cfg.Archiver.MaxSize, - }, - "datagateway": map[string]interface{}{ - "prefix": cfg.DataGateway.Prefix, - "transfer_shared_secret": cfg.TransferSecret, - "timeout": 86400, - "insecure": true, - }, - "ocs": map[string]interface{}{ - "storage_registry_svc": cfg.Reva.Address, - "share_prefix": cfg.OCS.SharePrefix, - "home_namespace": cfg.OCS.HomeNamespace, - "resource_info_cache_ttl": cfg.OCS.ResourceInfoCacheTTL, - "prefix": cfg.OCS.Prefix, - "additional_info_attribute": cfg.OCS.AdditionalInfoAttribute, - "machine_auth_apikey": cfg.MachineAuthAPIKey, - "cache_warmup_driver": cfg.OCS.CacheWarmupDriver, - "cache_warmup_drivers": map[string]interface{}{ - "cbox": map[string]interface{}{ - "db_username": cfg.OCS.CacheWarmupDrivers.CBOX.DBUsername, - "db_password": cfg.OCS.CacheWarmupDrivers.CBOX.DBPassword, - "db_host": cfg.OCS.CacheWarmupDrivers.CBOX.DBHost, - "db_port": cfg.OCS.CacheWarmupDrivers.CBOX.DBPort, - "db_name": cfg.OCS.CacheWarmupDrivers.CBOX.DBName, - "namespace": cfg.OCS.CacheWarmupDrivers.CBOX.Namespace, - "gatewaysvc": cfg.Reva.Address, - }, - }, - "config": map[string]interface{}{ - "version": "1.7", - "website": "ownCloud", - "host": cfg.PublicURL, - "contact": "", - "ssl": "false", - }, - "default_upload_protocol": cfg.DefaultUploadProtocol, - "capabilities": map[string]interface{}{ - "capabilities": map[string]interface{}{ - "core": map[string]interface{}{ - "poll_interval": 60, - "webdav_root": "remote.php/webdav", - "status": map[string]interface{}{ - "installed": true, - "maintenance": false, - "needsDbUpgrade": false, - "version": version.Legacy, - "versionstring": version.LegacyString, - "edition": "Community", - "productname": "Infinite Scale", - "product": "Infinite Scale", - "productversion": version.GetString(), - "hostname": "", - }, - "support_url_signing": true, - }, - "checksums": map[string]interface{}{ - "supported_types": cfg.Checksums.SupportedTypes, - "preferred_upload_type": cfg.Checksums.PreferredUploadType, - }, - "files": filesCfg, - "dav": map[string]interface{}{ - "reports": []string{"search-files"}, - }, - "files_sharing": map[string]interface{}{ - "api_enabled": true, - "resharing": cfg.EnableResharing, - "group_sharing": true, - "auto_accept_share": true, - "share_with_group_members_only": true, - "share_with_membership_groups_only": true, - "default_permissions": 22, - "search_min_length": 3, - "public": map[string]interface{}{ - "enabled": true, - "send_mail": true, - "defaultPublicLinkShareName": "Public link", - "social_share": true, - "upload": true, - "multiple": true, - "supports_upload_only": true, - "password": map[string]interface{}{ - "enforced": false, - "enforced_for": map[string]interface{}{ - "read_only": false, - "read_write": false, - "upload_only": false, - }, - }, - "expire_date": map[string]interface{}{ - "enabled": true, - }, - "can_edit": true, - }, - "user": map[string]interface{}{ - "send_mail": true, - "profile_picture": false, - "settings": []map[string]interface{}{ - { - "enabled": true, - "version": "1.0.0", - }, - }, - }, - "user_enumeration": map[string]interface{}{ - "enabled": true, - "group_members_only": true, - }, - "federation": map[string]interface{}{ - "outgoing": true, - "incoming": true, - }, - }, - "spaces": map[string]interface{}{ - "version": "0.0.1", - "enabled": cfg.EnableProjectSpaces || cfg.EnableShareJail, - "projects": cfg.EnableProjectSpaces, - "share_jail": cfg.EnableShareJail, - }, - }, - "version": map[string]interface{}{ - "product": "Infinite Scale", - "edition": "Community", - "major": version.ParsedLegacy().Major(), - "minor": version.ParsedLegacy().Minor(), - "micro": version.ParsedLegacy().Patch(), - "string": version.LegacyString, - "productversion": version.GetString(), - }, - }, - }, - }, - }, - } -} diff --git a/extensions/frontend/pkg/server/debug/option.go b/extensions/frontend/pkg/server/debug/option.go deleted file mode 100644 index b4f5ec064d5..00000000000 --- a/extensions/frontend/pkg/server/debug/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/frontend/pkg/config" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 7da91c51033..00000000000 --- a/extensions/frontend/pkg/server/debug/server.go +++ /dev/null @@ -1,63 +0,0 @@ -package debug - -import ( - "io" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/frontend/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/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.GetString()), - 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 deleted file mode 100644 index b55b70c5601..00000000000 --- a/extensions/frontend/pkg/tracing/tracing.go +++ /dev/null @@ -1,18 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/v2/extensions/frontend/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/owncloud/ocis/v2/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/cmd/gateway/main.go b/extensions/gateway/cmd/gateway/main.go deleted file mode 100644 index 570a3c41a36..00000000000 --- a/extensions/gateway/cmd/gateway/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/gateway/pkg/command" - "github.com/owncloud/ocis/v2/extensions/gateway/pkg/config/defaults" -) - -func main() { - if err := command.Execute(defaults.DefaultConfig()); err != nil { - os.Exit(1) - } -} diff --git a/extensions/gateway/pkg/command/health.go b/extensions/gateway/pkg/command/health.go deleted file mode 100644 index 9ff7965c774..00000000000 --- a/extensions/gateway/pkg/command/health.go +++ /dev/null @@ -1,57 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/gateway/pkg/config" - "github.com/owncloud/ocis/v2/extensions/gateway/pkg/config/parser" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 058d6b4f062..00000000000 --- a/extensions/gateway/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/gateway/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 375f0a8f30a..00000000000 --- a/extensions/gateway/pkg/command/server.go +++ /dev/null @@ -1,103 +0,0 @@ -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/v2/extensions/gateway/pkg/config" - "github.com/owncloud/ocis/v2/extensions/gateway/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/gateway/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/gateway/pkg/revaconfig" - "github.com/owncloud/ocis/v2/extensions/gateway/pkg/server/debug" - "github.com/owncloud/ocis/v2/extensions/gateway/pkg/tracing" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" - "github.com/owncloud/ocis/v2/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) - os.Exit(1) - } - 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.GetString(), - 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 deleted file mode 100644 index 7fd1d3afcba..00000000000 --- a/extensions/gateway/pkg/command/version.go +++ /dev/null @@ -1,50 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - tw "github.com/olekukonko/tablewriter" - "github.com/owncloud/ocis/v2/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.GetString()) - 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/defaults/defaultconfig.go b/extensions/gateway/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index f7616174afe..00000000000 --- a/extensions/gateway/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,109 +0,0 @@ -package defaults - -import ( - "github.com/owncloud/ocis/v2/extensions/gateway/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:9143", - Token: "", - Pprof: false, - Zpages: false, - }, - GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9142", - Namespace: "com.owncloud.api", - Protocol: "tcp", - }, - Service: config.Service{ - Name: "gateway", - }, - Reva: &config.Reva{ - Address: "127.0.0.1:9142", - }, - - CommitShareToStorageGrant: true, - CommitShareToStorageRef: true, - ShareFolder: "Shares", - DisableHomeCreationOnLogin: true, - TransferExpires: 24 * 60 * 60, - EtagCacheTTL: 0, - - 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", - StorageSharesEndpoint: "localhost:9154", - StorageUsersEndpoint: "localhost:9157", - UsersEndpoint: "localhost:9144", - - StorageRegistry: config.StorageRegistry{ - Driver: "spaces", - JSON: "", - }, - } -} - -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{} - } - - if cfg.TransferSecret == "" && cfg.Commons != nil && cfg.Commons.TransferSecret != "" { - cfg.TransferSecret = cfg.Commons.TransferSecret - } -} - -func Sanitize(cfg *config.Config) { - // nothing to sanitize here atm -} diff --git a/extensions/gateway/pkg/config/parser/parse.go b/extensions/gateway/pkg/config/parser/parse.go deleted file mode 100644 index 62146646b34..00000000000 --- a/extensions/gateway/pkg/config/parser/parse.go +++ /dev/null @@ -1,46 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/gateway/pkg/config" - "github.com/owncloud/ocis/v2/extensions/gateway/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -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/gateway/pkg/logging/logging.go b/extensions/gateway/pkg/logging/logging.go deleted file mode 100644 index 6e80a90017c..00000000000 --- a/extensions/gateway/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/gateway/pkg/config" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index ad93f3eaeaa..00000000000 --- a/extensions/gateway/pkg/revaconfig/config.go +++ /dev/null @@ -1,160 +0,0 @@ -package revaconfig - -import ( - "encoding/json" - "io/ioutil" - "strings" - - "github.com/owncloud/ocis/v2/ocis-pkg/log" - - "github.com/cs3org/reva/v2/pkg/utils" - "github.com/owncloud/ocis/v2/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, - "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 deleted file mode 100644 index 34edb7f8250..00000000000 --- a/extensions/gateway/pkg/server/debug/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/gateway/pkg/config" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 0d3a8d24990..00000000000 --- a/extensions/gateway/pkg/server/debug/server.go +++ /dev/null @@ -1,63 +0,0 @@ -package debug - -import ( - "io" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/gateway/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/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.GetString()), - 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 deleted file mode 100644 index 41f6df08f16..00000000000 --- a/extensions/gateway/pkg/tracing/tracing.go +++ /dev/null @@ -1,18 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/v2/extensions/gateway/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/owncloud/ocis/v2/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/graph-explorer/cmd/graph-explorer/main.go b/extensions/graph-explorer/cmd/graph-explorer/main.go deleted file mode 100644 index dc1c83a80f7..00000000000 --- a/extensions/graph-explorer/cmd/graph-explorer/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/command" - "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/config/defaults" -) - -func main() { - if err := command.Execute(defaults.DefaultConfig()); err != nil { - os.Exit(1) - } -} diff --git a/extensions/graph-explorer/pkg/assets/option.go b/extensions/graph-explorer/pkg/assets/option.go deleted file mode 100644 index d0d5e115503..00000000000 --- a/extensions/graph-explorer/pkg/assets/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package assets - -import ( - "net/http" - - graphexplorer "github.com/owncloud/ocis/v2/extensions/graph-explorer" - "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/assetsfs" - "github.com/owncloud/ocis/v2/ocis-pkg/log" -) - -// New returns a new http filesystem to serve assets. -func New(opts ...Option) http.FileSystem { - options := newOptions(opts...) - return assetsfs.New(graphexplorer.Assets, "", options.Logger) -} - -// 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 - 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 - } -} - -// 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/graph-explorer/pkg/command/health.go b/extensions/graph-explorer/pkg/command/health.go deleted file mode 100644 index da1feefd43c..00000000000 --- a/extensions/graph-explorer/pkg/command/health.go +++ /dev/null @@ -1,57 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/config" - "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/graph-explorer/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/graph-explorer/pkg/command/root.go b/extensions/graph-explorer/pkg/command/root.go deleted file mode 100644 index ce7e1dbafaf..00000000000 --- a/extensions/graph-explorer/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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 graph-explorer command. -func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "graph-explorer", - Usage: "Serve Graph-Explorer 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 graph-explorer command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new graph-explorer.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.GraphExplorer.Commons = cfg.Commons - return SutureService{ - cfg: cfg.GraphExplorer, - } -} - -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/graph-explorer/pkg/command/server.go b/extensions/graph-explorer/pkg/command/server.go deleted file mode 100644 index f70d840f6d9..00000000000 --- a/extensions/graph-explorer/pkg/command/server.go +++ /dev/null @@ -1,108 +0,0 @@ -package command - -import ( - "context" - "fmt" - "os" - - "github.com/oklog/run" - "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/config" - "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/metrics" - "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/server/debug" - "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/server/http" - "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/tracing" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - "github.com/urfave/cli/v2" -) - -// Server is the entrypoint 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(ctx *cli.Context) error { - err := parser.ParseConfig(cfg) - if err != nil { - fmt.Printf("%v", err) - os.Exit(1) - } - return err - }, - Action: func(c *cli.Context) error { - logger := logging.Configure(cfg.Service.Name, cfg.Log) - err := tracing.Configure(cfg) - if err != nil { - return err - } - var ( - gr = run.Group{} - ctx, cancel = func() (context.Context, context.CancelFunc) { - if cfg.Context == nil { - return context.WithCancel(context.Background()) - } - return context.WithCancel(cfg.Context) - }() - mtrcs = metrics.New() - ) - - defer cancel() - - mtrcs.BuildInfo.WithLabelValues(version.GetString()).Set(1) - - { - server, err := http.Server( - http.Logger(logger), - http.Context(ctx), - http.Namespace(cfg.HTTP.Namespace), - http.Config(cfg), - http.Metrics(mtrcs), - ) - - if err != nil { - logger.Info().Err(err).Str("transport", "http").Msg("Failed to initialize server") - return err - } - - gr.Add(func() error { - err := server.Run() - if err != nil { - logger.Error(). - Err(err). - Str("transport", "http"). - Msg("Failed to start server") - } - return err - }, func(_ error) { - logger.Info(). - Str("transport", "http"). - Msg("Shutting down server") - - cancel() - }) - } - - { - server, err := debug.Server( - debug.Logger(logger), - debug.Context(ctx), - debug.Config(cfg), - ) - - if err != nil { - logger.Info().Err(err).Str("transport", "debug").Msg("Failed to initialize server") - return err - } - - gr.Add(server.ListenAndServe, func(_ error) { - _ = server.Shutdown(ctx) - cancel() - }) - } - - return gr.Run() - }, - } -} diff --git a/extensions/graph-explorer/pkg/command/version.go b/extensions/graph-explorer/pkg/command/version.go deleted file mode 100644 index 61021010b7f..00000000000 --- a/extensions/graph-explorer/pkg/command/version.go +++ /dev/null @@ -1,50 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - tw "github.com/olekukonko/tablewriter" - "github.com/owncloud/ocis/v2/extensions/graph-explorer/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.GetString()) - 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/graph-explorer/pkg/config/defaults/defaultconfig.go b/extensions/graph-explorer/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index 1e2d77b1677..00000000000 --- a/extensions/graph-explorer/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,71 +0,0 @@ -package defaults - -import ( - "strings" - - "github.com/owncloud/ocis/v2/extensions/graph-explorer/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:9136", - Token: "", - Pprof: false, - Zpages: false, - }, - HTTP: config.HTTP{ - Addr: "127.0.0.1:9135", - Root: "/graph-explorer", - Namespace: "com.owncloud.web", - }, - Service: config.Service{ - Name: "graph-explorer", - }, - GraphExplorer: config.GraphExplorer{ - ClientID: "ocis-explorer.js", - Issuer: "https://localhost:9200", - GraphURLBase: "https://localhost:9200", - GraphURLPath: "/graph", - }, - } -} - -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{} - } -} - -func Sanitize(cfg *config.Config) { - // sanitize config - if cfg.HTTP.Root == "/" { - cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/graph-explorer") - } -} diff --git a/extensions/graph-explorer/pkg/config/parser/parse.go b/extensions/graph-explorer/pkg/config/parser/parse.go deleted file mode 100644 index d6f355a2ffe..00000000000 --- a/extensions/graph-explorer/pkg/config/parser/parse.go +++ /dev/null @@ -1,38 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/config" - "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - // sanitize config - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - return nil -} diff --git a/extensions/graph-explorer/pkg/logging/logging.go b/extensions/graph-explorer/pkg/logging/logging.go deleted file mode 100644 index bf2db03f50a..00000000000 --- a/extensions/graph-explorer/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/config" - "github.com/owncloud/ocis/v2/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/graph-explorer/pkg/server/debug/option.go b/extensions/graph-explorer/pkg/server/debug/option.go deleted file mode 100644 index f18c953b987..00000000000 --- a/extensions/graph-explorer/pkg/server/debug/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/config" - "github.com/owncloud/ocis/v2/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/graph-explorer/pkg/server/debug/server.go b/extensions/graph-explorer/pkg/server/debug/server.go deleted file mode 100644 index 00cc7bd14dd..00000000000 --- a/extensions/graph-explorer/pkg/server/debug/server.go +++ /dev/null @@ -1,59 +0,0 @@ -package debug - -import ( - "io" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/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.GetString()), - 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)), - ), 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/graph-explorer/pkg/server/http/option.go b/extensions/graph-explorer/pkg/server/http/option.go deleted file mode 100644 index 3edd0957825..00000000000 --- a/extensions/graph-explorer/pkg/server/http/option.go +++ /dev/null @@ -1,76 +0,0 @@ -package http - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/config" - "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/metrics" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/urfave/cli/v2" -) - -// 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 - Metrics *metrics.Metrics - Flags []cli.Flag - Namespace string -} - -// 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 - } -} - -// Metrics provides a function to set the metrics option. -func Metrics(val *metrics.Metrics) Option { - return func(o *Options) { - o.Metrics = val - } -} - -// Flags provides a function to set the flags option. -func Flags(val []cli.Flag) Option { - return func(o *Options) { - o.Flags = append(o.Flags, val...) - } -} - -// Namespace provides a function to set the Namespace option. -func Namespace(val string) Option { - return func(o *Options) { - o.Namespace = val - } -} diff --git a/extensions/graph-explorer/pkg/server/http/server.go b/extensions/graph-explorer/pkg/server/http/server.go deleted file mode 100644 index 865bf7b101e..00000000000 --- a/extensions/graph-explorer/pkg/server/http/server.go +++ /dev/null @@ -1,55 +0,0 @@ -package http - -import ( - chimiddleware "github.com/go-chi/chi/v5/middleware" - svc "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/service/v0" - "github.com/owncloud/ocis/v2/ocis-pkg/middleware" - "github.com/owncloud/ocis/v2/ocis-pkg/service/http" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - "go-micro.dev/v4" -) - -// Server initializes the http service and server. -func Server(opts ...Option) (http.Service, error) { - options := newOptions(opts...) - - service := http.NewService( - http.Logger(options.Logger), - http.Namespace(options.Namespace), - http.Name("graph-explorer"), - http.Version(version.GetString()), - http.Address(options.Config.HTTP.Addr), - http.Context(options.Context), - http.Flags(options.Flags...), - ) - - handle := svc.NewService( - svc.Logger(options.Logger), - svc.Config(options.Config), - svc.Middleware( - chimiddleware.RealIP, - chimiddleware.RequestID, - middleware.NoCache, - middleware.Secure, - middleware.Version( - "graph-explorer", - version.GetString(), - ), - middleware.Logger( - options.Logger, - ), - ), - ) - - { - handle = svc.NewInstrument(handle, options.Metrics) - handle = svc.NewLogging(handle, options.Logger) - handle = svc.NewTracing(handle) - } - - if err := micro.RegisterHandler(service.Server(), handle); err != nil { - return http.Service{}, err - } - - return service, nil -} diff --git a/extensions/graph-explorer/pkg/service/v0/instrument.go b/extensions/graph-explorer/pkg/service/v0/instrument.go deleted file mode 100644 index 82bf24f2be9..00000000000 --- a/extensions/graph-explorer/pkg/service/v0/instrument.go +++ /dev/null @@ -1,30 +0,0 @@ -package svc - -import ( - "net/http" - - "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/metrics" -) - -// NewInstrument returns a service that instruments metrics. -func NewInstrument(next Service, metrics *metrics.Metrics) Service { - return instrument{ - next: next, - metrics: metrics, - } -} - -type instrument struct { - next Service - metrics *metrics.Metrics -} - -// ServeHTTP implements the Service interface. -func (i instrument) ServeHTTP(w http.ResponseWriter, r *http.Request) { - i.next.ServeHTTP(w, r) -} - -// ConfigJs implements the Service interface. -func (i instrument) ConfigJs(w http.ResponseWriter, r *http.Request) { - i.next.ConfigJs(w, r) -} diff --git a/extensions/graph-explorer/pkg/service/v0/option.go b/extensions/graph-explorer/pkg/service/v0/option.go deleted file mode 100644 index ea1eb949f9e..00000000000 --- a/extensions/graph-explorer/pkg/service/v0/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package svc - -import ( - "net/http" - - "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/config" - "github.com/owncloud/ocis/v2/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 - Config *config.Config - Middleware []func(http.Handler) http.Handler -} - -// 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 - } -} - -// Config provides a function to set the config option. -func Config(val *config.Config) Option { - return func(o *Options) { - o.Config = val - } -} - -// Middleware provides a function to set the middleware option. -func Middleware(val ...func(http.Handler) http.Handler) Option { - return func(o *Options) { - o.Middleware = val - } -} diff --git a/extensions/graph-explorer/pkg/service/v0/service.go b/extensions/graph-explorer/pkg/service/v0/service.go deleted file mode 100644 index 3875aa90b67..00000000000 --- a/extensions/graph-explorer/pkg/service/v0/service.go +++ /dev/null @@ -1,110 +0,0 @@ -package svc - -import ( - "fmt" - "io" - "net/http" - "strings" - - "github.com/go-chi/chi/v5" - "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/assets" - "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/log" -) - -// Service defines the extension handlers. -type Service interface { - ServeHTTP(http.ResponseWriter, *http.Request) - ConfigJs(http.ResponseWriter, *http.Request) -} - -// NewService returns a service implementation for Service. -func NewService(opts ...Option) Service { - options := newOptions(opts...) - - m := chi.NewMux() - m.Use(options.Middleware...) - - svc := GraphExplorer{ - logger: options.Logger, - config: options.Config, - mux: m, - } - - m.Route(options.Config.HTTP.Root, func(r chi.Router) { - r.Get("/config.js", svc.ConfigJs) - r.Mount("/", svc.Static()) - }) - - return svc -} - -// GraphExplorer defines implements the business logic for Service. -type GraphExplorer struct { - logger log.Logger - config *config.Config - mux *chi.Mux -} - -// ServeHTTP implements the Service interface. -func (p GraphExplorer) ServeHTTP(w http.ResponseWriter, r *http.Request) { - p.mux.ServeHTTP(w, r) -} - -// ConfigJs implements the Service interface. -func (p GraphExplorer) ConfigJs(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/javascript") - w.WriteHeader(http.StatusOK) - if _, err := io.WriteString(w, fmt.Sprintf("window.ClientId = \"%v\";", p.config.GraphExplorer.ClientID)); err != nil { - p.logger.Error().Err(err).Msg("Could not write to response writer") - } - if _, err := io.WriteString(w, fmt.Sprintf("window.Iss = \"%v\";", p.config.GraphExplorer.Issuer)); err != nil { - p.logger.Error().Err(err).Msg("Could not write to response writer") - } - if _, err := io.WriteString(w, fmt.Sprintf("window.GraphUrl = \"%v\";", p.config.GraphExplorer.GraphURLBase+p.config.GraphExplorer.GraphURLPath)); err != nil { - p.logger.Error().Err(err).Msg("Could not write to response writer") - } -} - -// Static simply serves all static files. -func (p GraphExplorer) Static() http.HandlerFunc { - rootWithSlash := p.config.HTTP.Root - - if !strings.HasSuffix(rootWithSlash, "/") { - rootWithSlash = rootWithSlash + "/" - } - - static := http.StripPrefix( - rootWithSlash, - http.FileServer( - assets.New( - assets.Logger(p.logger), - assets.Config(p.config), - ), - ), - ) - - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if rootWithSlash != "/" && r.URL.Path == p.config.HTTP.Root { - http.Redirect( - w, - r, - rootWithSlash, - http.StatusMovedPermanently, - ) - - return - } - - if r.URL.Path != rootWithSlash && strings.HasSuffix(r.URL.Path, "/") { - http.NotFound( - w, - r, - ) - - return - } - - static.ServeHTTP(w, r) - }) -} diff --git a/extensions/graph-explorer/pkg/tracing/tracing.go b/extensions/graph-explorer/pkg/tracing/tracing.go deleted file mode 100644 index 12c3f163869..00000000000 --- a/extensions/graph-explorer/pkg/tracing/tracing.go +++ /dev/null @@ -1,23 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/config" - pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" - "go.opentelemetry.io/otel/trace" -) - -var ( - // TraceProvider is the global trace provider for the proxy service. - TraceProvider = trace.NewNoopTracerProvider() -) - -func Configure(cfg *config.Config) error { - var err error - if cfg.Tracing.Enabled { - if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { - return err - } - } - - return nil -} diff --git a/extensions/graph/cmd/graph/main.go b/extensions/graph/cmd/graph/main.go deleted file mode 100644 index d11a6ed6e96..00000000000 --- a/extensions/graph/cmd/graph/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/graph/pkg/command" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/config/defaults" -) - -func main() { - if err := command.Execute(defaults.DefaultConfig()); err != nil { - os.Exit(1) - } -} diff --git a/extensions/graph/pkg/command/health.go b/extensions/graph/pkg/command/health.go deleted file mode 100644 index d6bdf3924e5..00000000000 --- a/extensions/graph/pkg/command/health.go +++ /dev/null @@ -1,57 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/graph/pkg/config" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/graph/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/graph/pkg/command/root.go b/extensions/graph/pkg/command/root.go deleted file mode 100644 index d1132fbeff3..00000000000 --- a/extensions/graph/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - "github.com/thejerf/suture/v4" - - "github.com/owncloud/ocis/v2/extensions/graph/pkg/config" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "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-graph command. -func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "graph", - Usage: "Serve Graph 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 graph command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new graph.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Graph.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Graph, - } -} - -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/graph/pkg/command/server.go b/extensions/graph/pkg/command/server.go deleted file mode 100644 index d8d46cc6a77..00000000000 --- a/extensions/graph/pkg/command/server.go +++ /dev/null @@ -1,99 +0,0 @@ -package command - -import ( - "context" - "fmt" - "os" - - "github.com/oklog/run" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/config" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/metrics" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/server/debug" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/server/http" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/tracing" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - "github.com/urfave/cli/v2" -) - -// Server is the entrypoint 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) - os.Exit(1) - } - return err - }, - Action: func(c *cli.Context) error { - logger := logging.Configure(cfg.Service.Name, cfg.Log) - err := tracing.Configure(cfg) - if err != nil { - return err - } - - gr := run.Group{} - ctx, cancel := func() (context.Context, context.CancelFunc) { - if cfg.Context == nil { - return context.WithCancel(context.Background()) - } - return context.WithCancel(cfg.Context) - }() - mtrcs := metrics.New() - - defer cancel() - - mtrcs.BuildInfo.WithLabelValues(version.GetString()).Set(1) - - { - server, err := http.Server( - http.Logger(logger), - http.Context(ctx), - http.Config(cfg), - http.Metrics(mtrcs), - ) - - if err != nil { - logger.Info().Err(err).Str("transport", "http").Msg("Failed to initialize server") - return err - } - - gr.Add(func() error { - return server.Run() - }, func(_ error) { - logger.Info(). - Str("transport", "http"). - Msg("Shutting down server") - - cancel() - }) - } - - { - server, err := debug.Server( - debug.Logger(logger), - debug.Context(ctx), - debug.Config(cfg), - ) - - if err != nil { - logger.Info().Err(err).Str("transport", "debug").Msg("Failed to initialize server") - return err - } - - gr.Add(server.ListenAndServe, func(_ error) { - _ = server.Shutdown(ctx) - cancel() - }) - } - - return gr.Run() - }, - } -} diff --git a/extensions/graph/pkg/command/version.go b/extensions/graph/pkg/command/version.go deleted file mode 100644 index b36862b14df..00000000000 --- a/extensions/graph/pkg/command/version.go +++ /dev/null @@ -1,50 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - tw "github.com/olekukonko/tablewriter" - "github.com/owncloud/ocis/v2/extensions/graph/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.GetString()) - 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/graph/pkg/config/defaults/defaultconfig.go b/extensions/graph/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index a9c59d81c40..00000000000 --- a/extensions/graph/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,113 +0,0 @@ -package defaults - -import ( - "path" - "strings" - - "github.com/owncloud/ocis/v2/extensions/graph/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" -) - -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:9124", - Token: "", - }, - HTTP: config.HTTP{ - Addr: "127.0.0.1:9120", - Namespace: "com.owncloud.graph", - Root: "/graph", - }, - Service: config.Service{ - Name: "graph", - }, - Reva: &config.Reva{ - Address: "127.0.0.1:9142", - }, - Spaces: config.Spaces{ - WebDavBase: "https://localhost:9200", - WebDavPath: "/dav/spaces/", - DefaultQuota: "1000000000", - Insecure: false, - }, - Identity: config.Identity{ - Backend: "ldap", - LDAP: config.LDAP{ - URI: "ldaps://localhost:9235", - Insecure: false, - CACert: path.Join(defaults.BaseDataPath(), "idm", "ldap.crt"), - BindDN: "uid=libregraph,ou=sysusers,o=libregraph-idm", - UseServerUUID: false, - WriteEnabled: true, - UserBaseDN: "ou=users,o=libregraph-idm", - UserSearchScope: "sub", - UserFilter: "", - UserObjectClass: "inetOrgPerson", - UserEmailAttribute: "mail", - UserDisplayNameAttribute: "displayName", - UserNameAttribute: "uid", - // FIXME: switch this to some more widely available attribute by default - // ideally this needs to be constant for the lifetime of a users - UserIDAttribute: "owncloudUUID", - GroupBaseDN: "ou=groups,o=libregraph-idm", - GroupSearchScope: "sub", - GroupFilter: "", - GroupObjectClass: "groupOfNames", - GroupNameAttribute: "cn", - GroupIDAttribute: "owncloudUUID", - }, - }, - Events: config.Events{ - Endpoint: "127.0.0.1:9233", - Cluster: "ocis-cluster", - }, - } -} - -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.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) { - // sanitize config - if cfg.HTTP.Root != "/" { - cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") - } -} diff --git a/extensions/graph/pkg/config/parser/parse.go b/extensions/graph/pkg/config/parser/parse.go deleted file mode 100644 index 145c5289448..00000000000 --- a/extensions/graph/pkg/config/parser/parse.go +++ /dev/null @@ -1,46 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/graph/pkg/config" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - if cfg.TokenManager.JWTSecret == "" { - return shared.MissingJWTTokenError(cfg.Service.Name) - } - - if cfg.Identity.Backend == "ldap" && cfg.Identity.LDAP.BindPassword == "" { - return shared.MissingLDAPBindPassword(cfg.Service.Name) - } - - return nil -} diff --git a/extensions/graph/pkg/identity/cs3.go b/extensions/graph/pkg/identity/cs3.go deleted file mode 100644 index 8c2dd73c61a..00000000000 --- a/extensions/graph/pkg/identity/cs3.go +++ /dev/null @@ -1,209 +0,0 @@ -package identity - -import ( - "context" - "net/url" - - cs3group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" - cs3user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" - libregraph "github.com/owncloud/libre-graph-api-go" - - "github.com/owncloud/ocis/v2/extensions/graph/pkg/config" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/service/v0/errorcode" - "github.com/owncloud/ocis/v2/ocis-pkg/log" -) - -var ( - errNotImplemented = errorcode.New(errorcode.NotSupported, "not implemented") -) - -type CS3 struct { - Config *config.Reva - Logger *log.Logger -} - -// CreateUser implements the Backend Interface. It's currently not supported for the CS3 backend -func (i *CS3) CreateUser(ctx context.Context, user libregraph.User) (*libregraph.User, error) { - return nil, errNotImplemented -} - -// DeleteUser implements the Backend Interface. It's currently not supported for the CS3 backend -func (i *CS3) DeleteUser(ctx context.Context, nameOrID string) error { - return errNotImplemented -} - -// UpdateUser implements the Backend Interface. It's currently not suported for the CS3 backend -func (i *CS3) UpdateUser(ctx context.Context, nameOrID string, user libregraph.User) (*libregraph.User, error) { - return nil, errNotImplemented -} - -func (i *CS3) GetUser(ctx context.Context, userID string, queryParam url.Values) (*libregraph.User, error) { - client, err := pool.GetGatewayServiceClient(i.Config.Address) - if err != nil { - i.Logger.Error().Err(err).Msg("could not get client") - return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) - } - - res, err := client.GetUserByClaim(ctx, &cs3user.GetUserByClaimRequest{ - Claim: "userid", // FIXME add consts to reva - Value: userID, - }) - - switch { - case err != nil: - i.Logger.Error().Err(err).Str("userid", userID).Msg("error sending get user by claim id grpc request") - return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) - case res.Status.Code != cs3rpc.Code_CODE_OK: - if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND { - return nil, errorcode.New(errorcode.ItemNotFound, res.Status.Message) - } - i.Logger.Error().Err(err).Str("userid", userID).Msg("error sending get user by claim id grpc request") - return nil, errorcode.New(errorcode.GeneralException, res.Status.Message) - } - return CreateUserModelFromCS3(res.User), nil -} - -func (i *CS3) GetUsers(ctx context.Context, queryParam url.Values) ([]*libregraph.User, error) { - client, err := pool.GetGatewayServiceClient(i.Config.Address) - if err != nil { - i.Logger.Error().Err(err).Msg("could not get client") - return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) - } - - search := queryParam.Get("search") - if search == "" { - search = queryParam.Get("$search") - } - - res, err := client.FindUsers(ctx, &cs3user.FindUsersRequest{ - // FIXME presence match is currently not implemented, an empty search currently leads to - // Unwilling To Perform": Search Error: error parsing filter: (&(objectclass=posixAccount)(|(cn=*)(displayname=*)(mail=*))), error: Present filter match for cn not implemented - Filter: search, - }) - switch { - case err != nil: - i.Logger.Error().Err(err).Str("search", search).Msg("error sending find users grpc request") - return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) - case res.Status.Code != cs3rpc.Code_CODE_OK: - if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND { - return nil, errorcode.New(errorcode.ItemNotFound, res.Status.Message) - } - i.Logger.Error().Err(err).Str("search", search).Msg("error sending find users grpc request") - return nil, errorcode.New(errorcode.GeneralException, res.Status.Message) - } - - users := make([]*libregraph.User, 0, len(res.Users)) - - for _, user := range res.Users { - users = append(users, CreateUserModelFromCS3(user)) - } - - return users, nil -} - -func (i *CS3) GetGroups(ctx context.Context, queryParam url.Values) ([]*libregraph.Group, error) { - client, err := pool.GetGatewayServiceClient(i.Config.Address) - if err != nil { - i.Logger.Error().Err(err).Msg("could not get client") - return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) - } - - search := queryParam.Get("search") - if search == "" { - search = queryParam.Get("$search") - } - - res, err := client.FindGroups(ctx, &cs3group.FindGroupsRequest{ - // FIXME presence match is currently not implemented, an empty search currently leads to - // Unwilling To Perform": Search Error: error parsing filter: (&(objectclass=posixAccount)(|(cn=*)(displayname=*)(mail=*))), error: Present filter match for cn not implemented - Filter: search, - }) - - switch { - case err != nil: - i.Logger.Error().Err(err).Str("search", search).Msg("error sending find groups grpc request") - return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) - case res.Status.Code != cs3rpc.Code_CODE_OK: - if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND { - return nil, errorcode.New(errorcode.ItemNotFound, res.Status.Message) - } - i.Logger.Error().Err(err).Str("search", search).Msg("error sending find groups grpc request") - return nil, errorcode.New(errorcode.GeneralException, res.Status.Message) - } - - groups := make([]*libregraph.Group, 0, len(res.Groups)) - - for _, group := range res.Groups { - groups = append(groups, createGroupModelFromCS3(group)) - } - - return groups, nil -} - -// CreateGroup implements the Backend Interface. It's currently not supported for the CS3 backend -func (i *CS3) CreateGroup(ctx context.Context, group libregraph.Group) (*libregraph.Group, error) { - return nil, errorcode.New(errorcode.NotSupported, "not implemented") -} - -func (i *CS3) GetGroup(ctx context.Context, groupID string, queryParam url.Values) (*libregraph.Group, error) { - client, err := pool.GetGatewayServiceClient(i.Config.Address) - if err != nil { - i.Logger.Error().Err(err).Msg("could not get client") - return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) - } - - res, err := client.GetGroupByClaim(ctx, &cs3group.GetGroupByClaimRequest{ - Claim: "groupid", // FIXME add consts to reva - Value: groupID, - }) - - switch { - case err != nil: - i.Logger.Error().Err(err).Str("groupid", groupID).Msg("error sending get group by claim id grpc request") - return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) - case res.Status.Code != cs3rpc.Code_CODE_OK: - if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND { - return nil, errorcode.New(errorcode.ItemNotFound, res.Status.Message) - } - i.Logger.Error().Err(err).Str("groupid", groupID).Msg("error sending get group by claim id grpc request") - return nil, errorcode.New(errorcode.GeneralException, res.Status.Message) - } - - return createGroupModelFromCS3(res.Group), nil -} - -// DeleteGroup implements the Backend Interface. It's currently not supported for the CS3 backend -func (i *CS3) DeleteGroup(ctx context.Context, id string) error { - return errorcode.New(errorcode.NotSupported, "not implemented") -} - -// GetGroupMembers implements the Backend Interface. It's currently not supported for the CS3 backend -func (i *CS3) GetGroupMembers(ctx context.Context, groupID string) ([]*libregraph.User, error) { - return nil, errorcode.New(errorcode.NotSupported, "not implemented") -} - -// AddMembersToGroup implements the Backend Interface. It's currently not supported for the CS3 backend -func (i *CS3) AddMembersToGroup(ctx context.Context, groupID string, memberID []string) error { - return errorcode.New(errorcode.NotSupported, "not implemented") -} - -// RemoveMemberFromGroup implements the Backend Interface. It's currently not supported for the CS3 backend -func (i *CS3) RemoveMemberFromGroup(ctx context.Context, groupID string, memberID string) error { - return errorcode.New(errorcode.NotSupported, "not implemented") -} - -func createGroupModelFromCS3(g *cs3group.Group) *libregraph.Group { - if g.Id == nil { - g.Id = &cs3group.GroupId{} - } - return &libregraph.Group{ - Id: &g.Id.OpaqueId, - OnPremisesDomainName: &g.Id.Idp, - OnPremisesSamAccountName: &g.GroupName, - DisplayName: &g.DisplayName, - Mail: &g.Mail, - // TODO when to fetch and expand memberof, usernames or ids? - } -} diff --git a/extensions/graph/pkg/logging/logging.go b/extensions/graph/pkg/logging/logging.go deleted file mode 100644 index a62806d0db0..00000000000 --- a/extensions/graph/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/graph/pkg/config" - "github.com/owncloud/ocis/v2/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/graph/pkg/middleware/requireadmin.go b/extensions/graph/pkg/middleware/requireadmin.go deleted file mode 100644 index 5bc027ec0d6..00000000000 --- a/extensions/graph/pkg/middleware/requireadmin.go +++ /dev/null @@ -1,53 +0,0 @@ -package middleware - -import ( - "net/http" - - revactx "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/service/v0/errorcode" - settings "github.com/owncloud/ocis/v2/extensions/settings/pkg/service/v0" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/owncloud/ocis/v2/ocis-pkg/roles" -) - -// RequireAdmin middleware is used to require the user in context to be an admin / have account management permissions -func RequireAdmin(rm *roles.Manager, logger log.Logger) func(next http.Handler) http.Handler { - - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - u, ok := revactx.ContextGetUser(r.Context()) - if !ok { - errorcode.AccessDenied.Render(w, r, http.StatusUnauthorized, "Unauthorized") - return - } - if u.Id == nil || u.Id.OpaqueId == "" { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "user is missing an id") - return - } - // get roles from context - roleIDs, ok := roles.ReadRoleIDsFromContext(r.Context()) - if !ok { - logger.Debug().Str("userid", u.Id.OpaqueId).Msg("No roles in context, contacting settings service") - var err error - roleIDs, err = rm.FindRoleIDsForUser(r.Context(), u.Id.OpaqueId) - if err != nil { - logger.Err(err).Str("userid", u.Id.OpaqueId).Msg("failed to get roles for user") - errorcode.AccessDenied.Render(w, r, http.StatusUnauthorized, "Unauthorized") - return - } - if len(roleIDs) == 0 { - errorcode.AccessDenied.Render(w, r, http.StatusUnauthorized, "Unauthorized") - return - } - } - - // check if permission is present in roles of the authenticated account - if rm.FindPermissionByID(r.Context(), roleIDs, settings.AccountManagementPermissionID) != nil { - next.ServeHTTP(w, r) - return - } - - errorcode.AccessDenied.Render(w, r, http.StatusUnauthorized, "Unauthorized") - }) - } -} diff --git a/extensions/graph/pkg/server/debug/option.go b/extensions/graph/pkg/server/debug/option.go deleted file mode 100644 index 5ad37a18d9e..00000000000 --- a/extensions/graph/pkg/server/debug/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/graph/pkg/config" - "github.com/owncloud/ocis/v2/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/graph/pkg/server/debug/server.go b/extensions/graph/pkg/server/debug/server.go deleted file mode 100644 index ba4eed23afb..00000000000 --- a/extensions/graph/pkg/server/debug/server.go +++ /dev/null @@ -1,59 +0,0 @@ -package debug - -import ( - "io" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/graph/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/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.GetString()), - 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)), - ), 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/graph/pkg/server/http/option.go b/extensions/graph/pkg/server/http/option.go deleted file mode 100644 index ff4b19d7210..00000000000 --- a/extensions/graph/pkg/server/http/option.go +++ /dev/null @@ -1,76 +0,0 @@ -package http - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/graph/pkg/config" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/metrics" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/urfave/cli/v2" -) - -// 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 - Metrics *metrics.Metrics - Flags []cli.Flag - Namespace string -} - -// 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 - } -} - -// Metrics provides a function to set the metrics option. -func Metrics(val *metrics.Metrics) Option { - return func(o *Options) { - o.Metrics = val - } -} - -// Flags provides a function to set the flags option. -func Flags(val []cli.Flag) Option { - return func(o *Options) { - o.Flags = append(o.Flags, val...) - } -} - -// Namespace provides a function to set the Namespace option. -func Namespace(val string) Option { - return func(o *Options) { - o.Namespace = val - } -} diff --git a/extensions/graph/pkg/server/http/server.go b/extensions/graph/pkg/server/http/server.go deleted file mode 100644 index ed208ad22a0..00000000000 --- a/extensions/graph/pkg/server/http/server.go +++ /dev/null @@ -1,77 +0,0 @@ -package http - -import ( - "github.com/cs3org/reva/v2/pkg/events/server" - chimiddleware "github.com/go-chi/chi/v5/middleware" - "github.com/go-micro/plugins/v4/events/natsjs" - graphMiddleware "github.com/owncloud/ocis/v2/extensions/graph/pkg/middleware" - svc "github.com/owncloud/ocis/v2/extensions/graph/pkg/service/v0" - "github.com/owncloud/ocis/v2/ocis-pkg/account" - "github.com/owncloud/ocis/v2/ocis-pkg/middleware" - "github.com/owncloud/ocis/v2/ocis-pkg/service/http" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - "github.com/pkg/errors" - "go-micro.dev/v4" -) - -// Server initializes the http service and server. -func Server(opts ...Option) (http.Service, error) { - options := newOptions(opts...) - - service := http.NewService( - http.Logger(options.Logger), - http.Namespace(options.Config.HTTP.Namespace), - http.Name("graph"), - http.Version(version.GetString()), - http.Address(options.Config.HTTP.Addr), - http.Context(options.Context), - http.Flags(options.Flags...), - ) - - publisher, err := server.NewNatsStream( - natsjs.Address(options.Config.Events.Endpoint), - natsjs.ClusterID(options.Config.Events.Cluster), - ) - if err != nil { - options.Logger.Error(). - Err(err). - Msg("Error initializing events publisher") - return http.Service{}, errors.Wrap(err, "could not initialize events publisher") - } - - handle := svc.NewService( - svc.Logger(options.Logger), - svc.Config(options.Config), - svc.Middleware( - chimiddleware.RequestID, - middleware.Version( - "graph", - version.GetString(), - ), - middleware.Logger( - options.Logger, - ), - graphMiddleware.Auth( - account.Logger(options.Logger), - account.JWTSecret(options.Config.TokenManager.JWTSecret), - ), - ), - svc.EventsPublisher(publisher), - ) - - if handle == nil { - return http.Service{}, errors.New("could not initialize graph service") - } - - { - handle = svc.NewInstrument(handle, options.Metrics) - handle = svc.NewLogging(handle, options.Logger) - handle = svc.NewTracing(handle) - } - - if err := micro.RegisterHandler(service.Server(), handle); err != nil { - return http.Service{}, err - } - - return service, nil -} diff --git a/extensions/graph/pkg/service/v0/groups.go b/extensions/graph/pkg/service/v0/groups.go deleted file mode 100644 index 6d19bd7b480..00000000000 --- a/extensions/graph/pkg/service/v0/groups.go +++ /dev/null @@ -1,374 +0,0 @@ -package svc - -import ( - "encoding/json" - "errors" - "fmt" - "net/http" - "net/url" - "sort" - "strings" - - "github.com/CiscoM31/godata" - libregraph "github.com/owncloud/libre-graph-api-go" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/service/v0/errorcode" - - ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/cs3org/reva/v2/pkg/events" - "github.com/go-chi/chi/v5" - "github.com/go-chi/render" -) - -const memberRefsLimit = 20 - -// GetGroups implements the Service interface. -func (g Graph) GetGroups(w http.ResponseWriter, r *http.Request) { - sanitizedPath := strings.TrimPrefix(r.URL.Path, "/graph/v1.0/") - odataReq, err := godata.ParseRequest(r.Context(), sanitizedPath, r.URL.Query()) - if err != nil { - g.logger.Err(err).Interface("query", r.URL.Query()).Msg("query error") - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) - return - } - - groups, err := g.identityBackend.GetGroups(r.Context(), r.URL.Query()) - - if err != nil { - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } - } - - groups, err = sortGroups(odataReq, groups) - if err != nil { - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } - return - } - render.Status(r, http.StatusOK) - render.JSON(w, r, &listResponse{Value: groups}) -} - -// PostGroup implements the Service interface. -func (g Graph) PostGroup(w http.ResponseWriter, r *http.Request) { - grp := libregraph.NewGroup() - err := json.NewDecoder(r.Body).Decode(grp) - if err != nil { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) - return - } - - if _, ok := grp.GetDisplayNameOk(); !ok { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "Missing Required Attribute") - return - } - - // Disallow user-supplied IDs. It's supposed to be readonly. We're either - // generating them in the backend ourselves or rely on the Backend's - // storage (e.g. LDAP) to provide a unique ID. - if _, ok := grp.GetIdOk(); ok { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "group id is a read-only attribute") - return - } - - if grp, err = g.identityBackend.CreateGroup(r.Context(), *grp); err != nil { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - return - } - - if grp != nil && grp.Id != nil { - currentUser := ctxpkg.ContextMustGetUser(r.Context()) - g.publishEvent(events.GroupCreated{Executant: currentUser.Id, GroupID: *grp.Id}) - } - render.Status(r, http.StatusOK) - render.JSON(w, r, grp) -} - -// PatchGroup implements the Service interface. -func (g Graph) PatchGroup(w http.ResponseWriter, r *http.Request) { - g.logger.Debug().Msg("Calling PatchGroup") - groupID := chi.URLParam(r, "groupID") - groupID, err := url.PathUnescape(groupID) - if err != nil { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping group id failed") - return - } - - if groupID == "" { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing group id") - return - } - changes := libregraph.NewGroup() - err = json.NewDecoder(r.Body).Decode(changes) - if err != nil { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) - return - } - - if memberRefs, ok := changes.GetMembersodataBindOk(); ok { - // The spec defines a limit of 20 members maxium per Request - if len(memberRefs) > memberRefsLimit { - errorcode.NotAllowed.Render(w, r, http.StatusInternalServerError, - fmt.Sprintf("Request is limited to %d members", memberRefsLimit)) - return - } - memberIDs := make([]string, 0, len(memberRefs)) - for _, memberRef := range memberRefs { - memberType, id, err := g.parseMemberRef(memberRef) - if err != nil { - errorcode.InvalidRequest.Render(w, r, http.StatusInternalServerError, "Error parsing member@odata.bind values") - return - } - g.logger.Debug().Str("memberType", memberType).Str("memberid", id).Msg("Add Member") - // The MS Graph spec allows "directoryObject", "user", "group" and "organizational Contact" - // we restrict this to users for now. Might add Groups as members later - if memberType != "users" { - errorcode.InvalidRequest.Render(w, r, http.StatusInternalServerError, "Only user are allowed as group members") - return - } - memberIDs = append(memberIDs, id) - } - err = g.identityBackend.AddMembersToGroup(r.Context(), groupID, memberIDs) - } - - if err != nil { - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } - return - } - render.Status(r, http.StatusNoContent) - render.NoContent(w, r) -} - -// GetGroup implements the Service interface. -func (g Graph) GetGroup(w http.ResponseWriter, r *http.Request) { - groupID := chi.URLParam(r, "groupID") - groupID, err := url.PathUnescape(groupID) - if err != nil { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping group id failed") - } - - if groupID == "" { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing group id") - return - } - - group, err := g.identityBackend.GetGroup(r.Context(), groupID, r.URL.Query()) - if err != nil { - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } - } - - render.Status(r, http.StatusOK) - render.JSON(w, r, group) -} - -// DeleteGroup implements the Service interface. -func (g Graph) DeleteGroup(w http.ResponseWriter, r *http.Request) { - groupID := chi.URLParam(r, "groupID") - groupID, err := url.PathUnescape(groupID) - if err != nil { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping group id failed") - return - } - - if groupID == "" { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing group id") - return - } - - err = g.identityBackend.DeleteGroup(r.Context(), groupID) - - if err != nil { - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } - return - } - - currentUser := ctxpkg.ContextMustGetUser(r.Context()) - g.publishEvent(events.GroupDeleted{Executant: currentUser.Id, GroupID: groupID}) - render.Status(r, http.StatusNoContent) - render.NoContent(w, r) -} - -func (g Graph) GetGroupMembers(w http.ResponseWriter, r *http.Request) { - groupID := chi.URLParam(r, "groupID") - groupID, err := url.PathUnescape(groupID) - if err != nil { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping group id failed") - return - } - - if groupID == "" { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing group id") - return - } - - members, err := g.identityBackend.GetGroupMembers(r.Context(), groupID) - if err != nil { - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } - return - } - - render.Status(r, http.StatusOK) - render.JSON(w, r, members) -} - -// PostGroupMember implements the Service interface. -func (g Graph) PostGroupMember(w http.ResponseWriter, r *http.Request) { - g.logger.Info().Msg("Calling PostGroupMember") - - groupID := chi.URLParam(r, "groupID") - groupID, err := url.PathUnescape(groupID) - if err != nil { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping group id failed") - return - } - - if groupID == "" { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing group id") - return - } - memberRef := libregraph.NewMemberReference() - err = json.NewDecoder(r.Body).Decode(memberRef) - if err != nil { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) - return - } - memberRefURL, ok := memberRef.GetOdataIdOk() - if !ok { - errorcode.InvalidRequest.Render(w, r, http.StatusInternalServerError, "@odata.id refernce is missing") - return - } - memberType, id, err := g.parseMemberRef(*memberRefURL) - if err != nil { - errorcode.InvalidRequest.Render(w, r, http.StatusInternalServerError, "Error parsing @odata.id url") - return - } - // The MS Graph spec allows "directoryObject", "user", "group" and "organizational Contact" - // we restrict this to users for now. Might add Groups as members later - if memberType != "users" { - errorcode.InvalidRequest.Render(w, r, http.StatusInternalServerError, "Only user are allowed as group members") - return - } - - g.logger.Debug().Str("memberType", memberType).Str("id", id).Msg("Add Member") - err = g.identityBackend.AddMembersToGroup(r.Context(), groupID, []string{id}) - - if err != nil { - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } - return - } - - currentUser := ctxpkg.ContextMustGetUser(r.Context()) - g.publishEvent(events.GroupMemberAdded{Executant: currentUser.Id, GroupID: groupID, UserID: id}) - render.Status(r, http.StatusNoContent) - render.NoContent(w, r) -} - -// DeleteGroupMember implements the Service interface. -func (g Graph) DeleteGroupMember(w http.ResponseWriter, r *http.Request) { - g.logger.Info().Msg("Calling DeleteGroupMember") - - groupID := chi.URLParam(r, "groupID") - groupID, err := url.PathUnescape(groupID) - if err != nil { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping group id failed") - return - } - - if groupID == "" { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing group id") - return - } - - memberID := chi.URLParam(r, "memberID") - memberID, err = url.PathUnescape(memberID) - if err != nil { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping group id failed") - return - } - - if memberID == "" { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing group id") - return - } - g.logger.Debug().Str("groupID", groupID).Str("memberID", memberID).Msg("DeleteGroupMember") - err = g.identityBackend.RemoveMemberFromGroup(r.Context(), groupID, memberID) - - if err != nil { - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } - return - } - currentUser := ctxpkg.ContextMustGetUser(r.Context()) - g.publishEvent(events.GroupMemberRemoved{Executant: currentUser.Id, GroupID: groupID, UserID: memberID}) - render.Status(r, http.StatusNoContent) - render.NoContent(w, r) -} - -func (g Graph) parseMemberRef(ref string) (string, string, error) { - memberURL, err := url.ParseRequestURI(ref) - if err != nil { - return "", "", err - } - segments := strings.Split(memberURL.Path, "/") - if len(segments) < 2 { - return "", "", errors.New("invalid member reference") - } - id := segments[len(segments)-1] - memberType := segments[len(segments)-2] - return memberType, id, nil -} - -func sortGroups(req *godata.GoDataRequest, groups []*libregraph.Group) ([]*libregraph.Group, error) { - var sorter sort.Interface - if req.Query.OrderBy == nil || len(req.Query.OrderBy.OrderByItems) != 1 { - return groups, nil - } - switch req.Query.OrderBy.OrderByItems[0].Field.Value { - case "displayName": - sorter = groupsByDisplayName{groups} - default: - return nil, fmt.Errorf("we do not support <%s> as a order parameter", req.Query.OrderBy.OrderByItems[0].Field.Value) - } - - if req.Query.OrderBy.OrderByItems[0].Order == "desc" { - sorter = sort.Reverse(sorter) - } - sort.Sort(sorter) - return groups, nil -} diff --git a/extensions/graph/pkg/service/v0/instrument.go b/extensions/graph/pkg/service/v0/instrument.go deleted file mode 100644 index 7cad7f7c9c0..00000000000 --- a/extensions/graph/pkg/service/v0/instrument.go +++ /dev/null @@ -1,105 +0,0 @@ -package svc - -import ( - "net/http" - - "github.com/owncloud/ocis/v2/extensions/graph/pkg/metrics" -) - -// NewInstrument returns a service that instruments metrics. -func NewInstrument(next Service, metrics *metrics.Metrics) Service { - return instrument{ - next: next, - metrics: metrics, - } -} - -type instrument struct { - next Service - metrics *metrics.Metrics -} - -// ServeHTTP implements the Service interface. -func (i instrument) ServeHTTP(w http.ResponseWriter, r *http.Request) { - i.next.ServeHTTP(w, r) -} - -// GetMe implements the Service interface. -func (i instrument) GetMe(w http.ResponseWriter, r *http.Request) { - i.next.GetMe(w, r) -} - -// GetUsers implements the Service interface. -func (i instrument) GetUsers(w http.ResponseWriter, r *http.Request) { - i.next.GetUsers(w, r) -} - -// GetUser implements the Service interface. -func (i instrument) GetUser(w http.ResponseWriter, r *http.Request) { - i.next.GetUser(w, r) -} - -// PostUser implements the Service interface. -func (i instrument) PostUser(w http.ResponseWriter, r *http.Request) { - i.next.PostUser(w, r) -} - -// DeleteUser implements the Service interface. -func (i instrument) DeleteUser(w http.ResponseWriter, r *http.Request) { - i.next.DeleteUser(w, r) -} - -// PatchUser implements the Service interface. -func (i instrument) PatchUser(w http.ResponseWriter, r *http.Request) { - i.next.PatchUser(w, r) -} - -// ChangeOwnPassword implements the Service interface. -func (i instrument) ChangeOwnPassword(w http.ResponseWriter, r *http.Request) { - i.next.ChangeOwnPassword(w, r) -} - -// GetGroups implements the Service interface. -func (i instrument) GetGroups(w http.ResponseWriter, r *http.Request) { - i.next.GetGroups(w, r) -} - -// GetGroup implements the Service interface. -func (i instrument) GetGroup(w http.ResponseWriter, r *http.Request) { - i.next.GetGroup(w, r) -} - -// PostGroup implements the Service interface. -func (i instrument) PostGroup(w http.ResponseWriter, r *http.Request) { - i.next.PostGroup(w, r) -} - -// PatchGroup implements the Service interface. -func (i instrument) PatchGroup(w http.ResponseWriter, r *http.Request) { - i.next.PatchGroup(w, r) -} - -// DeleteGroup implements the Service interface. -func (i instrument) DeleteGroup(w http.ResponseWriter, r *http.Request) { - i.next.DeleteGroup(w, r) -} - -// GetGroupMembers implements the Service interface. -func (i instrument) GetGroupMembers(w http.ResponseWriter, r *http.Request) { - i.next.GetGroupMembers(w, r) -} - -// PostGroupMember implements the Service interface. -func (i instrument) PostGroupMember(w http.ResponseWriter, r *http.Request) { - i.next.PostGroupMember(w, r) -} - -// DeleteGroupMember implements the Service interface. -func (i instrument) DeleteGroupMember(w http.ResponseWriter, r *http.Request) { - i.next.DeleteGroupMember(w, r) -} - -// GetDrives implements the Service interface. -func (i instrument) GetDrives(w http.ResponseWriter, r *http.Request) { - i.next.GetDrives(w, r) -} diff --git a/extensions/graph/pkg/service/v0/option.go b/extensions/graph/pkg/service/v0/option.go deleted file mode 100644 index b8ebe47479c..00000000000 --- a/extensions/graph/pkg/service/v0/option.go +++ /dev/null @@ -1,102 +0,0 @@ -package svc - -import ( - "net/http" - - "github.com/cs3org/reva/v2/pkg/events" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/config" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/identity" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/owncloud/ocis/v2/ocis-pkg/roles" - settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" -) - -// 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 - Config *config.Config - Middleware []func(http.Handler) http.Handler - GatewayClient GatewayClient - IdentityBackend identity.Backend - HTTPClient HTTPClient - RoleService settingssvc.RoleService - RoleManager *roles.Manager - EventsPublisher events.Publisher -} - -// 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 - } -} - -// Config provides a function to set the config option. -func Config(val *config.Config) Option { - return func(o *Options) { - o.Config = val - } -} - -// Middleware provides a function to set the middleware option. -func Middleware(val ...func(http.Handler) http.Handler) Option { - return func(o *Options) { - o.Middleware = val - } -} - -// WithGatewayClient provides a function to set the gateway client option. -func WithGatewayClient(val GatewayClient) Option { - return func(o *Options) { - o.GatewayClient = val - } -} - -// WithIdentityBackend provides a function to set the IdentityBackend option. -func WithIdentityBackend(val identity.Backend) Option { - return func(o *Options) { - o.IdentityBackend = val - } -} - -// WithHTTPClient provides a function to set the http client option. -func WithHTTPClient(val HTTPClient) Option { - return func(o *Options) { - o.HTTPClient = val - } -} - -// RoleService provides a function to set the RoleService option. -func RoleService(val settingssvc.RoleService) Option { - return func(o *Options) { - o.RoleService = val - } -} - -// RoleManager provides a function to set the RoleManager option. -func RoleManager(val *roles.Manager) Option { - return func(o *Options) { - o.RoleManager = val - } -} - -// EventsPublisher provides a function to set the EventsPublisher option. -func EventsPublisher(val events.Publisher) Option { - return func(o *Options) { - o.EventsPublisher = val - } -} diff --git a/extensions/graph/pkg/service/v0/service.go b/extensions/graph/pkg/service/v0/service.go deleted file mode 100644 index ee31660a866..00000000000 --- a/extensions/graph/pkg/service/v0/service.go +++ /dev/null @@ -1,224 +0,0 @@ -package svc - -import ( - "crypto/tls" - "crypto/x509" - "io/ioutil" - "net/http" - "strconv" - "time" - - "github.com/ReneKroon/ttlcache/v2" - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" - "github.com/go-chi/chi/v5" - "github.com/go-chi/chi/v5/middleware" - - "github.com/owncloud/ocis/v2/extensions/graph/pkg/identity" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/identity/ldap" - graphm "github.com/owncloud/ocis/v2/extensions/graph/pkg/middleware" - ocisldap "github.com/owncloud/ocis/v2/ocis-pkg/ldap" - "github.com/owncloud/ocis/v2/ocis-pkg/roles" - "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" - settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" -) - -const ( - // HeaderPurge defines the header name for the purge header. - HeaderPurge = "Purge" -) - -// Service defines the extension handlers. -type Service interface { - ServeHTTP(http.ResponseWriter, *http.Request) - GetMe(http.ResponseWriter, *http.Request) - GetUsers(http.ResponseWriter, *http.Request) - GetUser(http.ResponseWriter, *http.Request) - PostUser(http.ResponseWriter, *http.Request) - DeleteUser(http.ResponseWriter, *http.Request) - PatchUser(http.ResponseWriter, *http.Request) - ChangeOwnPassword(http.ResponseWriter, *http.Request) - - GetGroups(http.ResponseWriter, *http.Request) - GetGroup(http.ResponseWriter, *http.Request) - PostGroup(http.ResponseWriter, *http.Request) - PatchGroup(http.ResponseWriter, *http.Request) - DeleteGroup(http.ResponseWriter, *http.Request) - GetGroupMembers(http.ResponseWriter, *http.Request) - PostGroupMember(http.ResponseWriter, *http.Request) - DeleteGroupMember(http.ResponseWriter, *http.Request) - - GetDrives(w http.ResponseWriter, r *http.Request) -} - -// NewService returns a service implementation for Service. -func NewService(opts ...Option) Service { - options := newOptions(opts...) - - m := chi.NewMux() - m.Use(options.Middleware...) - - svc := Graph{ - config: options.Config, - mux: m, - logger: &options.Logger, - spacePropertiesCache: ttlcache.NewCache(), - eventsPublisher: options.EventsPublisher, - } - if options.GatewayClient == nil { - var err error - svc.gatewayClient, err = pool.GetGatewayServiceClient(options.Config.Reva.Address) - if err != nil { - options.Logger.Error().Err(err).Msg("Could not get gateway client") - return nil - } - } else { - svc.gatewayClient = options.GatewayClient - } - if options.IdentityBackend == nil { - switch options.Config.Identity.Backend { - case "cs3": - svc.identityBackend = &identity.CS3{ - Config: options.Config.Reva, - Logger: &options.Logger, - } - case "ldap": - var err error - - var tlsConf *tls.Config - if options.Config.Identity.LDAP.Insecure { - // When insecure is set to true then we don't need a certificate. - options.Config.Identity.LDAP.CACert = "" - tlsConf = &tls.Config{ - //nolint:gosec // We need the ability to run with "insecure" (dev/testing) - InsecureSkipVerify: options.Config.Identity.LDAP.Insecure, - } - } - - if options.Config.Identity.LDAP.CACert != "" { - if err := ocisldap.WaitForCA(options.Logger, - options.Config.Identity.LDAP.Insecure, - options.Config.Identity.LDAP.CACert); err != nil { - options.Logger.Fatal().Err(err).Msg("The configured LDAP CA cert does not exist") - } - if tlsConf == nil { - tlsConf = &tls.Config{} - } - certs := x509.NewCertPool() - pemData, err := ioutil.ReadFile(options.Config.Identity.LDAP.CACert) - if err != nil { - options.Logger.Error().Err(err).Msgf("Error initializing LDAP Backend") - return nil - } - if !certs.AppendCertsFromPEM(pemData) { - options.Logger.Error().Msgf("Error initializing LDAP Backend. Adding CA cert failed") - return nil - } - tlsConf.RootCAs = certs - } - - conn := ldap.NewLDAPWithReconnect(&options.Logger, - ldap.Config{ - URI: options.Config.Identity.LDAP.URI, - BindDN: options.Config.Identity.LDAP.BindDN, - BindPassword: options.Config.Identity.LDAP.BindPassword, - TLSConfig: tlsConf, - }, - ) - if svc.identityBackend, err = identity.NewLDAPBackend(conn, options.Config.Identity.LDAP, &options.Logger); err != nil { - options.Logger.Error().Msgf("Error initializing LDAP Backend: '%s'", err) - return nil - } - default: - options.Logger.Error().Msgf("Unknown Identity Backend: '%s'", options.Config.Identity.Backend) - return nil - } - } else { - svc.identityBackend = options.IdentityBackend - } - - if options.HTTPClient == nil { - http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{ - InsecureSkipVerify: options.Config.Spaces.Insecure, //nolint:gosec - } - svc.httpClient = &http.Client{} - } else { - svc.httpClient = options.HTTPClient - } - - if options.RoleService == nil { - svc.roleService = settingssvc.NewRoleService("com.owncloud.api.settings", grpc.DefaultClient) - } else { - svc.roleService = options.RoleService - } - - roleManager := options.RoleManager - if roleManager == nil { - m := roles.NewManager( - roles.CacheSize(1024), - roles.CacheTTL(time.Hour), - roles.Logger(options.Logger), - roles.RoleService(svc.roleService), - ) - roleManager = &m - } - - requireAdmin := graphm.RequireAdmin(roleManager, options.Logger) - - m.Route(options.Config.HTTP.Root, func(r chi.Router) { - r.Use(middleware.StripSlashes) - r.Route("/v1.0", func(r chi.Router) { - r.Route("/me", func(r chi.Router) { - r.Get("/", svc.GetMe) - r.Get("/drives", svc.GetDrives) - r.Get("/drive/root/children", svc.GetRootDriveChildren) - r.Post("/changePassword", svc.ChangeOwnPassword) - }) - r.Route("/users", func(r chi.Router) { - r.With(requireAdmin).Get("/", svc.GetUsers) - r.With(requireAdmin).Post("/", svc.PostUser) - r.Route("/{userID}", func(r chi.Router) { - r.Get("/", svc.GetUser) - r.With(requireAdmin).Delete("/", svc.DeleteUser) - r.With(requireAdmin).Patch("/", svc.PatchUser) - }) - }) - r.Route("/groups", func(r chi.Router) { - r.With(requireAdmin).Get("/", svc.GetGroups) - r.With(requireAdmin).Post("/", svc.PostGroup) - r.Route("/{groupID}", func(r chi.Router) { - r.Get("/", svc.GetGroup) - r.With(requireAdmin).Delete("/", svc.DeleteGroup) - r.With(requireAdmin).Patch("/", svc.PatchGroup) - r.Route("/members", func(r chi.Router) { - r.With(requireAdmin).Get("/", svc.GetGroupMembers) - r.With(requireAdmin).Post("/$ref", svc.PostGroupMember) - r.With(requireAdmin).Delete("/{memberID}/$ref", svc.DeleteGroupMember) - }) - }) - }) - r.Route("/drives", func(r chi.Router) { - r.Get("/", svc.GetAllDrives) - r.Post("/", svc.CreateDrive) - r.Route("/{driveID}", func(r chi.Router) { - r.Patch("/", svc.UpdateDrive) - r.Get("/", svc.GetSingleDrive) - r.Delete("/", svc.DeleteDrive) - }) - }) - }) - }) - - return svc -} - -// parseHeaderPurge parses the 'Purge' header. -// '1', 't', 'T', 'TRUE', 'true', 'True' are parsed as true -// all other values are false. -func parsePurgeHeader(h http.Header) bool { - val := h.Get(HeaderPurge) - - if b, err := strconv.ParseBool(val); err == nil { - return b - } - return false -} diff --git a/extensions/graph/pkg/service/v0/users.go b/extensions/graph/pkg/service/v0/users.go deleted file mode 100644 index d567e3e98fe..00000000000 --- a/extensions/graph/pkg/service/v0/users.go +++ /dev/null @@ -1,309 +0,0 @@ -package svc - -import ( - "encoding/json" - "errors" - "fmt" - "net/http" - "net/url" - "regexp" - "sort" - "strings" - - "github.com/CiscoM31/godata" - ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" - revactx "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/cs3org/reva/v2/pkg/events" - "github.com/go-chi/chi/v5" - "github.com/go-chi/render" - libregraph "github.com/owncloud/libre-graph-api-go" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/identity" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/service/v0/errorcode" - settingssvc "github.com/owncloud/ocis/v2/extensions/settings/pkg/service/v0" - settings "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" -) - -// GetMe implements the Service interface. -func (g Graph) GetMe(w http.ResponseWriter, r *http.Request) { - - u, ok := revactx.ContextGetUser(r.Context()) - if !ok { - g.logger.Error().Msg("user not in context") - errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, "user not in context") - return - } - - g.logger.Info().Interface("user", u).Msg("User in /me") - - me := identity.CreateUserModelFromCS3(u) - - render.Status(r, http.StatusOK) - render.JSON(w, r, me) -} - -// GetUsers implements the Service interface. -func (g Graph) GetUsers(w http.ResponseWriter, r *http.Request) { - sanitizedPath := strings.TrimPrefix(r.URL.Path, "/graph/v1.0/") - odataReq, err := godata.ParseRequest(r.Context(), sanitizedPath, r.URL.Query()) - if err != nil { - g.logger.Err(err).Interface("query", r.URL.Query()).Msg("query error") - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) - return - } - users, err := g.identityBackend.GetUsers(r.Context(), r.URL.Query()) - if err != nil { - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } - return - } - - users, err = sortUsers(odataReq, users) - if err != nil { - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } - return - } - render.Status(r, http.StatusOK) - render.JSON(w, r, &listResponse{Value: users}) -} - -func (g Graph) PostUser(w http.ResponseWriter, r *http.Request) { - u := libregraph.NewUser() - err := json.NewDecoder(r.Body).Decode(u) - if err != nil { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) - return - } - - if _, ok := u.GetDisplayNameOk(); !ok { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing required Attribute: 'displayName'") - return - } - if accountName, ok := u.GetOnPremisesSamAccountNameOk(); ok { - if !isValidUsername(*accountName) { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, - fmt.Sprintf("username '%s' must be at least the local part of an email", *u.OnPremisesSamAccountName)) - return - } - } else { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing required Attribute: 'onPremisesSamAccountName'") - return - } - - if mail, ok := u.GetMailOk(); ok { - if !isValidEmail(*mail) { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, - fmt.Sprintf("'%s' is not a valid email address", *u.Mail)) - return - } - } else { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing required Attribute: 'mail'") - return - } - - // Disallow user-supplied IDs. It's supposed to be readonly. We're either - // generating them in the backend ourselves or rely on the Backend's - // storage (e.g. LDAP) to provide a unique ID. - if _, ok := u.GetIdOk(); ok { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "user id is a read-only attribute") - return - } - - if u, err = g.identityBackend.CreateUser(r.Context(), *u); err != nil { - var ecErr errorcode.Error - if errors.As(err, &ecErr) { - ecErr.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } - return - } - - // All users get the user role by default currently. - // to all new users for now, as create Account request does not have any role field - if g.roleService == nil { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "could not assign role to account: roleService not configured") - return - } - if _, err = g.roleService.AssignRoleToUser(r.Context(), &settings.AssignRoleToUserRequest{ - AccountUuid: *u.Id, - RoleId: settingssvc.BundleUUIDRoleUser, - }); err != nil { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, fmt.Sprintf("could not assign role to account %s", err.Error())) - return - } - - currentUser := ctxpkg.ContextMustGetUser(r.Context()) - g.publishEvent(events.UserCreated{Executant: currentUser.Id, UserID: *u.Id}) - - render.Status(r, http.StatusOK) - render.JSON(w, r, u) -} - -// GetUser implements the Service interface. -func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) { - userID := chi.URLParam(r, "userID") - userID, err := url.PathUnescape(userID) - if err != nil { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping user id failed") - } - - if userID == "" { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing user id") - return - } - - user, err := g.identityBackend.GetUser(r.Context(), userID, r.URL.Query()) - - if err != nil { - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } - } - - render.Status(r, http.StatusOK) - render.JSON(w, r, user) -} - -func (g Graph) DeleteUser(w http.ResponseWriter, r *http.Request) { - userID := chi.URLParam(r, "userID") - userID, err := url.PathUnescape(userID) - if err != nil { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping user id failed") - } - - if userID == "" { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing user id") - return - } - - err = g.identityBackend.DeleteUser(r.Context(), userID) - - if err != nil { - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } - } - - currentUser := ctxpkg.ContextMustGetUser(r.Context()) - g.publishEvent(events.UserDeleted{Executant: currentUser.Id, UserID: userID}) - - render.Status(r, http.StatusNoContent) - render.NoContent(w, r) -} - -// PatchUser implements the Service Interface. Updates the specified attributes of an -// ExistingUser -func (g Graph) PatchUser(w http.ResponseWriter, r *http.Request) { - nameOrID := chi.URLParam(r, "userID") - nameOrID, err := url.PathUnescape(nameOrID) - if err != nil { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping user id failed") - } - - if nameOrID == "" { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing user id") - return - } - changes := libregraph.NewUser() - err = json.NewDecoder(r.Body).Decode(changes) - if err != nil { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) - return - } - - var features []events.UserFeature - if mail, ok := changes.GetMailOk(); ok { - if !isValidEmail(*mail) { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, - fmt.Sprintf("'%s' is not a valid email address", *mail)) - return - } - features = append(features, events.UserFeature{Name: "email", Value: *mail}) - } - - if name, ok := changes.GetDisplayNameOk(); ok { - features = append(features, events.UserFeature{Name: "displayname", Value: *name}) - } - - u, err := g.identityBackend.UpdateUser(r.Context(), nameOrID, *changes) - if err != nil { - var errcode errorcode.Error - if errors.As(err, &errcode) { - errcode.Render(w, r) - } else { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - } - } - - currentUser := ctxpkg.ContextMustGetUser(r.Context()) - g.publishEvent( - events.UserFeatureChanged{ - Executant: currentUser.Id, - UserID: nameOrID, - Features: features, - }, - ) - render.Status(r, http.StatusOK) - render.JSON(w, r, u) - -} - -// We want to allow email addresses as usernames so they show up when using them in ACLs on storages that allow integration with our glauth LDAP service -// so we are adding a few restrictions from https://stackoverflow.com/questions/6949667/what-are-the-real-rules-for-linux-usernames-on-centos-6-and-rhel-6 -// names should not start with numbers -var usernameRegex = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]*(@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)*$") - -func isValidUsername(e string) bool { - if len(e) < 1 && len(e) > 254 { - return false - } - return usernameRegex.MatchString(e) -} - -// regex from https://www.w3.org/TR/2016/REC-html51-20161101/sec-forms.html#valid-e-mail-address -var emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") - -func isValidEmail(e string) bool { - if len(e) < 3 && len(e) > 254 { - return false - } - return emailRegex.MatchString(e) -} - -func sortUsers(req *godata.GoDataRequest, users []*libregraph.User) ([]*libregraph.User, error) { - var sorter sort.Interface - if req.Query.OrderBy == nil || len(req.Query.OrderBy.OrderByItems) != 1 { - return users, nil - } - switch req.Query.OrderBy.OrderByItems[0].Field.Value { - case "displayName": - sorter = usersByDisplayName{users} - case "mail": - sorter = usersByMail{users} - case "onPremisesSamAccountName": - sorter = usersByOnPremisesSamAccountName{users} - default: - return nil, fmt.Errorf("we do not support <%s> as a order parameter", req.Query.OrderBy.OrderByItems[0].Field.Value) - } - - if req.Query.OrderBy.OrderByItems[0].Order == "desc" { - sorter = sort.Reverse(sorter) - } - sort.Sort(sorter) - return users, nil -} diff --git a/extensions/graph/pkg/tracing/tracing.go b/extensions/graph/pkg/tracing/tracing.go deleted file mode 100644 index fe7439f635e..00000000000 --- a/extensions/graph/pkg/tracing/tracing.go +++ /dev/null @@ -1,23 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/v2/extensions/graph/pkg/config" - pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" - "go.opentelemetry.io/otel/trace" -) - -var ( - // TraceProvider is the global trace provider for the graph service. - TraceProvider = trace.NewNoopTracerProvider() -) - -func Configure(cfg *config.Config) error { - var err error - if cfg.Tracing.Enabled { - if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { - return err - } - } - - return nil -} diff --git a/extensions/groups/cmd/groups/main.go b/extensions/groups/cmd/groups/main.go deleted file mode 100644 index f6a9bf09f82..00000000000 --- a/extensions/groups/cmd/groups/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/groups/pkg/command" - "github.com/owncloud/ocis/v2/extensions/groups/pkg/config/defaults" -) - -func main() { - if err := command.Execute(defaults.DefaultConfig()); err != nil { - os.Exit(1) - } -} diff --git a/extensions/groups/pkg/command/health.go b/extensions/groups/pkg/command/health.go deleted file mode 100644 index 756205dbe6d..00000000000 --- a/extensions/groups/pkg/command/health.go +++ /dev/null @@ -1,57 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/groups/pkg/config" - "github.com/owncloud/ocis/v2/extensions/groups/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/groups/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/groups/pkg/command/root.go b/extensions/groups/pkg/command/root.go deleted file mode 100644 index 9488ae00288..00000000000 --- a/extensions/groups/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/groups/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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.Groups.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Groups, - } -} - -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/groups/pkg/command/server.go b/extensions/groups/pkg/command/server.go deleted file mode 100644 index 00c1247b307..00000000000 --- a/extensions/groups/pkg/command/server.go +++ /dev/null @@ -1,121 +0,0 @@ -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/v2/extensions/groups/pkg/config" - "github.com/owncloud/ocis/v2/extensions/groups/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/groups/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/groups/pkg/revaconfig" - "github.com/owncloud/ocis/v2/extensions/groups/pkg/server/debug" - "github.com/owncloud/ocis/v2/extensions/groups/pkg/tracing" - "github.com/owncloud/ocis/v2/ocis-pkg/ldap" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" - "github.com/owncloud/ocis/v2/ocis-pkg/sync" - "github.com/owncloud/ocis/v2/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) - os.Exit(1) - } - 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.GetString(), - 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/groups/pkg/command/version.go b/extensions/groups/pkg/command/version.go deleted file mode 100644 index 9ad6bb137a2..00000000000 --- a/extensions/groups/pkg/command/version.go +++ /dev/null @@ -1,50 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - tw "github.com/olekukonko/tablewriter" - "github.com/owncloud/ocis/v2/extensions/groups/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.GetString()) - 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/groups/pkg/config/defaults/defaultconfig.go b/extensions/groups/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index 459d6f097fc..00000000000 --- a/extensions/groups/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,125 +0,0 @@ -package defaults - -import ( - "path/filepath" - - "github.com/owncloud/ocis/v2/extensions/groups/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" -) - -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:9161", - Token: "", - Pprof: false, - Zpages: false, - }, - GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9160", - Namespace: "com.owncloud.api", - Protocol: "tcp", - }, - Service: config.Service{ - Name: "groups", - }, - Reva: &config.Reva{ - Address: "127.0.0.1:9142", - }, - Driver: "ldap", - Drivers: config.Drivers{ - LDAP: config.LDAPDriver{ - URI: "ldaps://localhost:9235", - CACert: filepath.Join(defaults.BaseDataPath(), "idm", "ldap.crt"), - Insecure: false, - UserBaseDN: "ou=users,o=libregraph-idm", - GroupBaseDN: "ou=groups,o=libregraph-idm", - UserScope: "sub", - GroupScope: "sub", - UserFilter: "", - GroupFilter: "", - UserObjectClass: "inetOrgPerson", - GroupObjectClass: "groupOfNames", - BindDN: "uid=reva,ou=sysusers,o=libregraph-idm", - IDP: "https://localhost:9200", - UserSchema: config.LDAPUserSchema{ - ID: "ownclouduuid", - Mail: "mail", - DisplayName: "displayname", - Username: "uid", - }, - GroupSchema: config.LDAPGroupSchema{ - ID: "ownclouduuid", - Mail: "mail", - DisplayName: "cn", - Groupname: "cn", - Member: "member", - }, - }, - OwnCloudSQL: config.OwnCloudSQLDriver{ - DBUsername: "owncloud", - DBPassword: "", - DBHost: "mysql", - DBPort: 3306, - DBName: "owncloud", - IDP: "https://localhost:9200", - Nobody: 90, - JoinUsername: false, - JoinOwnCloudUUID: false, - EnableMedialSearch: false, - }, - }, - } -} - -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/groups/pkg/config/parser/parse.go b/extensions/groups/pkg/config/parser/parse.go deleted file mode 100644 index a55593fbb04..00000000000 --- a/extensions/groups/pkg/config/parser/parse.go +++ /dev/null @@ -1,46 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/groups/pkg/config" - "github.com/owncloud/ocis/v2/extensions/groups/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - if cfg.TokenManager.JWTSecret == "" { - return shared.MissingJWTTokenError(cfg.Service.Name) - } - - if cfg.Drivers.LDAP.BindPassword == "" && cfg.Driver == "ldap" { - return shared.MissingLDAPBindPassword(cfg.Service.Name) - } - - return nil -} diff --git a/extensions/groups/pkg/logging/logging.go b/extensions/groups/pkg/logging/logging.go deleted file mode 100644 index 8303ffbfa7a..00000000000 --- a/extensions/groups/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/groups/pkg/config" - "github.com/owncloud/ocis/v2/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/groups/pkg/revaconfig/config.go b/extensions/groups/pkg/revaconfig/config.go deleted file mode 100644 index 2bdae3d6e85..00000000000 --- a/extensions/groups/pkg/revaconfig/config.go +++ /dev/null @@ -1,83 +0,0 @@ -package revaconfig - -import ( - "github.com/owncloud/ocis/v2/extensions/groups/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, - "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, - "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/groups/pkg/server/debug/option.go b/extensions/groups/pkg/server/debug/option.go deleted file mode 100644 index 96aa7566af1..00000000000 --- a/extensions/groups/pkg/server/debug/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/groups/pkg/config" - "github.com/owncloud/ocis/v2/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/groups/pkg/server/debug/server.go b/extensions/groups/pkg/server/debug/server.go deleted file mode 100644 index f10461c48aa..00000000000 --- a/extensions/groups/pkg/server/debug/server.go +++ /dev/null @@ -1,63 +0,0 @@ -package debug - -import ( - "io" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/groups/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/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.GetString()), - 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/groups/pkg/tracing/tracing.go b/extensions/groups/pkg/tracing/tracing.go deleted file mode 100644 index 93f60b50268..00000000000 --- a/extensions/groups/pkg/tracing/tracing.go +++ /dev/null @@ -1,18 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/v2/extensions/groups/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/owncloud/ocis/v2/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/cmd/idm/main.go b/extensions/idm/cmd/idm/main.go deleted file mode 100644 index 56faeab58cf..00000000000 --- a/extensions/idm/cmd/idm/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/idm/pkg/command" - "github.com/owncloud/ocis/v2/extensions/idm/pkg/config/defaults" -) - -func main() { - if err := command.Execute(defaults.DefaultConfig()); err != nil { - os.Exit(1) - } -} diff --git a/extensions/idm/pkg/command/health.go b/extensions/idm/pkg/command/health.go deleted file mode 100644 index 9d4cedc4594..00000000000 --- a/extensions/idm/pkg/command/health.go +++ /dev/null @@ -1,57 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/idm/pkg/config" - "github.com/owncloud/ocis/v2/extensions/idm/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/idm/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/idm/pkg/command/root.go b/extensions/idm/pkg/command/root.go deleted file mode 100644 index c4057e9cf3e..00000000000 --- a/extensions/idm/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/idm/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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-idm command. -func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "idm", - Usage: "Embedded LDAP service 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 idm command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new idm.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.IDM.Commons = cfg.Commons - return SutureService{ - cfg: cfg.IDM, - } -} - -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/idm/pkg/command/server.go b/extensions/idm/pkg/command/server.go deleted file mode 100644 index 45f6e2d18fd..00000000000 --- a/extensions/idm/pkg/command/server.go +++ /dev/null @@ -1,174 +0,0 @@ -package command - -import ( - "context" - "encoding/base64" - "errors" - "fmt" - "html/template" - "os" - "strings" - - "github.com/go-ldap/ldif" - "github.com/libregraph/idm/pkg/ldappassword" - "github.com/libregraph/idm/pkg/ldbbolt" - "github.com/libregraph/idm/server" - "github.com/owncloud/ocis/v2/extensions/idm" - "github.com/owncloud/ocis/v2/extensions/idm/pkg/config" - "github.com/owncloud/ocis/v2/extensions/idm/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/idm/pkg/logging" - pkgcrypto "github.com/owncloud/ocis/v2/ocis-pkg/crypto" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/urfave/cli/v2" -) - -// Server is the entrypoint 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) - os.Exit(1) - } - return err - }, - Action: func(c *cli.Context) error { - logger := logging.Configure(cfg.Service.Name, cfg.Log) - ctx, cancel := func() (context.Context, context.CancelFunc) { - if cfg.Context == nil { - return context.WithCancel(context.Background()) - } - return context.WithCancel(cfg.Context) - }() - - defer cancel() - return start(ctx, logger, cfg) - }, - } -} - -func start(ctx context.Context, logger log.Logger, cfg *config.Config) error { - servercfg := server.Config{ - Logger: log.LogrusWrap(logger.Logger), - LDAPHandler: "boltdb", - LDAPSListenAddr: cfg.IDM.LDAPSAddr, - TLSCertFile: cfg.IDM.Cert, - TLSKeyFile: cfg.IDM.Key, - LDAPBaseDN: "o=libregraph-idm", - LDAPAdminDN: "uid=libregraph,ou=sysusers,o=libregraph-idm", - - BoltDBFile: cfg.IDM.DatabasePath, - } - - if cfg.IDM.LDAPSAddr != "" { - // Generate a self-signing cert if no certificate is present - if err := pkgcrypto.GenCert(cfg.IDM.Cert, cfg.IDM.Key, logger); err != nil { - logger.Fatal().Err(err).Msgf("Could not generate test-certificate") - } - } - if _, err := os.Stat(servercfg.BoltDBFile); errors.Is(err, os.ErrNotExist) { - logger.Debug().Msg("Bootstrapping IDM database") - if err = bootstrap(logger, cfg, servercfg); err != nil { - logger.Error().Err(err).Msg("failed to bootstrap idm database") - } - } - - svc, err := server.NewServer(&servercfg) - if err != nil { - return err - } - return svc.Serve(ctx) -} - -func bootstrap(logger log.Logger, cfg *config.Config, srvcfg server.Config) error { - // Hash password if the config does not supply a hash already - var err error - - type svcUser struct { - Name string - Password string - ID string - } - - serviceUsers := []svcUser{ - { - Name: "admin", - Password: cfg.ServiceUserPasswords.OcisAdmin, - ID: cfg.AdminUserID, - }, - { - Name: "libregraph", - Password: cfg.ServiceUserPasswords.Idm, - }, - { - Name: "idp", - Password: cfg.ServiceUserPasswords.Idp, - }, - { - Name: "reva", - Password: cfg.ServiceUserPasswords.Reva, - }, - } - - bdb := &ldbbolt.LdbBolt{} - - if err := bdb.Configure(srvcfg.Logger, srvcfg.LDAPBaseDN, srvcfg.BoltDBFile, nil); err != nil { - return err - } - defer bdb.Close() - - if err := bdb.Initialize(); err != nil { - return err - } - - // Prepare the initial Data from template. To be able to set the - // supplied service user passwords - tmpl, err := template.New("baseldif").Parse(idm.BaseLDIF) - if err != nil { - return err - } - - for i := range serviceUsers { - if strings.HasPrefix(serviceUsers[i].Password, "$argon2id$") { - // password is alread hashed - serviceUsers[i].Password = "{ARGON2}" + serviceUsers[i].Password - } else { - if serviceUsers[i].Password, err = ldappassword.Hash(serviceUsers[i].Password, "{ARGON2}"); err != nil { - return err - } - } - // We need to treat the hash as binary in the LDIF template to avoid - // go-ldap/ldif to to any fancy escaping - serviceUsers[i].Password = base64.StdEncoding.EncodeToString([]byte(serviceUsers[i].Password)) - } - var tmplWriter strings.Builder - err = tmpl.Execute(&tmplWriter, serviceUsers) - if err != nil { - return err - } - - bootstrapData := tmplWriter.String() - if cfg.CreateDemoUsers { - bootstrapData = bootstrapData + "\n" + idm.DemoUsersLDIF - } - - s := strings.NewReader(bootstrapData) - lf := &ldif.LDIF{} - err = ldif.Unmarshal(s, lf) - if err != nil { - return err - } - - for _, entry := range lf.AllEntries() { - logger.Debug().Str("dn", entry.DN).Msg("Adding entry") - if err := bdb.EntryPut(entry); err != nil { - return fmt.Errorf("error adding Entry '%s': %w", entry.DN, err) - } - } - - return nil -} diff --git a/extensions/idm/pkg/command/version.go b/extensions/idm/pkg/command/version.go deleted file mode 100644 index d11ce980edd..00000000000 --- a/extensions/idm/pkg/command/version.go +++ /dev/null @@ -1,19 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/extensions/idm/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 { - // not implemented - return nil - }, - } -} diff --git a/extensions/idm/pkg/config/defaults/defaultconfig.go b/extensions/idm/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index 985ec60cecb..00000000000 --- a/extensions/idm/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,66 +0,0 @@ -package defaults - -import ( - "path" - - "github.com/owncloud/ocis/v2/extensions/idm/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" -) - -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:9239", - }, - Service: config.Service{ - Name: "idm", - }, - CreateDemoUsers: false, - IDM: config.Settings{ - LDAPSAddr: "127.0.0.1:9235", - Cert: path.Join(defaults.BaseDataPath(), "idm", "ldap.crt"), - Key: path.Join(defaults.BaseDataPath(), "idm", "ldap.key"), - DatabasePath: path.Join(defaults.BaseDataPath(), "idm", "ocis.boltdb"), - }, - } -} - -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.AdminUserID == "" && cfg.Commons != nil { - cfg.AdminUserID = cfg.Commons.AdminUserID - } -} - -func Sanitize(cfg *config.Config) { - // nothing to sanitize here -} diff --git a/extensions/idm/pkg/config/parser/parse.go b/extensions/idm/pkg/config/parser/parse.go deleted file mode 100644 index e5c791e1dc0..00000000000 --- a/extensions/idm/pkg/config/parser/parse.go +++ /dev/null @@ -1,57 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/idm/pkg/config" - "github.com/owncloud/ocis/v2/extensions/idm/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - if cfg.AdminUserID == "" { - return shared.MissingAdminUserID(cfg.Service.Name) - } - - if cfg.ServiceUserPasswords.Idm == "" { - return shared.MissingServiceUserPassword(cfg.Service.Name, "IDM") - } - - if cfg.ServiceUserPasswords.OcisAdmin == "" { - return shared.MissingServiceUserPassword(cfg.Service.Name, "admin") - } - - if cfg.ServiceUserPasswords.Idp == "" { - return shared.MissingServiceUserPassword(cfg.Service.Name, "IDP") - } - - if cfg.ServiceUserPasswords.Reva == "" { - return shared.MissingServiceUserPassword(cfg.Service.Name, "REVA") - } - - return nil -} diff --git a/extensions/idm/pkg/logging/logging.go b/extensions/idm/pkg/logging/logging.go deleted file mode 100644 index 76d45f3e4d9..00000000000 --- a/extensions/idm/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/idm/pkg/config" - "github.com/owncloud/ocis/v2/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/idm/pkg/server/debug/option.go b/extensions/idm/pkg/server/debug/option.go deleted file mode 100644 index 1eca46af326..00000000000 --- a/extensions/idm/pkg/server/debug/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/idm/pkg/config" - "github.com/owncloud/ocis/v2/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/idm/pkg/server/debug/server.go b/extensions/idm/pkg/server/debug/server.go deleted file mode 100644 index 6db3a833e58..00000000000 --- a/extensions/idm/pkg/server/debug/server.go +++ /dev/null @@ -1,59 +0,0 @@ -package debug - -import ( - "io" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/idm/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/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.GetString()), - 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)), - ), 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/idp/cmd/idp/main.go b/extensions/idp/cmd/idp/main.go deleted file mode 100644 index d632da7f4a2..00000000000 --- a/extensions/idp/cmd/idp/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/idp/pkg/command" - "github.com/owncloud/ocis/v2/extensions/idp/pkg/config/defaults" -) - -func main() { - if err := command.Execute(defaults.DefaultConfig()); err != nil { - os.Exit(1) - } -} diff --git a/extensions/idp/pkg/assets/option.go b/extensions/idp/pkg/assets/option.go deleted file mode 100644 index 6e89ac6f254..00000000000 --- a/extensions/idp/pkg/assets/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package assets - -import ( - "net/http" - - "github.com/owncloud/ocis/v2/extensions/idp" - "github.com/owncloud/ocis/v2/extensions/idp/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/assetsfs" - "github.com/owncloud/ocis/v2/ocis-pkg/log" -) - -// New returns a new http filesystem to serve assets. -func New(opts ...Option) http.FileSystem { - options := newOptions(opts...) - return assetsfs.New(idp.Assets, options.Config.Asset.Path, options.Logger) -} - -// 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 - 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 - } -} - -// 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/idp/pkg/backends/cs3/bootstrap/cs3.go b/extensions/idp/pkg/backends/cs3/bootstrap/cs3.go deleted file mode 100644 index 964332d5a77..00000000000 --- a/extensions/idp/pkg/backends/cs3/bootstrap/cs3.go +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2021 Kopano and its licensors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package bootstrap - -import ( - "fmt" - "os" - - "github.com/libregraph/lico/bootstrap" - "github.com/libregraph/lico/identifier" - "github.com/libregraph/lico/identity" - "github.com/libregraph/lico/identity/managers" - cs3 "github.com/owncloud/ocis/v2/extensions/idp/pkg/backends/cs3/identifier" -) - -// Identity managers. -const ( - identityManagerName = "cs3" -) - -// Register adds the CS3 identity manager to the lico bootstrap -func Register() error { - return bootstrap.RegisterIdentityManager(identityManagerName, NewIdentityManager) -} - -// MustRegister adds the CS3 identity manager to the lico bootstrap or panics -func MustRegister() { - if err := Register(); err != nil { - panic(err) - } -} - -// NewIdentityManager produces a CS3 backed identity manager instance for the idp -func NewIdentityManager(bs bootstrap.Bootstrap) (identity.Manager, error) { - config := bs.Config() - - logger := config.Config.Logger - - if config.AuthorizationEndpointURI.String() != "" { - return nil, fmt.Errorf("cs3 backend is incompatible with authorization-endpoint-uri parameter") - } - config.AuthorizationEndpointURI.Path = bs.MakeURIPath(bootstrap.APITypeSignin, "/identifier/_/authorize") - - if config.EndSessionEndpointURI.String() != "" { - return nil, fmt.Errorf("cs3 backend is incompatible with endsession-endpoint-uri parameter") - } - config.EndSessionEndpointURI.Path = bs.MakeURIPath(bootstrap.APITypeSignin, "/identifier/_/endsession") - - if config.SignInFormURI.EscapedPath() == "" { - config.SignInFormURI.Path = bs.MakeURIPath(bootstrap.APITypeSignin, "/identifier") - } - - if config.SignedOutURI.EscapedPath() == "" { - config.SignedOutURI.Path = bs.MakeURIPath(bootstrap.APITypeSignin, "/goodbye") - } - - identifierBackend, identifierErr := cs3.NewCS3Backend( - config.Config, - config.TLSClientConfig, - // FIXME add a map[string]interface{} property to the lico config.Config so backends can pass custom config parameters through the bootstrap process - os.Getenv("CS3_GATEWAY"), - os.Getenv("CS3_MACHINE_AUTH_API_KEY"), - config.Settings.Insecure, - ) - if identifierErr != nil { - return nil, fmt.Errorf("failed to create identifier backend: %v", identifierErr) - } - - fullAuthorizationEndpointURL := bootstrap.WithSchemeAndHost(config.AuthorizationEndpointURI, config.IssuerIdentifierURI) - fullSignInFormURL := bootstrap.WithSchemeAndHost(config.SignInFormURI, config.IssuerIdentifierURI) - fullSignedOutEndpointURL := bootstrap.WithSchemeAndHost(config.SignedOutURI, config.IssuerIdentifierURI) - - activeIdentifier, err := identifier.NewIdentifier(&identifier.Config{ - Config: config.Config, - - BaseURI: config.IssuerIdentifierURI, - PathPrefix: bs.MakeURIPath(bootstrap.APITypeSignin, ""), - StaticFolder: config.IdentifierClientPath, - LogonCookieName: "__Secure-KKT", // Kopano-Konnect-Token - ScopesConf: config.IdentifierScopesConf, - WebAppDisabled: config.IdentifierClientDisabled, - - AuthorizationEndpointURI: fullAuthorizationEndpointURL, - SignedOutEndpointURI: fullSignedOutEndpointURL, - - DefaultBannerLogo: config.IdentifierDefaultBannerLogo, - DefaultSignInPageText: config.IdentifierDefaultSignInPageText, - DefaultUsernameHintText: config.IdentifierDefaultUsernameHintText, - UILocales: config.IdentifierUILocales, - - Backend: identifierBackend, - }) - if err != nil { - return nil, fmt.Errorf("failed to create identifier: %v", err) - } - err = activeIdentifier.SetKey(config.EncryptionSecret) - if err != nil { - return nil, fmt.Errorf("invalid --encryption-secret parameter value for identifier: %v", err) - } - - identityManagerConfig := &identity.Config{ - SignInFormURI: fullSignInFormURL, - SignedOutURI: fullSignedOutEndpointURL, - - Logger: logger, - - ScopesSupported: config.Config.AllowedScopes, - } - - identifierIdentityManager := managers.NewIdentifierIdentityManager(identityManagerConfig, activeIdentifier) - logger.Infoln("using identifier backed identity manager") - - return identifierIdentityManager, nil -} diff --git a/extensions/idp/pkg/command/health.go b/extensions/idp/pkg/command/health.go deleted file mode 100644 index 33376cb6c54..00000000000 --- a/extensions/idp/pkg/command/health.go +++ /dev/null @@ -1,57 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/idp/pkg/config" - "github.com/owncloud/ocis/v2/extensions/idp/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/idp/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/idp/pkg/command/root.go b/extensions/idp/pkg/command/root.go deleted file mode 100644 index c09d8042ecc..00000000000 --- a/extensions/idp/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/idp/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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-idp command. -func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "idp", - Usage: "Serve IDP 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 idp command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new idp.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.IDP.Commons = cfg.Commons - return SutureService{ - cfg: cfg.IDP, - } -} - -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/idp/pkg/command/server.go b/extensions/idp/pkg/command/server.go deleted file mode 100644 index 31024e92f23..00000000000 --- a/extensions/idp/pkg/command/server.go +++ /dev/null @@ -1,198 +0,0 @@ -package command - -import ( - "bytes" - "context" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "encoding/pem" - "errors" - "fmt" - "io" - "io/fs" - "os" - "path/filepath" - - "github.com/oklog/run" - "github.com/owncloud/ocis/v2/extensions/idp/pkg/config" - "github.com/owncloud/ocis/v2/extensions/idp/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/idp/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/idp/pkg/metrics" - "github.com/owncloud/ocis/v2/extensions/idp/pkg/server/debug" - "github.com/owncloud/ocis/v2/extensions/idp/pkg/server/http" - "github.com/owncloud/ocis/v2/extensions/idp/pkg/tracing" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - "github.com/urfave/cli/v2" -) - -const _rsaKeySize = 4096 - -// Server is the entrypoint 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) - os.Exit(1) - } - - if cfg.IDP.EncryptionSecretFile != "" { - if err := ensureEncryptionSecretExists(cfg.IDP.EncryptionSecretFile); err != nil { - return err - } - if err := ensureSigningPrivateKeyExists(cfg.IDP.SigningPrivateKeyFiles); err != nil { - return err - } - } - return err - }, - Action: func(c *cli.Context) error { - logger := logging.Configure(cfg.Service.Name, cfg.Log) - err := tracing.Configure(cfg) - if err != nil { - return err - } - var ( - gr = run.Group{} - ctx, cancel = func() (context.Context, context.CancelFunc) { - if cfg.Context == nil { - return context.WithCancel(context.Background()) - } - return context.WithCancel(cfg.Context) - }() - metrics = metrics.New() - ) - - defer cancel() - - metrics.BuildInfo.WithLabelValues(version.GetString()).Set(1) - - { - server, err := http.Server( - http.Logger(logger), - http.Context(ctx), - http.Config(cfg), - http.Metrics(metrics), - ) - - if err != nil { - logger.Info(). - Err(err). - Str("transport", "http"). - Msg("Failed to initialize server") - - return err - } - - gr.Add(func() error { - return server.Run() - }, func(_ error) { - logger.Info(). - Str("transport", "http"). - Msg("Shutting down server") - - cancel() - }) - } - - { - server, err := debug.Server( - debug.Logger(logger), - debug.Context(ctx), - debug.Config(cfg), - ) - - if err != nil { - logger.Info().Err(err).Str("transport", "debug").Msg("Failed to initialize server") - return err - } - - gr.Add(server.ListenAndServe, func(_ error) { - _ = server.Shutdown(ctx) - cancel() - }) - } - - return gr.Run() - }, - } -} - -func ensureEncryptionSecretExists(path string) error { - _, err := os.Stat(path) - if err == nil { - // If the file exists we can just return - return nil - } - if !errors.Is(err, fs.ErrNotExist) { - return err - } - - dir := filepath.Dir(path) - err = os.MkdirAll(dir, 0700) - if err != nil { - return err - } - - f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0600) - if err != nil { - return nil - } - defer f.Close() - - secret := make([]byte, 32) - _, err = rand.Read(secret) - if err != nil { - return err - } - _, err = io.Copy(f, bytes.NewReader(secret)) - if err != nil { - return err - } - - return nil -} - -func ensureSigningPrivateKeyExists(paths []string) error { - for _, path := range paths { - _, err := os.Stat(path) - if err == nil { - // If the file exists we can just return - return nil - } - if !errors.Is(err, fs.ErrNotExist) { - return err - } - - dir := filepath.Dir(path) - err = os.MkdirAll(dir, 0700) - if err != nil { - return err - } - - f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0600) - if err != nil { - return nil - } - defer f.Close() - - pk, err := rsa.GenerateKey(rand.Reader, _rsaKeySize) - if err != nil { - return err - } - - pb := &pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(pk), - } - if err := pem.Encode(f, pb); err != nil { - return err - } - } - return nil -} diff --git a/extensions/idp/pkg/command/version.go b/extensions/idp/pkg/command/version.go deleted file mode 100644 index 64522d25c0a..00000000000 --- a/extensions/idp/pkg/command/version.go +++ /dev/null @@ -1,50 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - tw "github.com/olekukonko/tablewriter" - "github.com/owncloud/ocis/v2/extensions/idp/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.GetString()) - 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/idp/pkg/config/defaults/defaultconfig.go b/extensions/idp/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index 2fd213cdfa4..00000000000 --- a/extensions/idp/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,184 +0,0 @@ -package defaults - -import ( - "path/filepath" - "strings" - - "github.com/owncloud/ocis/v2/extensions/idp/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" -) - -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:9134", - }, - HTTP: config.HTTP{ - Addr: "127.0.0.1:9130", - Root: "/", - Namespace: "com.owncloud.web", - TLSCert: filepath.Join(defaults.BaseDataPath(), "idp", "server.crt"), - TLSKey: filepath.Join(defaults.BaseDataPath(), "idp", "server.key"), - TLS: false, - }, - Reva: &config.Reva{ - Address: "127.0.0.1:9142", - }, - Service: config.Service{ - Name: "idp", - }, - IDP: config.Settings{ - Iss: "https://localhost:9200", - IdentityManager: "ldap", - URIBasePath: "", - SignInURI: "", - SignedOutURI: "", - AuthorizationEndpointURI: "", - EndsessionEndpointURI: "", - Insecure: false, - TrustedProxy: nil, - AllowScope: nil, - AllowClientGuests: false, - AllowDynamicClientRegistration: false, - EncryptionSecretFile: filepath.Join(defaults.BaseDataPath(), "idp", "encryption.key"), - Listen: "", - IdentifierClientDisabled: true, - IdentifierClientPath: filepath.Join(defaults.BaseDataPath(), "idp"), - IdentifierRegistrationConf: filepath.Join(defaults.BaseDataPath(), "idp", "tmp", "identifier-registration.yaml"), - IdentifierScopesConf: "", - IdentifierDefaultBannerLogo: "", - IdentifierDefaultSignInPageText: "", - IdentifierDefaultUsernameHintText: "", - SigningKid: "private-key", - SigningMethod: "PS256", - SigningPrivateKeyFiles: []string{filepath.Join(defaults.BaseDataPath(), "idp", "private-key.pem")}, - ValidationKeysPath: "", - CookieBackendURI: "", - CookieNames: nil, - AccessTokenDurationSeconds: 60 * 60 * 24, // 1 day - IDTokenDurationSeconds: 60 * 60, // 1 hour - RefreshTokenDurationSeconds: 60 * 60 * 24 * 365 * 3, // 1 year - DyamicClientSecretDurationSeconds: 0, - }, - Clients: []config.Client{ - { - ID: "web", - Name: "ownCloud Web app", - Trusted: true, - RedirectURIs: []string{ - "{{OCIS_URL}}/", - "{{OCIS_URL}}/oidc-callback.html", - "{{OCIS_URL}}/oidc-silent-redirect.html", - }, - Origins: []string{ - "{{OCIS_URL}}", - }, - }, - { - ID: "ocis-explorer.js", - Name: "oCIS Graph Explorer", - Trusted: true, - RedirectURIs: []string{ - "{{OCIS_URL}}/graph-explorer/", - }, - Origins: []string{ - "{{OCIS_URL}}", - }, - }, - { - ID: "xdXOt13JKxym1B1QcEncf2XDkLAexMBFwiT9j6EfhhHFJhs2KM9jbjTmf8JBXE69", - Secret: "UBntmLjC2yYCeHwsyj73Uwo9TAaecAetRwMw0xYcvNL9yRdLSUi0hUAHfvCHFeFh", - Name: "ownCloud desktop app", - ApplicationType: "native", - RedirectURIs: []string{ - "http://127.0.0.1", - "http://localhost", - }, - }, - { - ID: "e4rAsNUSIUs0lF4nbv9FmCeUkTlV9GdgTLDH1b5uie7syb90SzEVrbN7HIpmWJeD", - Secret: "dInFYGV33xKzhbRmpqQltYNdfLdJIfJ9L5ISoKhNoT9qZftpdWSP71VrpGR9pmoD", - Name: "ownCloud Android app", - ApplicationType: "native", - RedirectURIs: []string{ - "oc://android.owncloud.com", - }, - }, - { - ID: "mxd5OQDk6es5LzOzRvidJNfXLUZS2oN3oUFeXPP8LpPrhx3UroJFduGEYIBOxkY1", - Secret: "KFeFWWEZO9TkisIQzR3fo7hfiMXlOpaqP8CFuTbSHzV1TUuGECglPxpiVKJfOXIx", - Name: "ownCloud iOS app", - ApplicationType: "native", - RedirectURIs: []string{ - "oc://ios.owncloud.com", - "oc.ios://ios.owncloud.com", - }, - }, - }, - Ldap: config.Ldap{ - URI: "ldaps://localhost:9235", - TLSCACert: filepath.Join(defaults.BaseDataPath(), "idm", "ldap.crt"), - BindDN: "uid=idp,ou=sysusers,o=libregraph-idm", - BaseDN: "ou=users,o=libregraph-idm", - Scope: "sub", - LoginAttribute: "uid", - EmailAttribute: "mail", - NameAttribute: "displayName", - UUIDAttribute: "uid", - UUIDAttributeType: "text", - Filter: "", - ObjectClass: "inetOrgPerson", - }, - } -} - -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.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" { - cfg.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey - } -} - -func Sanitize(cfg *config.Config) { - // sanitize config - if cfg.HTTP.Root != "/" { - cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") - } -} diff --git a/extensions/idp/pkg/config/parser/parse.go b/extensions/idp/pkg/config/parser/parse.go deleted file mode 100644 index 0a978219c58..00000000000 --- a/extensions/idp/pkg/config/parser/parse.go +++ /dev/null @@ -1,49 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/idp/pkg/config" - "github.com/owncloud/ocis/v2/extensions/idp/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - switch cfg.IDP.IdentityManager { - case "cs3": - if cfg.MachineAuthAPIKey == "" { - return shared.MissingMachineAuthApiKeyError(cfg.Service.Name) - } - case "ldap": - if cfg.Ldap.BindPassword == "" { - return shared.MissingLDAPBindPassword(cfg.Service.Name) - } - } - - return nil -} diff --git a/extensions/idp/pkg/logging/logging.go b/extensions/idp/pkg/logging/logging.go deleted file mode 100644 index c52de8351e2..00000000000 --- a/extensions/idp/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/idp/pkg/config" - "github.com/owncloud/ocis/v2/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/idp/pkg/server/debug/option.go b/extensions/idp/pkg/server/debug/option.go deleted file mode 100644 index 5648bbbcf33..00000000000 --- a/extensions/idp/pkg/server/debug/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/idp/pkg/config" - "github.com/owncloud/ocis/v2/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/idp/pkg/server/debug/server.go b/extensions/idp/pkg/server/debug/server.go deleted file mode 100644 index bac512a9c4f..00000000000 --- a/extensions/idp/pkg/server/debug/server.go +++ /dev/null @@ -1,70 +0,0 @@ -package debug - -import ( - "io" - "net/http" - "net/url" - - "github.com/owncloud/ocis/v2/extensions/idp/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" - "github.com/owncloud/ocis/v2/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.GetString()), - 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, options.Logger)), - debug.Ready(ready(options.Config)), - ), nil -} - -// health implements the health check. -func health(cfg *config.Config, l log.Logger) func(http.ResponseWriter, *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - targetHost, err := url.Parse(cfg.Ldap.URI) - if err != nil { - l.Fatal().Err(err).Str("uri", cfg.Ldap.URI).Msg("invalid LDAP URI") - } - err = shared.RunChecklist(shared.TCPConnect(targetHost.Host)) - retVal := http.StatusOK - if err != nil { - l.Error().Err(err).Msg("Healtcheck failed") - retVal = http.StatusInternalServerError - } - w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(retVal) - - _, err = io.WriteString(w, http.StatusText(retVal)) - if err != nil { - l.Fatal().Err(err).Msg("Could not write health check body") - } - } -} - -// 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) - // if we can call this function, a http(200) is a valid response as - // there is nothing we can check at this point for IDP - // if there is a mishap when initializing, there is a minimal (talking ms or ns window) - // timeframe where this code is callable - _, 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/idp/pkg/server/http/option.go b/extensions/idp/pkg/server/http/option.go deleted file mode 100644 index fffc93a8a00..00000000000 --- a/extensions/idp/pkg/server/http/option.go +++ /dev/null @@ -1,76 +0,0 @@ -package http - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/idp/pkg/config" - "github.com/owncloud/ocis/v2/extensions/idp/pkg/metrics" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/urfave/cli/v2" -) - -// Option defines a single option function. -type Option func(o *Options) - -// Options defines the available options for this package. -type Options struct { - Namespace string - Logger log.Logger - Context context.Context - Config *config.Config - Metrics *metrics.Metrics - Flags []cli.Flag -} - -// 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 - } -} - -// Metrics provides a function to set the metrics option. -func Metrics(val *metrics.Metrics) Option { - return func(o *Options) { - o.Metrics = val - } -} - -// Flags provides a function to set the flags option. -func Flags(val []cli.Flag) Option { - return func(o *Options) { - o.Flags = append(o.Flags, val...) - } -} - -// Namespace provides a function to set the namespace option. -func Namespace(val string) Option { - return func(o *Options) { - o.Namespace = val - } -} diff --git a/extensions/idp/pkg/server/http/server.go b/extensions/idp/pkg/server/http/server.go deleted file mode 100644 index e974dc9d415..00000000000 --- a/extensions/idp/pkg/server/http/server.go +++ /dev/null @@ -1,82 +0,0 @@ -package http - -import ( - "crypto/tls" - "os" - - chimiddleware "github.com/go-chi/chi/v5/middleware" - svc "github.com/owncloud/ocis/v2/extensions/idp/pkg/service/v0" - pkgcrypto "github.com/owncloud/ocis/v2/ocis-pkg/crypto" - "github.com/owncloud/ocis/v2/ocis-pkg/middleware" - "github.com/owncloud/ocis/v2/ocis-pkg/service/http" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - "go-micro.dev/v4" -) - -// Server initializes the http service and server. -func Server(opts ...Option) (http.Service, error) { - options := newOptions(opts...) - - var tlsConfig *tls.Config - if options.Config.HTTP.TLS { - _, certErr := os.Stat(options.Config.HTTP.TLSCert) - _, keyErr := os.Stat(options.Config.HTTP.TLSKey) - - if os.IsNotExist(certErr) || os.IsNotExist(keyErr) { - options.Logger.Info().Msgf("Generating certs") - if err := pkgcrypto.GenCert(options.Config.HTTP.TLSCert, options.Config.HTTP.TLSKey, options.Logger); err != nil { - options.Logger.Fatal().Err(err).Msg("Could not setup TLS") - os.Exit(1) - } - } - - cer, err := tls.LoadX509KeyPair(options.Config.HTTP.TLSCert, options.Config.HTTP.TLSKey) - if err != nil { - options.Logger.Fatal().Err(err).Msg("Could not setup TLS") - os.Exit(1) - } - - tlsConfig = &tls.Config{MinVersion: tls.VersionTLS12, Certificates: []tls.Certificate{cer}} - } - - service := http.NewService( - http.Logger(options.Logger), - http.Namespace(options.Config.HTTP.Namespace), - http.Name(options.Config.Service.Name), - http.Version(version.GetString()), - http.Address(options.Config.HTTP.Addr), - http.Context(options.Context), - http.Flags(options.Flags...), - http.TLSConfig(tlsConfig), - ) - - handle := svc.NewService( - svc.Logger(options.Logger), - svc.Config(options.Config), - svc.Middleware( - chimiddleware.RealIP, - chimiddleware.RequestID, - middleware.TraceContext, - middleware.NoCache, - middleware.Secure, - middleware.Version( - options.Config.Service.Name, - version.GetString(), - ), - middleware.Logger( - options.Logger, - ), - ), - ) - - { - handle = svc.NewInstrument(handle, options.Metrics) - handle = svc.NewLoggingHandler(handle, options.Logger) - } - - if err := micro.RegisterHandler(service.Server(), handle); err != nil { - return http.Service{}, err - } - - return service, nil -} diff --git a/extensions/idp/pkg/service/v0/instrument.go b/extensions/idp/pkg/service/v0/instrument.go deleted file mode 100644 index 8c7a948eae4..00000000000 --- a/extensions/idp/pkg/service/v0/instrument.go +++ /dev/null @@ -1,25 +0,0 @@ -package svc - -import ( - "net/http" - - "github.com/owncloud/ocis/v2/extensions/idp/pkg/metrics" -) - -// NewInstrument returns a service that instruments metrics. -func NewInstrument(next Service, metrics *metrics.Metrics) Service { - return instrument{ - next: next, - metrics: metrics, - } -} - -type instrument struct { - next Service - metrics *metrics.Metrics -} - -// ServeHTTP implements the Service interface. -func (i instrument) ServeHTTP(w http.ResponseWriter, r *http.Request) { - i.next.ServeHTTP(w, r) -} diff --git a/extensions/idp/pkg/service/v0/option.go b/extensions/idp/pkg/service/v0/option.go deleted file mode 100644 index d86aa51d330..00000000000 --- a/extensions/idp/pkg/service/v0/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package svc - -import ( - "net/http" - - "github.com/owncloud/ocis/v2/extensions/idp/pkg/config" - "github.com/owncloud/ocis/v2/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 - Config *config.Config - Middleware []func(http.Handler) http.Handler -} - -// 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 - } -} - -// Config provides a function to set the config option. -func Config(val *config.Config) Option { - return func(o *Options) { - o.Config = val - } -} - -// Middleware provides a function to set the middleware option. -func Middleware(val ...func(http.Handler) http.Handler) Option { - return func(o *Options) { - o.Middleware = val - } -} diff --git a/extensions/idp/pkg/service/v0/service.go b/extensions/idp/pkg/service/v0/service.go deleted file mode 100644 index 8f6b8db3602..00000000000 --- a/extensions/idp/pkg/service/v0/service.go +++ /dev/null @@ -1,279 +0,0 @@ -package svc - -import ( - "bytes" - "context" - "fmt" - "io/ioutil" - "net/http" - "os" - "path" - "strings" - - "github.com/go-chi/chi/v5" - "github.com/gorilla/mux" - "github.com/libregraph/lico/bootstrap" - guestBackendSupport "github.com/libregraph/lico/bootstrap/backends/guest" - kcBackendSupport "github.com/libregraph/lico/bootstrap/backends/kc" - ldapBackendSupport "github.com/libregraph/lico/bootstrap/backends/ldap" - libreGraphBackendSupport "github.com/libregraph/lico/bootstrap/backends/libregraph" - licoconfig "github.com/libregraph/lico/config" - "github.com/libregraph/lico/server" - "github.com/owncloud/ocis/v2/extensions/idp/pkg/assets" - cs3BackendSupport "github.com/owncloud/ocis/v2/extensions/idp/pkg/backends/cs3/bootstrap" - "github.com/owncloud/ocis/v2/extensions/idp/pkg/config" - "github.com/owncloud/ocis/v2/extensions/idp/pkg/middleware" - "github.com/owncloud/ocis/v2/ocis-pkg/ldap" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "gopkg.in/yaml.v2" - "stash.kopano.io/kgol/rndm" -) - -// Service defines the extension handlers. -type Service interface { - ServeHTTP(http.ResponseWriter, *http.Request) -} - -// NewService returns a service implementation for Service. -func NewService(opts ...Option) Service { - ctx := context.Background() - options := newOptions(opts...) - logger := options.Logger.Logger - assetVFS := assets.New( - assets.Logger(options.Logger), - assets.Config(options.Config), - ) - - if err := createTemporaryClientsConfig( - options.Config.IDP.IdentifierRegistrationConf, - options.Config.IDP.Iss, - options.Config.Clients, - ); err != nil { - logger.Fatal().Err(err).Msg("could not create default config") - } - - switch options.Config.IDP.IdentityManager { - case "cs3": - cs3BackendSupport.MustRegister() - if err := initCS3EnvVars(options.Config.Reva.Address, options.Config.MachineAuthAPIKey); err != nil { - logger.Fatal().Err(err).Msg("could not initialize cs3 backend env vars") - } - case "ldap": - - if err := ldap.WaitForCA(options.Logger, options.Config.IDP.Insecure, options.Config.Ldap.TLSCACert); err != nil { - logger.Fatal().Err(err).Msg("The configured LDAP CA cert does not exist") - } - if options.Config.IDP.Insecure { - // force CACert to be empty to avoid lico try to load it - options.Config.Ldap.TLSCACert = "" - } - - ldapBackendSupport.MustRegister() - if err := initLicoInternalLDAPEnvVars(&options.Config.Ldap); err != nil { - logger.Fatal().Err(err).Msg("could not initialize ldap env vars") - } - default: - guestBackendSupport.MustRegister() - kcBackendSupport.MustRegister() - libreGraphBackendSupport.MustRegister() - } - - // https://play.golang.org/p/Mh8AVJCd593 - idpSettings := bootstrap.Settings(options.Config.IDP) - bs, err := bootstrap.Boot(ctx, &idpSettings, &licoconfig.Config{ - Logger: log.LogrusWrap(logger), - }) - - if err != nil { - logger.Fatal().Err(err).Msg("could not bootstrap idp") - } - - managers := bs.Managers() - routes := []server.WithRoutes{managers.Must("identity").(server.WithRoutes)} - handlers := managers.Must("handler").(http.Handler) - - svc := IDP{ - logger: options.Logger, - config: options.Config, - assets: assetVFS, - } - - svc.initMux(ctx, routes, handlers, options) - - return svc -} - -type temporaryClientConfig struct { - Clients []config.Client `yaml:"clients"` -} - -func createTemporaryClientsConfig(filePath, ocisURL string, clients []config.Client) error { - - folder := path.Dir(filePath) - if _, err := os.Stat(folder); os.IsNotExist(err) { - if err := os.MkdirAll(folder, 0700); err != nil { - return err - } - } - - for i, client := range clients { - - for i, entry := range client.RedirectURIs { - client.RedirectURIs[i] = strings.ReplaceAll(entry, "{{OCIS_URL}}", strings.TrimRight(ocisURL, "/")) - } - for i, entry := range client.Origins { - client.Origins[i] = strings.ReplaceAll(entry, "{{OCIS_URL}}", strings.TrimRight(ocisURL, "/")) - } - clients[i] = client - } - - c := temporaryClientConfig{ - Clients: clients, - } - - conf, err := yaml.Marshal(c) - if err != nil { - return err - } - - confOnDisk, err := os.Create(filePath) - if err != nil { - return err - } - - defer confOnDisk.Close() - - err = ioutil.WriteFile(filePath, conf, 0600) - if err != nil { - return err - } - - return nil - -} - -// Init cs3 backend vars which are currently not accessible via idp api -func initCS3EnvVars(cs3Addr, machineAuthAPIKey string) error { - var defaults = map[string]string{ - "CS3_GATEWAY": cs3Addr, - "CS3_MACHINE_AUTH_API_KEY": machineAuthAPIKey, - } - - for k, v := range defaults { - if err := os.Setenv(k, v); err != nil { - return fmt.Errorf("could not set cs3 env var %s=%s", k, v) - } - } - - return nil -} - -// Init ldap backend vars which are currently not accessible via idp api -func initLicoInternalLDAPEnvVars(ldap *config.Ldap) error { - filter := fmt.Sprintf("(objectclass=%s)", ldap.ObjectClass) - if ldap.Filter != "" { - filter = fmt.Sprintf("(&%s%s)", ldap.Filter, filter) - } - var defaults = map[string]string{ - "LDAP_URI": ldap.URI, - "LDAP_BINDDN": ldap.BindDN, - "LDAP_BINDPW": ldap.BindPassword, - "LDAP_BASEDN": ldap.BaseDN, - "LDAP_SCOPE": ldap.Scope, - "LDAP_LOGIN_ATTRIBUTE": ldap.LoginAttribute, - "LDAP_EMAIL_ATTRIBUTE": ldap.EmailAttribute, - "LDAP_NAME_ATTRIBUTE": ldap.NameAttribute, - "LDAP_UUID_ATTRIBUTE": ldap.UUIDAttribute, - "LDAP_UUID_ATTRIBUTE_TYPE": ldap.UUIDAttributeType, - "LDAP_FILTER": filter, - } - - if ldap.TLSCACert != "" { - defaults["LDAP_TLS_CACERT"] = ldap.TLSCACert - } - - for k, v := range defaults { - if err := os.Setenv(k, v); err != nil { - return fmt.Errorf("could not set ldap env var %s=%s", k, v) - } - } - - return nil -} - -// IDP defines implements the business logic for Service. -type IDP struct { - logger log.Logger - config *config.Config - mux *chi.Mux - assets http.FileSystem -} - -// initMux initializes the internal idp gorilla mux and mounts it in to a ocis chi-router -func (idp *IDP) initMux(ctx context.Context, r []server.WithRoutes, h http.Handler, options Options) { - gm := mux.NewRouter() - for _, route := range r { - route.AddRoutes(ctx, gm) - } - - // Delegate rest to provider which is also a handler. - if h != nil { - gm.NotFoundHandler = h - } - - idp.mux = chi.NewMux() - idp.mux.Use(options.Middleware...) - - idp.mux.Use(middleware.Static( - "/signin/v1/", - assets.New( - assets.Logger(options.Logger), - assets.Config(options.Config), - ), - )) - - // handle / | index.html with a template that needs to have the BASE_PREFIX replaced - idp.mux.Get("/signin/v1/identifier", idp.Index()) - idp.mux.Get("/signin/v1/identifier/", idp.Index()) - idp.mux.Get("/signin/v1/identifier/index.html", idp.Index()) - - idp.mux.Mount("/", gm) -} - -// ServeHTTP implements the Service interface. -func (idp IDP) ServeHTTP(w http.ResponseWriter, r *http.Request) { - idp.mux.ServeHTTP(w, r) -} - -// Index renders the static html with the -func (idp IDP) Index() http.HandlerFunc { - - f, err := idp.assets.Open("/identifier/index.html") - if err != nil { - idp.logger.Fatal().Err(err).Msg("Could not open index template") - } - - template, err := ioutil.ReadAll(f) - if err != nil { - idp.logger.Fatal().Err(err).Msg("Could not read index template") - } - if err = f.Close(); err != nil { - idp.logger.Fatal().Err(err).Msg("Could not close body") - } - - // TODO add environment variable to make the path prefix configurable - pp := "/signin/v1" - indexHTML := bytes.Replace(template, []byte("__PATH_PREFIX__"), []byte(pp), 1) - - nonce := rndm.GenerateRandomString(32) - indexHTML = bytes.Replace(indexHTML, []byte("__CSP_NONCE__"), []byte(nonce), 1) - - indexHTML = bytes.Replace(indexHTML, []byte("__PASSWORD_RESET_LINK__"), []byte(idp.config.Service.PasswordResetURI), 1) - - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - if _, err := w.Write(indexHTML); err != nil { - idp.logger.Error().Err(err).Msg("could not write to response writer") - } - }) -} diff --git a/extensions/idp/pkg/tracing/tracing.go b/extensions/idp/pkg/tracing/tracing.go deleted file mode 100644 index 43ef5bc0fb2..00000000000 --- a/extensions/idp/pkg/tracing/tracing.go +++ /dev/null @@ -1,23 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/v2/extensions/idp/pkg/config" - pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" - "go.opentelemetry.io/otel/trace" -) - -var ( - // TraceProvider is the global trace provider for the idp service. - TraceProvider = trace.NewNoopTracerProvider() -) - -func Configure(cfg *config.Config) error { - var err error - if cfg.Tracing.Enabled { - if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { - return err - } - } - - return nil -} diff --git a/extensions/nats/cmd/nats/main.go b/extensions/nats/cmd/nats/main.go deleted file mode 100644 index afd94256874..00000000000 --- a/extensions/nats/cmd/nats/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/nats/pkg/command" - "github.com/owncloud/ocis/v2/extensions/nats/pkg/config/defaults" -) - -func main() { - if err := command.Execute(defaults.DefaultConfig()); err != nil { - os.Exit(1) - } -} diff --git a/extensions/nats/pkg/command/health.go b/extensions/nats/pkg/command/health.go deleted file mode 100644 index 6c6a0d817bb..00000000000 --- a/extensions/nats/pkg/command/health.go +++ /dev/null @@ -1,18 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/extensions/nats/pkg/config" - "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", - Action: func(c *cli.Context) error { - // Not implemented - return nil - }, - } -} diff --git a/extensions/nats/pkg/command/root.go b/extensions/nats/pkg/command/root.go deleted file mode 100644 index faa30183915..00000000000 --- a/extensions/nats/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/nats/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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 nats command. -func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "nats", - Usage: "starts nats server", - Commands: GetCommands(cfg), - }) - - cli.HelpFlag = &cli.BoolFlag{ - Name: "help,h", - Usage: "Show the help", - } - - return app.Run(os.Args) -} - -// SutureService allows for the nats command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new nats.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Nats.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Nats, - } -} - -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/nats/pkg/command/server.go b/extensions/nats/pkg/command/server.go deleted file mode 100644 index 9c029815ce1..00000000000 --- a/extensions/nats/pkg/command/server.go +++ /dev/null @@ -1,76 +0,0 @@ -package command - -import ( - "context" - "fmt" - "os" - - "github.com/oklog/run" - - "github.com/owncloud/ocis/v2/extensions/nats/pkg/config" - "github.com/owncloud/ocis/v2/extensions/nats/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/nats/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/nats/pkg/server/nats" - "github.com/urfave/cli/v2" -) - -// Server is the entrypoint 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) - os.Exit(1) - } - return err - }, - Action: func(c *cli.Context) error { - logger := logging.Configure(cfg.Service.Name, cfg.Log) - - 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() - - natsServer, err := nats.NewNATSServer( - ctx, - logging.NewLogWrapper(logger), - nats.Host(cfg.Nats.Host), - nats.Port(cfg.Nats.Port), - nats.ClusterID(cfg.Nats.ClusterID), - nats.StoreDir(cfg.Nats.StoreDir), - ) - if err != nil { - return err - } - - gr.Add(func() error { - err := make(chan error) - select { - case <-ctx.Done(): - return nil - case err <- natsServer.ListenAndServe(): - return <-err - } - - }, func(_ error) { - logger.Info(). - Msg("Shutting down server") - - natsServer.Shutdown() - cancel() - }) - - return gr.Run() - }, - } -} diff --git a/extensions/nats/pkg/command/version.go b/extensions/nats/pkg/command/version.go deleted file mode 100644 index fff10c540fb..00000000000 --- a/extensions/nats/pkg/command/version.go +++ /dev/null @@ -1,19 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/extensions/nats/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 { - // not implemented - return nil - }, - } -} diff --git a/extensions/nats/pkg/config/defaults/defaultconfig.go b/extensions/nats/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index 1766572423b..00000000000 --- a/extensions/nats/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,53 +0,0 @@ -package defaults - -import ( - "path" - - "github.com/owncloud/ocis/v2/extensions/nats/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" -) - -// NOTE: Most of this configuration is not needed to keep it as simple as possible -// TODO: Clean up unneeded configuration - -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:9234", - }, - Service: config.Service{ - Name: "nats", - }, - Nats: config.Nats{ - Host: "127.0.0.1", - Port: 9233, - ClusterID: "ocis-cluster", - StoreDir: path.Join(defaults.BaseDataPath(), "nats"), - }, - } -} - -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{} - } -} - -func Sanitize(cfg *config.Config) { - // nothing to sanitize here atm -} diff --git a/extensions/nats/pkg/config/parser/parse.go b/extensions/nats/pkg/config/parser/parse.go deleted file mode 100644 index 2625948bb86..00000000000 --- a/extensions/nats/pkg/config/parser/parse.go +++ /dev/null @@ -1,37 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/nats/pkg/config" - "github.com/owncloud/ocis/v2/extensions/nats/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - return nil -} diff --git a/extensions/nats/pkg/logging/logging.go b/extensions/nats/pkg/logging/logging.go deleted file mode 100644 index 57b1adac716..00000000000 --- a/extensions/nats/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/nats/pkg/config" - "github.com/owncloud/ocis/v2/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/notifications/cmd/notifications/main.go b/extensions/notifications/cmd/notifications/main.go deleted file mode 100644 index c965086a7a2..00000000000 --- a/extensions/notifications/cmd/notifications/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/notifications/pkg/command" - "github.com/owncloud/ocis/v2/extensions/notifications/pkg/config/defaults" -) - -func main() { - if err := command.Execute(defaults.DefaultConfig()); err != nil { - os.Exit(1) - } -} diff --git a/extensions/notifications/pkg/command/health.go b/extensions/notifications/pkg/command/health.go deleted file mode 100644 index 740dd8af8b8..00000000000 --- a/extensions/notifications/pkg/command/health.go +++ /dev/null @@ -1,18 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/extensions/notifications/pkg/config" - "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", - Action: func(c *cli.Context) error { - // Not implemented - return nil - }, - } -} diff --git a/extensions/notifications/pkg/command/root.go b/extensions/notifications/pkg/command/root.go deleted file mode 100644 index bf90ee56bea..00000000000 --- a/extensions/notifications/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/notifications/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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 notifications command. -func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "notifications", - Usage: "starts notifications service", - Commands: GetCommands(cfg), - }) - - cli.HelpFlag = &cli.BoolFlag{ - Name: "help,h", - Usage: "Show the help", - } - - return app.Run(os.Args) -} - -// SutureService allows for the notifications command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new notifications.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Notifications.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Notifications, - } -} - -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/notifications/pkg/command/server.go b/extensions/notifications/pkg/command/server.go deleted file mode 100644 index 1b9a68f103f..00000000000 --- a/extensions/notifications/pkg/command/server.go +++ /dev/null @@ -1,59 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/cs3org/reva/v2/pkg/events" - "github.com/cs3org/reva/v2/pkg/events/server" - "github.com/go-micro/plugins/v4/events/natsjs" - "github.com/owncloud/ocis/v2/extensions/notifications/pkg/channels" - "github.com/owncloud/ocis/v2/extensions/notifications/pkg/config" - "github.com/owncloud/ocis/v2/extensions/notifications/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/notifications/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/notifications/pkg/service" - "github.com/urfave/cli/v2" -) - -// Server is the entrypoint 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) - os.Exit(1) - } - return err - }, - Action: func(c *cli.Context) error { - logger := logging.Configure(cfg.Service.Name, cfg.Log) - - evs := []events.Unmarshaller{ - events.ShareCreated{}, - } - - evtsCfg := cfg.Notifications.Events - client, err := server.NewNatsStream( - natsjs.Address(evtsCfg.Endpoint), - natsjs.ClusterID(evtsCfg.Cluster), - ) - if err != nil { - return err - } - evts, err := events.Consume(client, evtsCfg.ConsumerGroup, evs...) - if err != nil { - return err - } - channel, err := channels.NewMailChannel(*cfg, logger) - if err != nil { - return err - } - svc := service.NewEventsNotifier(evts, channel, logger) - return svc.Run() - }, - } -} diff --git a/extensions/notifications/pkg/command/version.go b/extensions/notifications/pkg/command/version.go deleted file mode 100644 index d1b55c18fff..00000000000 --- a/extensions/notifications/pkg/command/version.go +++ /dev/null @@ -1,19 +0,0 @@ -package command - -import ( - "github.com/owncloud/ocis/v2/extensions/notifications/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 { - // not implemented - return nil - }, - } -} diff --git a/extensions/notifications/pkg/config/defaults/defaultconfig.go b/extensions/notifications/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index 5f29ea9278a..00000000000 --- a/extensions/notifications/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,61 +0,0 @@ -package defaults - -import ( - "github.com/owncloud/ocis/v2/extensions/notifications/pkg/config" -) - -func FullDefaultConfig() *config.Config { - cfg := DefaultConfig() - EnsureDefaults(cfg) - Sanitize(cfg) - return cfg -} - -// NOTE: Most of this configuration is not needed to keep it as simple as possible -// TODO: Clean up unneeded configuration - -func DefaultConfig() *config.Config { - return &config.Config{ - Debug: config.Debug{ - Addr: "127.0.0.1:9174", - }, - Service: config.Service{ - Name: "notifications", - }, - Notifications: config.Notifications{ - SMTP: config.SMTP{ - Host: "127.0.0.1", - Port: "1025", - Sender: "noreply@example.com", - }, - Events: config.Events{ - Endpoint: "127.0.0.1:9233", - Cluster: "ocis-cluster", - ConsumerGroup: "notifications", - }, - RevaGateway: "127.0.0.1:9142", - }, - } -} - -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{} - } - - if cfg.Notifications.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" { - cfg.Notifications.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey - } -} - -func Sanitize(cfg *config.Config) { - // nothing to sanitize here atm -} diff --git a/extensions/notifications/pkg/config/parser/parse.go b/extensions/notifications/pkg/config/parser/parse.go deleted file mode 100644 index 6bb231a066a..00000000000 --- a/extensions/notifications/pkg/config/parser/parse.go +++ /dev/null @@ -1,42 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/notifications/pkg/config" - "github.com/owncloud/ocis/v2/extensions/notifications/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - if cfg.Notifications.MachineAuthAPIKey == "" { - return shared.MissingMachineAuthApiKeyError(cfg.Service.Name) - } - - return nil -} diff --git a/extensions/notifications/pkg/logging/logging.go b/extensions/notifications/pkg/logging/logging.go deleted file mode 100644 index ed0042a0e89..00000000000 --- a/extensions/notifications/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/notifications/pkg/config" - "github.com/owncloud/ocis/v2/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/notifications/pkg/service/service.go b/extensions/notifications/pkg/service/service.go deleted file mode 100644 index a2d4a7cc7db..00000000000 --- a/extensions/notifications/pkg/service/service.go +++ /dev/null @@ -1,64 +0,0 @@ -package service - -import ( - "os" - "os/signal" - "syscall" - - "github.com/cs3org/reva/v2/pkg/events" - "github.com/owncloud/ocis/v2/extensions/notifications/pkg/channels" - "github.com/owncloud/ocis/v2/ocis-pkg/log" -) - -type Service interface { - Run() error -} - -func NewEventsNotifier(events <-chan interface{}, channel channels.Channel, logger log.Logger) Service { - return eventsNotifier{ - logger: logger, - channel: channel, - events: events, - signals: make(chan os.Signal, 1), - } -} - -type eventsNotifier struct { - logger log.Logger - channel channels.Channel - events <-chan interface{} - signals chan os.Signal -} - -func (s eventsNotifier) Run() error { - signal.Notify(s.signals, syscall.SIGINT, syscall.SIGTERM) - s.logger.Debug(). - Msg("eventsNotifier started") - for { - select { - case evt := <-s.events: - go func() { - switch e := evt.(type) { - case events.ShareCreated: - msg := "You got a share!" - var err error - if e.GranteeUserID != nil { - err = s.channel.SendMessage([]string{e.GranteeUserID.OpaqueId}, msg) - } else if e.GranteeGroupID != nil { - err = s.channel.SendMessageToGroup(e.GranteeGroupID, msg) - } - if err != nil { - s.logger.Error(). - Err(err). - Str("event", "ShareCreated"). - Msg("failed to send a message") - } - } - }() - case <-s.signals: - s.logger.Debug(). - Msg("eventsNotifier stopped") - return nil - } - } -} diff --git a/extensions/ocdav/cmd/ocdav/main.go b/extensions/ocdav/cmd/ocdav/main.go deleted file mode 100644 index 7346590a0bc..00000000000 --- a/extensions/ocdav/cmd/ocdav/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/ocdav/pkg/command" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 128b670aee3..00000000000 --- a/extensions/ocdav/pkg/command/health.go +++ /dev/null @@ -1,57 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/ocdav/pkg/config" - "github.com/owncloud/ocis/v2/extensions/ocdav/pkg/config/parser" - "github.com/owncloud/ocis/v2/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/root.go b/extensions/ocdav/pkg/command/root.go deleted file mode 100644 index 9b2bbf7d9c0..00000000000 --- a/extensions/ocdav/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/ocdav/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 2109e63039d..00000000000 --- a/extensions/ocdav/pkg/command/server.go +++ /dev/null @@ -1,117 +0,0 @@ -package command - -import ( - "context" - "fmt" - "os" - - "github.com/cs3org/reva/v2/pkg/micro/ocdav" - "github.com/oklog/run" - "github.com/owncloud/ocis/v2/extensions/ocdav/pkg/config" - "github.com/owncloud/ocis/v2/extensions/ocdav/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/ocdav/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/ocdav/pkg/server/debug" - "github.com/owncloud/ocis/v2/extensions/ocdav/pkg/tracing" - "github.com/owncloud/ocis/v2/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) - os.Exit(1) - } - 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 { - - opts := []ocdav.Option{ - ocdav.Name(cfg.HTTP.Namespace + "." + cfg.Service.Name), - ocdav.Version(version.GetString()), - 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.ProductName(cfg.Status.ProductName), - ocdav.ProductVersion(cfg.Status.ProductVersion), - ocdav.Product(cfg.Status.Product), - ocdav.Version(cfg.Status.Version), - ocdav.VersionString(cfg.Status.VersionString), - ocdav.Edition(cfg.Status.Edition), - // ocdav.FavoriteManager() // FIXME needs a proper persistence implementation https://github.com/owncloud/ocis/issues/1228 - // ocdav.LockSystem(), // will default to the CS3 lock system - // ocdav.TLSConfig() // tls config for the http server - } - - if cfg.Tracing.Enabled { - opts = append(opts, ocdav.Tracing(cfg.Tracing.Endpoint, cfg.Tracing.Collector)) - } - - s, err := ocdav.Service(opts...) - 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) { - _ = debugServer.Shutdown(ctx) - 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 deleted file mode 100644 index ae2b44f3122..00000000000 --- a/extensions/ocdav/pkg/command/version.go +++ /dev/null @@ -1,50 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - tw "github.com/olekukonko/tablewriter" - "github.com/owncloud/ocis/v2/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.GetString()) - 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/defaults/defaultconfig.go b/extensions/ocdav/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index e41d021b8e3..00000000000 --- a/extensions/ocdav/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,100 +0,0 @@ -package defaults - -import ( - "github.com/owncloud/ocis/v2/extensions/ocdav/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/version" -) - -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:9163", - Token: "", - Pprof: false, - Zpages: false, - }, - HTTP: config.HTTPConfig{ - Addr: "127.0.0.1:0", // :0 to pick any free local port - Namespace: "com.owncloud.web", - Protocol: "tcp", - Prefix: "", - }, - Service: config.Service{ - Name: "ocdav", - }, - Reva: &config.Reva{ - Address: "127.0.0.1:9142", - }, - WebdavNamespace: "/users/{{.Id.OpaqueId}}", - FilesNamespace: "/users/{{.Id.OpaqueId}}", - SharesNamespace: "/Shares", - PublicURL: "https://localhost:9200", - Insecure: false, - Timeout: 84300, - Middleware: config.Middleware{ - Auth: config.Auth{ - CredentialsByUserAgent: map[string]string{}, - }, - }, - Status: config.Status{ - Version: version.Legacy, - VersionString: version.LegacyString, - ProductVersion: version.GetString(), - Product: "Infinite Scale", - ProductName: "Infinite Scale", - Edition: "Community", - }, - } -} - -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/ocdav/pkg/config/parser/parse.go b/extensions/ocdav/pkg/config/parser/parse.go deleted file mode 100644 index bb7822ca943..00000000000 --- a/extensions/ocdav/pkg/config/parser/parse.go +++ /dev/null @@ -1,42 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/ocdav/pkg/config" - "github.com/owncloud/ocis/v2/extensions/ocdav/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - if cfg.TokenManager.JWTSecret == "" { - return shared.MissingJWTTokenError(cfg.Service.Name) - } - - return nil -} diff --git a/extensions/ocdav/pkg/logging/logging.go b/extensions/ocdav/pkg/logging/logging.go deleted file mode 100644 index eedbb81e235..00000000000 --- a/extensions/ocdav/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/ocdav/pkg/config" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index ab52227f956..00000000000 --- a/extensions/ocdav/pkg/server/debug/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/ocdav/pkg/config" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 2ef2c7e600f..00000000000 --- a/extensions/ocdav/pkg/server/debug/server.go +++ /dev/null @@ -1,63 +0,0 @@ -package debug - -import ( - "io" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/ocdav/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/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.GetString()), - 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 deleted file mode 100644 index 334ec29712f..00000000000 --- a/extensions/ocdav/pkg/tracing/tracing.go +++ /dev/null @@ -1,18 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/v2/extensions/ocdav/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/owncloud/ocis/v2/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/cmd/ocs/main.go b/extensions/ocs/cmd/ocs/main.go deleted file mode 100644 index b40fbb05078..00000000000 --- a/extensions/ocs/cmd/ocs/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/command" - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/config/defaults" -) - -func main() { - if err := command.Execute(defaults.DefaultConfig()); err != nil { - os.Exit(1) - } -} diff --git a/extensions/ocs/pkg/command/health.go b/extensions/ocs/pkg/command/health.go deleted file mode 100644 index 125f0d59003..00000000000 --- a/extensions/ocs/pkg/command/health.go +++ /dev/null @@ -1,57 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/config" - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/ocs/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/ocs/pkg/command/root.go b/extensions/ocs/pkg/command/root.go deleted file mode 100644 index 0ab6e615990..00000000000 --- a/extensions/ocs/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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-ocs command. -func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "ocs", - Usage: "Serve OCS 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 ocs command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new ocs.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.OCS.Commons = cfg.Commons - return SutureService{ - cfg: cfg.OCS, - } -} - -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/ocs/pkg/command/server.go b/extensions/ocs/pkg/command/server.go deleted file mode 100644 index ade9fa50d82..00000000000 --- a/extensions/ocs/pkg/command/server.go +++ /dev/null @@ -1,106 +0,0 @@ -package command - -import ( - "context" - "fmt" - "os" - - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/tracing" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - "github.com/oklog/run" - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/config" - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/metrics" - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/server/debug" - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/server/http" - "github.com/urfave/cli/v2" -) - -// Server is the entrypoint 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) - os.Exit(1) - } - return err - }, - Action: func(c *cli.Context) error { - logger := logging.Configure(cfg.Service.Name, cfg.Log) - err := tracing.Configure(cfg) - if err != nil { - return err - } - - var ( - gr = run.Group{} - ctx, cancel = func() (context.Context, context.CancelFunc) { - if cfg.Context == nil { - return context.WithCancel(context.Background()) - } - return context.WithCancel(cfg.Context) - }() - metrics = metrics.New() - ) - - defer cancel() - - metrics.BuildInfo.WithLabelValues(version.GetString()).Set(1) - - { - server, err := http.Server( - http.Logger(logger), - http.Context(ctx), - http.Config(cfg), - http.Metrics(metrics), - ) - - if err != nil { - logger.Info(). - Err(err). - Str("transport", "http"). - Msg("Failed to initialize server") - - return err - } - - gr.Add(func() error { - return server.Run() - }, func(_ error) { - logger.Info(). - Str("transport", "http"). - Msg("Shutting down server") - - cancel() - }) - } - - { - server, err := debug.Server( - debug.Logger(logger), - debug.Context(ctx), - debug.Config(cfg), - ) - - if err != nil { - logger.Info().Err(err).Str("transport", "debug").Msg("Failed to initialize server") - return err - } - - gr.Add(server.ListenAndServe, func(_ error) { - _ = server.Shutdown(ctx) - cancel() - }) - } - - return gr.Run() - }, - } -} diff --git a/extensions/ocs/pkg/command/version.go b/extensions/ocs/pkg/command/version.go deleted file mode 100644 index a49d4ce9449..00000000000 --- a/extensions/ocs/pkg/command/version.go +++ /dev/null @@ -1,50 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - tw "github.com/olekukonko/tablewriter" - "github.com/owncloud/ocis/v2/extensions/ocs/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.GetString()) - 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/ocs/pkg/config/defaults/defaultconfig.go b/extensions/ocs/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index 79d839aee3b..00000000000 --- a/extensions/ocs/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,98 +0,0 @@ -package defaults - -import ( - "strings" - - "github.com/owncloud/ocis/v2/extensions/ocs/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:9114", - Token: "", - Pprof: false, - Zpages: false, - }, - HTTP: config.HTTP{ - Addr: "127.0.0.1:9110", - Root: "/ocs", - Namespace: "com.owncloud.web", - CORS: config.CORS{ - AllowedOrigins: []string{"*"}, - AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, - AllowedHeaders: []string{"Authorization", "Origin", "Content-Type", "Accept", "X-Requested-With"}, - AllowCredentials: true, - }, - }, - Service: config.Service{ - Name: "ocs", - }, - AccountBackend: "cs3", - Reva: &config.Reva{ - Address: "127.0.0.1:9142", - }, - IdentityManagement: config.IdentityManagement{ - Address: "https://localhost:9200", - }, - } -} - -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{} - } - - if cfg.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" { - cfg.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey - } -} - -func Sanitize(cfg *config.Config) { - // sanitize config - if cfg.HTTP.Root != "/" { - cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") - } -} diff --git a/extensions/ocs/pkg/config/parser/parse.go b/extensions/ocs/pkg/config/parser/parse.go deleted file mode 100644 index d9e3c559088..00000000000 --- a/extensions/ocs/pkg/config/parser/parse.go +++ /dev/null @@ -1,47 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/config" - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/config/defaults" - - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - if cfg.TokenManager.JWTSecret == "" { - return shared.MissingJWTTokenError(cfg.Service.Name) - } - - if cfg.MachineAuthAPIKey == "" { - return shared.MissingMachineAuthApiKeyError(cfg.Service.Name) - } - - return nil -} diff --git a/extensions/ocs/pkg/logging/logging.go b/extensions/ocs/pkg/logging/logging.go deleted file mode 100644 index 9be2a6b5ca5..00000000000 --- a/extensions/ocs/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/config" - "github.com/owncloud/ocis/v2/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/ocs/pkg/middleware/requireadmin.go b/extensions/ocs/pkg/middleware/requireadmin.go deleted file mode 100644 index 4e41843c310..00000000000 --- a/extensions/ocs/pkg/middleware/requireadmin.go +++ /dev/null @@ -1,42 +0,0 @@ -package middleware - -import ( - "net/http" - - "github.com/go-chi/render" - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/service/v0/data" - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/service/v0/response" - settings "github.com/owncloud/ocis/v2/extensions/settings/pkg/service/v0" - "github.com/owncloud/ocis/v2/ocis-pkg/roles" -) - -// RequireAdmin middleware is used to require the user in context to be an admin / have account management permissions -func RequireAdmin(opts ...Option) func(next http.Handler) http.Handler { - opt := newOptions(opts...) - - mustRender := func(w http.ResponseWriter, r *http.Request, renderer render.Renderer) { - if err := render.Render(w, r, renderer); err != nil { - opt.Logger.Err(err).Msgf("failed to write response for ocs request %s on %s", r.Method, r.URL) - } - } - - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - - // get roles from context - roleIDs, ok := roles.ReadRoleIDsFromContext(r.Context()) - if !ok { - mustRender(w, r, response.ErrRender(data.MetaUnauthorized.StatusCode, "Unauthorized")) - return - } - - // check if permission is present in roles of the authenticated account - if opt.RoleManager.FindPermissionByID(r.Context(), roleIDs, settings.AccountManagementPermissionID) != nil { - next.ServeHTTP(w, r) - return - } - - mustRender(w, r, response.ErrRender(data.MetaUnauthorized.StatusCode, "Unauthorized")) - }) - } -} diff --git a/extensions/ocs/pkg/server/debug/option.go b/extensions/ocs/pkg/server/debug/option.go deleted file mode 100644 index a89ad2aa8f9..00000000000 --- a/extensions/ocs/pkg/server/debug/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/config" - "github.com/owncloud/ocis/v2/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/ocs/pkg/server/debug/server.go b/extensions/ocs/pkg/server/debug/server.go deleted file mode 100644 index 09c2b80f740..00000000000 --- a/extensions/ocs/pkg/server/debug/server.go +++ /dev/null @@ -1,63 +0,0 @@ -package debug - -import ( - "io" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/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.GetString()), - 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/ocs/pkg/server/http/option.go b/extensions/ocs/pkg/server/http/option.go deleted file mode 100644 index a2e1fc5b399..00000000000 --- a/extensions/ocs/pkg/server/http/option.go +++ /dev/null @@ -1,76 +0,0 @@ -package http - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/config" - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/metrics" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/urfave/cli/v2" -) - -// Option defines a single option function. -type Option func(o *Options) - -// Options defines the available options for this package. -type Options struct { - Namespace string - Logger log.Logger - Context context.Context - Config *config.Config - Metrics *metrics.Metrics - Flags []cli.Flag -} - -// 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 - } -} - -// Metrics provides a function to set the metrics option. -func Metrics(val *metrics.Metrics) Option { - return func(o *Options) { - o.Metrics = val - } -} - -// Flags provides a function to set the flags option. -func Flags(val []cli.Flag) Option { - return func(o *Options) { - o.Flags = append(o.Flags, val...) - } -} - -// Namespace provides a function to set the Namespace option. -func Namespace(val string) Option { - return func(o *Options) { - o.Namespace = val - } -} diff --git a/extensions/ocs/pkg/server/http/server.go b/extensions/ocs/pkg/server/http/server.go deleted file mode 100644 index 47f42247ac8..00000000000 --- a/extensions/ocs/pkg/server/http/server.go +++ /dev/null @@ -1,63 +0,0 @@ -package http - -import ( - chimiddleware "github.com/go-chi/chi/v5/middleware" - ocsmw "github.com/owncloud/ocis/v2/extensions/ocs/pkg/middleware" - svc "github.com/owncloud/ocis/v2/extensions/ocs/pkg/service/v0" - "github.com/owncloud/ocis/v2/ocis-pkg/cors" - "github.com/owncloud/ocis/v2/ocis-pkg/middleware" - "github.com/owncloud/ocis/v2/ocis-pkg/service/http" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - "go-micro.dev/v4" -) - -// Server initializes the http service and server. -func Server(opts ...Option) (http.Service, error) { - options := newOptions(opts...) - - service := http.NewService( - http.Logger(options.Logger), - http.Name(options.Config.Service.Name), - http.Version(version.GetString()), - http.Namespace(options.Config.HTTP.Namespace), - http.Address(options.Config.HTTP.Addr), - http.Context(options.Context), - http.Flags(options.Flags...), - ) - - handle := svc.NewService( - svc.Logger(options.Logger), - svc.Config(options.Config), - svc.Middleware( - chimiddleware.RealIP, - chimiddleware.RequestID, - middleware.NoCache, - middleware.Cors( - cors.Logger(options.Logger), - cors.AllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins), - cors.AllowedMethods(options.Config.HTTP.CORS.AllowedMethods), - cors.AllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders), - cors.AllowCredentials(options.Config.HTTP.CORS.AllowCredentials), - ), - middleware.Secure, - middleware.Version( - options.Config.Service.Name, - version.GetString(), - ), - middleware.Logger(options.Logger), - ocsmw.LogTrace, - ), - ) - - { - handle = svc.NewInstrument(handle, options.Metrics) - handle = svc.NewLogging(handle, options.Logger) - handle = svc.NewTracing(handle) - } - - if err := micro.RegisterHandler(service.Server(), handle); err != nil { - return http.Service{}, err - } - - return service, nil -} diff --git a/extensions/ocs/pkg/service/v0/config.go b/extensions/ocs/pkg/service/v0/config.go deleted file mode 100644 index da4b7bc9ffa..00000000000 --- a/extensions/ocs/pkg/service/v0/config.go +++ /dev/null @@ -1,19 +0,0 @@ -package svc - -import ( - "net/http" - - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/service/v0/data" - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/service/v0/response" -) - -// GetConfig renders the ocs config endpoint -func (o Ocs) GetConfig(w http.ResponseWriter, r *http.Request) { - o.mustRender(w, r, response.DataRender(&data.ConfigData{ - Version: "1.7", // TODO get from env - Website: "ocis", // TODO get from env - Host: "", // TODO get from FRONTEND config - Contact: "", // TODO get from env - SSL: "true", // TODO get from env - })) -} diff --git a/extensions/ocs/pkg/service/v0/groups.go b/extensions/ocs/pkg/service/v0/groups.go deleted file mode 100644 index ce0a658e603..00000000000 --- a/extensions/ocs/pkg/service/v0/groups.go +++ /dev/null @@ -1,98 +0,0 @@ -package svc - -import ( - "net/http" - "net/url" - - "github.com/go-chi/chi/v5" - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/service/v0/data" - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/service/v0/response" -) - -// ListUserGroups lists a users groups -func (o Ocs) ListUserGroups(w http.ResponseWriter, r *http.Request) { - userid := chi.URLParam(r, "userid") - userid, _ = url.PathUnescape(userid) - switch o.config.AccountBackend { - case "cs3": - // TODO - o.mustRender(w, r, response.DataRender(&data.Groups{})) - return - default: - o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) - } - return -} - -// AddToGroup adds a user to a group -func (o Ocs) AddToGroup(w http.ResponseWriter, r *http.Request) { - switch o.config.AccountBackend { - case "cs3": - // TODO - o.cs3WriteNotSupported(w, r) - return - default: - o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) - } -} - -// RemoveFromGroup removes a user from a group -func (o Ocs) RemoveFromGroup(w http.ResponseWriter, r *http.Request) { - switch o.config.AccountBackend { - case "cs3": - // TODO - o.cs3WriteNotSupported(w, r) - return - default: - o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) - } -} - -// ListGroups lists all groups -func (o Ocs) ListGroups(w http.ResponseWriter, r *http.Request) { - switch o.config.AccountBackend { - case "cs3": - // TODO - o.mustRender(w, r, response.DataRender(&data.Groups{})) - return - default: - o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) - } - return -} - -// AddGroup adds a group -// oC10 implementation: https://github.com/owncloud/core/blob/762780a23c9eadda4fb5fa8db99eba66a5100b6e/apps/provisioning_api/lib/Groups.php#L126-L154 -func (o Ocs) AddGroup(w http.ResponseWriter, r *http.Request) { - switch o.config.AccountBackend { - case "cs3": - o.cs3WriteNotSupported(w, r) - return - default: - o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) - } -} - -// DeleteGroup deletes a group -func (o Ocs) DeleteGroup(w http.ResponseWriter, r *http.Request) { - switch o.config.AccountBackend { - case "cs3": - o.cs3WriteNotSupported(w, r) - return - default: - o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) - } -} - -// GetGroupMembers lists all members of a group -func (o Ocs) GetGroupMembers(w http.ResponseWriter, r *http.Request) { - switch o.config.AccountBackend { - case "cs3": - // TODO - o.mustRender(w, r, response.DataRender(&data.Users{})) - return - default: - o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) - } - return -} diff --git a/extensions/ocs/pkg/service/v0/instrument.go b/extensions/ocs/pkg/service/v0/instrument.go deleted file mode 100644 index c1398734393..00000000000 --- a/extensions/ocs/pkg/service/v0/instrument.go +++ /dev/null @@ -1,30 +0,0 @@ -package svc - -import ( - "net/http" - - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/metrics" -) - -// NewInstrument returns a service that instruments metrics. -func NewInstrument(next Service, metrics *metrics.Metrics) Service { - return instrument{ - next: next, - metrics: metrics, - } -} - -type instrument struct { - next Service - metrics *metrics.Metrics -} - -// ServeHTTP implements the Service interface. -func (i instrument) ServeHTTP(w http.ResponseWriter, r *http.Request) { - i.next.ServeHTTP(w, r) -} - -// GetConfig implements the Service interface. -func (i instrument) GetConfig(w http.ResponseWriter, r *http.Request) { - i.next.GetConfig(w, r) -} diff --git a/extensions/ocs/pkg/service/v0/option.go b/extensions/ocs/pkg/service/v0/option.go deleted file mode 100644 index ba4a7b9b3a9..00000000000 --- a/extensions/ocs/pkg/service/v0/option.go +++ /dev/null @@ -1,68 +0,0 @@ -package svc - -import ( - "net/http" - - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/owncloud/ocis/v2/ocis-pkg/roles" - settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" -) - -// 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 - Config *config.Config - Middleware []func(http.Handler) http.Handler - RoleService settingssvc.RoleService - RoleManager *roles.Manager -} - -// 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 - } -} - -// Config provides a function to set the config option. -func Config(val *config.Config) Option { - return func(o *Options) { - o.Config = val - } -} - -// Middleware provides a function to set the middleware option. -func Middleware(val ...func(http.Handler) http.Handler) Option { - return func(o *Options) { - o.Middleware = val - } -} - -// RoleService provides a function to set the RoleService option. -func RoleService(val settingssvc.RoleService) Option { - return func(o *Options) { - o.RoleService = val - } -} - -// RoleManager provides a function to set the RoleManager option. -func RoleManager(val *roles.Manager) Option { - return func(o *Options) { - o.RoleManager = val - } -} diff --git a/extensions/ocs/pkg/service/v0/response/version.go b/extensions/ocs/pkg/service/v0/response/version.go deleted file mode 100644 index fe7f7176067..00000000000 --- a/extensions/ocs/pkg/service/v0/response/version.go +++ /dev/null @@ -1,92 +0,0 @@ -package response - -import ( - "context" - "net/http" - - "github.com/go-chi/chi/v5" - "github.com/go-chi/render" - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/service/v0/data" -) - -type key int - -const ( - apiVersionKey key = iota - ocsVersion1 = "1" - ocsVersion2 = "2" -) - -var ( - defaultStatusCodeMapper = OcsV2StatusCodes -) - -// APIVersion retrieves the api version from the context. -func APIVersion(ctx context.Context) string { - value := ctx.Value(apiVersionKey) - if value != nil { - return value.(string) - } - return "" -} - -// OcsV1StatusCodes returns the http status codes for the OCS API v1. -func OcsV1StatusCodes(meta data.Meta) int { - if meta.StatusCode == data.MetaUnauthorized.StatusCode { - return http.StatusUnauthorized - } - return http.StatusOK -} - -// OcsV2StatusCodes maps the OCS codes to http status codes for the ocs API v2. -// see https://github.com/owncloud/core/blob/c08baf580927ecb8ec179028dda255fdd85b4568/lib/private/legacy/api.php#L528 -// also HTTP status codes for apps are the same as OCS codes -// see https://github.com/owncloud/core/blob/b9ff4c93e051c94adfb301545098ae627e52ef76/lib/public/AppFramework/OCSController.php#L142-L150 -// I think this is a bug in the ocs v2 api, but since we are going to mimic bugs in ocis ... here goes -func OcsV2StatusCodes(meta data.Meta) int { - sc := meta.StatusCode - switch sc { - case data.MetaNotFound.StatusCode: - return http.StatusNotFound - case data.MetaUnknownError.StatusCode: - fallthrough - case data.MetaServerError.StatusCode: - return http.StatusInternalServerError - case data.MetaUnauthorized.StatusCode: - return http.StatusUnauthorized - case data.MetaOK.StatusCode: - // TODO mustn't data.Meta be a pointer so this assignment has an effect - meta.StatusCode = http.StatusOK - return http.StatusOK - } - // any 2xx, 4xx and 5xx will be used as is - if sc >= 200 && sc < 600 { - return sc - } - - // any error codes > 100 are treated as client errors - if sc > 100 && sc < 200 { - return http.StatusBadRequest - } - - // TODO change this status code? yes, align with oc10 core mapStatusCodes - return http.StatusOK -} - -// VersionCtx middleware is used to determine the response mapper from -// the URL parameters passed through as the request. In case -// the Version is unknown, we stop here and return a 404. -func VersionCtx(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - version := chi.URLParam(r, "version") - if version == "" { - _ = render.Render(w, r, ErrRender(data.MetaBadRequest.StatusCode, "unknown ocs api version")) - return - } - w.Header().Set("Ocs-Api-Version", version) - - // store version in context so handlers can access it - ctx := context.WithValue(r.Context(), apiVersionKey, version) - next.ServeHTTP(w, r.WithContext(ctx)) - }) -} diff --git a/extensions/ocs/pkg/service/v0/service.go b/extensions/ocs/pkg/service/v0/service.go deleted file mode 100644 index 1abae288d9b..00000000000 --- a/extensions/ocs/pkg/service/v0/service.go +++ /dev/null @@ -1,133 +0,0 @@ -package svc - -import ( - "net/http" - "time" - - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" - "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" - - "github.com/go-chi/chi/v5" - "github.com/go-chi/chi/v5/middleware" - "github.com/go-chi/render" - - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/config" - ocsm "github.com/owncloud/ocis/v2/extensions/ocs/pkg/middleware" - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/service/v0/data" - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/service/v0/response" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/user/backend" - "github.com/owncloud/ocis/v2/ocis-pkg/account" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - opkgm "github.com/owncloud/ocis/v2/ocis-pkg/middleware" - "github.com/owncloud/ocis/v2/ocis-pkg/roles" - settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" -) - -// Service defines the extension handlers. -type Service interface { - ServeHTTP(http.ResponseWriter, *http.Request) - GetConfig(http.ResponseWriter, *http.Request) -} - -// NewService returns a service implementation for Service. -func NewService(opts ...Option) Service { - options := newOptions(opts...) - - m := chi.NewMux() - m.Use(options.Middleware...) - - roleService := options.RoleService - if roleService == nil { - roleService = settingssvc.NewRoleService("com.owncloud.api.settings", grpc.DefaultClient) - } - roleManager := options.RoleManager - if roleManager == nil { - m := roles.NewManager( - roles.CacheSize(1024), - roles.CacheTTL(time.Hour*24*7), - roles.Logger(options.Logger), - roles.RoleService(roleService), - ) - roleManager = &m - } - - svc := Ocs{ - config: options.Config, - mux: m, - RoleManager: roleManager, - logger: options.Logger, - } - - if svc.config.AccountBackend == "" { - svc.config.AccountBackend = "cs3" - } - - requireUser := ocsm.RequireUser( - ocsm.Logger(options.Logger), - ) - - m.Route(options.Config.HTTP.Root, func(r chi.Router) { - r.NotFound(svc.NotFound) - r.Use(middleware.StripSlashes) - r.Use(opkgm.ExtractAccountUUID( - account.Logger(options.Logger), - account.JWTSecret(options.Config.TokenManager.JWTSecret)), - ) - r.Use(ocsm.OCSFormatCtx) // updates request Accept header according to format=(json|xml) query parameter - r.Route("/v{version:(1|2)}.php", func(r chi.Router) { - r.Use(response.VersionCtx) // stores version in context - r.Route("/apps/files_sharing/api/v1", func(r chi.Router) {}) - r.Route("/apps/notifications/api/v1", func(r chi.Router) {}) - r.Route("/cloud", func(r chi.Router) { - r.Route("/capabilities", func(r chi.Router) {}) - // TODO /apps - r.Route("/user", func(r chi.Router) { - r.Get("/signing-key", svc.GetSigningKey) - }) - }) - r.Route("/config", func(r chi.Router) { - r.With(requireUser).Get("/", svc.GetConfig) - }) - }) - }) - - return svc -} - -// Ocs defines implements the business logic for Service. -type Ocs struct { - config *config.Config - logger log.Logger - RoleService settingssvc.RoleService - RoleManager *roles.Manager - mux *chi.Mux -} - -// ServeHTTP implements the Service interface. -func (o Ocs) ServeHTTP(w http.ResponseWriter, r *http.Request) { - o.mux.ServeHTTP(w, r) -} - -// NotFound uses ErrRender to always return a proper OCS payload -func (o Ocs) NotFound(w http.ResponseWriter, r *http.Request) { - o.mustRender(w, r, response.ErrRender(data.MetaNotFound.StatusCode, "not found")) -} - -func (o Ocs) getCS3Backend() backend.UserBackend { - revaClient, err := pool.GetGatewayServiceClient(o.config.Reva.Address) - if err != nil { - o.logger.Fatal().Msgf("could not get reva client at address %s", o.config.Reva.Address) - } - return backend.NewCS3UserBackend(nil, revaClient, o.config.MachineAuthAPIKey, "", nil, o.logger) -} - -// NotImplementedStub returns a not implemented error -func (o Ocs) NotImplementedStub(w http.ResponseWriter, r *http.Request) { - o.mustRender(w, r, response.ErrRender(data.MetaUnknownError.StatusCode, "Not implemented")) -} - -func (o Ocs) mustRender(w http.ResponseWriter, r *http.Request, renderer render.Renderer) { - if err := render.Render(w, r, renderer); err != nil { - o.logger.Err(err).Msgf("failed to write response for ocs request %s on %s", r.Method, r.URL) - } -} diff --git a/extensions/ocs/pkg/service/v0/users.go b/extensions/ocs/pkg/service/v0/users.go deleted file mode 100644 index 035288b980d..00000000000 --- a/extensions/ocs/pkg/service/v0/users.go +++ /dev/null @@ -1,257 +0,0 @@ -package svc - -import ( - "context" - "crypto/rand" - "encoding/hex" - "net/http" - "net/url" - "strings" - - storemsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/store/v0" - storesvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/store/v0" - - cs3 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - revactx "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/go-chi/chi/v5" - "github.com/go-micro/plugins/v4/client/grpc" - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/service/v0/data" - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/service/v0/response" - ocstracing "github.com/owncloud/ocis/v2/extensions/ocs/pkg/tracing" - merrors "go-micro.dev/v4/errors" -) - -// GetSelf returns the currently logged in user -func (o Ocs) GetSelf(w http.ResponseWriter, r *http.Request) { - u, ok := revactx.ContextGetUser(r.Context()) - if !ok || u.Id == nil || u.Id.OpaqueId == "" { - o.mustRender(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "user is missing an id")) - return - } - d := &data.User{ - UserID: u.Username, - DisplayName: u.DisplayName, - LegacyDisplayName: u.DisplayName, - Email: u.Mail, - UIDNumber: u.UidNumber, - GIDNumber: u.GidNumber, - } - o.mustRender(w, r, response.DataRender(d)) - return -} - -// GetUser returns the user with the given userid -func (o Ocs) GetUser(w http.ResponseWriter, r *http.Request) { - userid := chi.URLParam(r, "userid") - userid, err := url.PathUnescape(userid) - if err != nil { - o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error())) - } - - var user *cs3.User - switch { - case userid == "": - o.mustRender(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "missing user in context")) - case o.config.AccountBackend == "cs3": - user, err = o.fetchAccountFromCS3Backend(r.Context(), userid) - default: - o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) - } - - if err != nil { - merr := merrors.FromError(err) - if merr.Code == http.StatusNotFound { - o.mustRender(w, r, response.ErrRender(data.MetaNotFound.StatusCode, data.MessageUserNotFound)) - } else { - o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error())) - } - o.logger.Error().Err(merr).Str("userid", userid).Msg("could not get account for user") - return - } - - o.logger.Debug().Interface("user", user).Msg("got user") - - d := &data.User{ - UserID: user.Username, - DisplayName: user.DisplayName, - LegacyDisplayName: user.DisplayName, - Email: user.Mail, - UIDNumber: user.UidNumber, - GIDNumber: user.GidNumber, - Enabled: "true", // TODO include in response only when admin? - // TODO query storage registry for free space? of home storage, maybe... - Quota: &data.Quota{ - Free: 2840756224000, - Used: 5059416668, - Total: 2845815640668, - Relative: 0.18, - Definition: "default", - }, - } - - _, span := ocstracing.TraceProvider. - Tracer("ocs"). - Start(r.Context(), "GetUser") - defer span.End() - - o.mustRender(w, r, response.DataRender(d)) -} - -// AddUser creates a new user account -func (o Ocs) AddUser(w http.ResponseWriter, r *http.Request) { - switch o.config.AccountBackend { - case "cs3": - o.cs3WriteNotSupported(w, r) - return - default: - o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) - } -} - -// EditUser creates a new user account -func (o Ocs) EditUser(w http.ResponseWriter, r *http.Request) { - switch o.config.AccountBackend { - case "cs3": - o.cs3WriteNotSupported(w, r) - return - default: - o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) - } -} - -// DeleteUser deletes a user -func (o Ocs) DeleteUser(w http.ResponseWriter, r *http.Request) { - switch o.config.AccountBackend { - case "cs3": - o.cs3WriteNotSupported(w, r) - return - default: - o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) - } -} - -// EnableUser enables a user -func (o Ocs) EnableUser(w http.ResponseWriter, r *http.Request) { - switch o.config.AccountBackend { - case "cs3": - o.cs3WriteNotSupported(w, r) - return - default: - o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) - } -} - -// DisableUser disables a user -func (o Ocs) DisableUser(w http.ResponseWriter, r *http.Request) { - switch o.config.AccountBackend { - case "cs3": - o.cs3WriteNotSupported(w, r) - return - default: - o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) - } -} - -// GetSigningKey returns the signing key for the current user. It will create it on the fly if it does not exist -// The signing key is part of the user settings and is used by the proxy to authenticate requests -// Currently, the username is used as the OC-Credential -func (o Ocs) GetSigningKey(w http.ResponseWriter, r *http.Request) { - u, ok := revactx.ContextGetUser(r.Context()) - if !ok { - //o.logger.Error().Msg("missing user in context") - o.mustRender(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "missing user in context")) - return - } - - // use the user's UUID - userID := u.Id.OpaqueId - - c := storesvc.NewStoreService("com.owncloud.api.store", grpc.NewClient()) - res, err := c.Read(r.Context(), &storesvc.ReadRequest{ - Options: &storemsg.ReadOptions{ - Database: "proxy", - Table: "signing-keys", - }, - Key: userID, - }) - if err == nil && len(res.Records) > 0 { - o.mustRender(w, r, response.DataRender(&data.SigningKey{ - User: userID, - SigningKey: string(res.Records[0].Value), - })) - return - } - if err != nil { - e := merrors.Parse(err.Error()) - if e.Code == http.StatusNotFound { - // not found is ok, so we can continue and generate the key on the fly - } else { - o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, "error reading from store")) - return - } - } - - // try creating it - key := make([]byte, 64) - _, err = rand.Read(key[:]) - if err != nil { - o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, "could not generate signing key")) - return - } - signingKey := hex.EncodeToString(key) - - _, err = c.Write(r.Context(), &storesvc.WriteRequest{ - Options: &storemsg.WriteOptions{ - Database: "proxy", - Table: "signing-keys", - }, - Record: &storemsg.Record{ - Key: userID, - Value: []byte(signingKey), - // TODO Expiry? - }, - }) - - if err != nil { - //o.logger.Error().Err(err).Msg("error writing key") - o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, "could not persist signing key")) - return - } - - o.mustRender(w, r, response.DataRender(&data.SigningKey{ - User: userID, - SigningKey: signingKey, - })) -} - -// ListUsers lists the users -func (o Ocs) ListUsers(w http.ResponseWriter, r *http.Request) { - switch o.config.AccountBackend { - case "cs3": - // TODO - o.cs3WriteNotSupported(w, r) - return - default: - o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) - } -} - -// escapeValue escapes all special characters in the value -func escapeValue(value string) string { - return strings.ReplaceAll(value, "'", "''") -} - -func (o Ocs) fetchAccountFromCS3Backend(ctx context.Context, name string) (*cs3.User, error) { - backend := o.getCS3Backend() - u, _, err := backend.GetUserByClaims(ctx, "username", name, false) - if err != nil { - return nil, err - } - return u, nil -} - -func (o Ocs) cs3WriteNotSupported(w http.ResponseWriter, r *http.Request) { - o.logger.Warn().Msg("the CS3 backend does not support adding or updating users") - o.NotImplementedStub(w, r) - return -} diff --git a/extensions/ocs/pkg/tracing/tracing.go b/extensions/ocs/pkg/tracing/tracing.go deleted file mode 100644 index ab9d2df2bae..00000000000 --- a/extensions/ocs/pkg/tracing/tracing.go +++ /dev/null @@ -1,23 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/config" - pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" - "go.opentelemetry.io/otel/trace" -) - -var ( - // TraceProvider is the global trace provider for the ocs service. - TraceProvider = trace.NewNoopTracerProvider() -) - -func Configure(cfg *config.Config) error { - var err error - if cfg.Tracing.Enabled { - if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { - return err - } - } - - return nil -} diff --git a/extensions/proxy/cmd/proxy/main.go b/extensions/proxy/cmd/proxy/main.go deleted file mode 100644 index b804dd4f8b7..00000000000 --- a/extensions/proxy/cmd/proxy/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/command" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config/defaults" -) - -func main() { - if err := command.Execute(defaults.DefaultConfig()); err != nil { - os.Exit(1) - } -} diff --git a/extensions/proxy/pkg/command/health.go b/extensions/proxy/pkg/command/health.go deleted file mode 100644 index 9b2dd4d9f67..00000000000 --- a/extensions/proxy/pkg/command/health.go +++ /dev/null @@ -1,57 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/proxy/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/proxy/pkg/command/root.go b/extensions/proxy/pkg/command/root.go deleted file mode 100644 index 467a1bfca3a..00000000000 --- a/extensions/proxy/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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-proxy command. -func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "proxy", - Usage: "proxy 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 proxy command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new proxy.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Proxy.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Proxy, - } -} - -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/proxy/pkg/command/server.go b/extensions/proxy/pkg/command/server.go deleted file mode 100644 index 769dba9620d..00000000000 --- a/extensions/proxy/pkg/command/server.go +++ /dev/null @@ -1,233 +0,0 @@ -package command - -import ( - "context" - "crypto/tls" - "fmt" - "net/http" - "os" - "time" - - "github.com/coreos/go-oidc/v3/oidc" - "github.com/cs3org/reva/v2/pkg/token/manager/jwt" - chimiddleware "github.com/go-chi/chi/v5/middleware" - "github.com/justinas/alice" - "github.com/oklog/run" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/cs3" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/metrics" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/middleware" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/proxy" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/server/debug" - proxyHTTP "github.com/owncloud/ocis/v2/extensions/proxy/pkg/server/http" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/tracing" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/user/backend" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - pkgmiddleware "github.com/owncloud/ocis/v2/ocis-pkg/middleware" - "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" - storesvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/store/v0" - "github.com/urfave/cli/v2" - "golang.org/x/oauth2" -) - -// Server is the entrypoint 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) - os.Exit(1) - } - return err - }, - Action: func(c *cli.Context) error { - logger := logging.Configure(cfg.Service.Name, cfg.Log) - err := tracing.Configure(cfg) - if err != nil { - return err - } - - var ( - m = metrics.New() - ) - - 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() - - m.BuildInfo.WithLabelValues(version.GetString()).Set(1) - - rp := proxy.NewMultiHostReverseProxy( - proxy.Logger(logger), - proxy.Config(cfg), - ) - - { - server, err := proxyHTTP.Server( - proxyHTTP.Handler(rp), - proxyHTTP.Logger(logger), - proxyHTTP.Context(ctx), - proxyHTTP.Config(cfg), - proxyHTTP.Metrics(metrics.New()), - proxyHTTP.Middlewares(loadMiddlewares(ctx, logger, cfg)), - ) - - if err != nil { - logger.Error(). - Err(err). - Str("server", "http"). - Msg("Failed to initialize server") - - return err - } - - gr.Add(func() error { - return server.Run() - }, func(_ error) { - logger.Info(). - Str("server", "http"). - Msg("Shutting down server") - - cancel() - }) - } - - { - server, err := debug.Server( - debug.Logger(logger), - debug.Context(ctx), - debug.Config(cfg), - ) - - if err != nil { - logger.Error().Err(err).Str("server", "debug").Msg("Failed to initialize server") - return err - } - - gr.Add(server.ListenAndServe, func(_ error) { - _ = server.Shutdown(ctx) - cancel() - }) - } - - return gr.Run() - }, - } -} - -func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config) alice.Chain { - rolesClient := settingssvc.NewRoleService("com.owncloud.api.settings", grpc.DefaultClient) - revaClient, err := cs3.GetGatewayServiceClient(cfg.Reva.Address) - var userProvider backend.UserBackend - switch cfg.AccountBackend { - case "cs3": - tokenManager, err := jwt.New(map[string]interface{}{ - "secret": cfg.TokenManager.JWTSecret, - }) - if err != nil { - logger.Error().Err(err). - Msg("Failed to create token manager") - } - - userProvider = backend.NewCS3UserBackend(rolesClient, revaClient, cfg.MachineAuthAPIKey, cfg.OIDC.Issuer, tokenManager, logger) - default: - logger.Fatal().Msgf("Invalid accounts backend type '%s'", cfg.AccountBackend) - } - - storeClient := storesvc.NewStoreService("com.owncloud.api.store", grpc.DefaultClient) - if err != nil { - logger.Error().Err(err). - Str("gateway", cfg.Reva.Address). - Msg("Failed to create reva gateway service client") - } - - var oidcHTTPClient = &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: cfg.OIDC.Insecure, //nolint:gosec - }, - DisableKeepAlives: true, - }, - Timeout: time.Second * 10, - } - - return alice.New( - // first make sure we log all requests and redirect to https if necessary - pkgmiddleware.TraceContext, - chimiddleware.RealIP, - chimiddleware.RequestID, - middleware.AccessLog(logger), - middleware.HTTPSRedirect, - - // now that we established the basics, on with authentication middleware - middleware.Authentication( - // OIDC Options - middleware.OIDCProviderFunc(func() (middleware.OIDCProvider, error) { - // Initialize a provider by specifying the issuer URL. - // it will fetch the keys from the issuer using the .well-known - // endpoint - return oidc.NewProvider( - context.WithValue(ctx, oauth2.HTTPClient, oidcHTTPClient), - cfg.OIDC.Issuer, - ) - }), - middleware.HTTPClient(oidcHTTPClient), - middleware.TokenCacheSize(cfg.OIDC.UserinfoCache.Size), - middleware.TokenCacheTTL(time.Second*time.Duration(cfg.OIDC.UserinfoCache.TTL)), - - // basic Options - middleware.Logger(logger), - middleware.EnableBasicAuth(cfg.EnableBasicAuth), - middleware.UserProvider(userProvider), - middleware.OIDCIss(cfg.OIDC.Issuer), - middleware.UserOIDCClaim(cfg.UserOIDCClaim), - middleware.UserCS3Claim(cfg.UserCS3Claim), - middleware.CredentialsByUserAgent(cfg.AuthMiddleware.CredentialsByUserAgent), - ), - middleware.SignedURLAuth( - middleware.Logger(logger), - middleware.PreSignedURLConfig(cfg.PreSignedURL), - middleware.UserProvider(userProvider), - middleware.Store(storeClient), - ), - middleware.AccountResolver( - middleware.Logger(logger), - middleware.UserProvider(userProvider), - middleware.TokenManagerConfig(*cfg.TokenManager), - middleware.UserOIDCClaim(cfg.UserOIDCClaim), - middleware.UserCS3Claim(cfg.UserCS3Claim), - middleware.AutoprovisionAccounts(cfg.AutoprovisionAccounts), - ), - - middleware.SelectorCookie( - middleware.Logger(logger), - middleware.UserProvider(userProvider), - middleware.PolicySelectorConfig(*cfg.PolicySelector), - ), - - // finally, trigger home creation when a user logs in - middleware.CreateHome( - middleware.Logger(logger), - middleware.TokenManagerConfig(*cfg.TokenManager), - middleware.RevaGatewayClient(revaClient), - ), - middleware.PublicShareAuth( - middleware.Logger(logger), - middleware.RevaGatewayClient(revaClient), - ), - ) -} diff --git a/extensions/proxy/pkg/command/version.go b/extensions/proxy/pkg/command/version.go deleted file mode 100644 index e2d1a6eb0e6..00000000000 --- a/extensions/proxy/pkg/command/version.go +++ /dev/null @@ -1,50 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - tw "github.com/olekukonko/tablewriter" - "github.com/owncloud/ocis/v2/extensions/proxy/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: "Version", - Action: func(c *cli.Context) error { - fmt.Println("Version: " + version.GetString()) - 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/proxy/pkg/config/defaults/defaultconfig.go b/extensions/proxy/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index b7f90f8d1fa..00000000000 --- a/extensions/proxy/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,238 +0,0 @@ -package defaults - -import ( - "path" - "strings" - - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" -) - -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:9205", - Token: "", - }, - HTTP: config.HTTP{ - Addr: "0.0.0.0:9200", - Root: "/", - Namespace: "com.owncloud.web", - TLSCert: path.Join(defaults.BaseDataPath(), "proxy", "server.crt"), - TLSKey: path.Join(defaults.BaseDataPath(), "proxy", "server.key"), - TLS: true, - }, - Service: config.Service{ - Name: "proxy", - }, - OIDC: config.OIDC{ - Issuer: "https://localhost:9200", - Insecure: true, - //Insecure: true, - UserinfoCache: config.UserinfoCache{ - Size: 1024, - TTL: 10, - }, - }, - PolicySelector: nil, - Reva: &config.Reva{ - Address: "127.0.0.1:9142", - }, - PreSignedURL: config.PreSignedURL{ - AllowedHTTPMethods: []string{"GET"}, - Enabled: true, - }, - AccountBackend: "cs3", - UserOIDCClaim: "email", - UserCS3Claim: "mail", - AutoprovisionAccounts: false, - EnableBasicAuth: false, - InsecureBackends: false, - } -} - -func DefaultPolicies() []config.Policy { - return []config.Policy{ - { - Name: "ocis", - Routes: []config.Route{ - { - Endpoint: "/", - Backend: "http://localhost:9100", - }, - { - Endpoint: "/.well-known/", - Backend: "http://localhost:9130", - }, - { - Endpoint: "/konnect/", - Backend: "http://localhost:9130", - }, - { - Endpoint: "/signin/", - Backend: "http://localhost:9130", - }, - { - Endpoint: "/archiver", - Backend: "http://localhost:9140", - }, - { - Type: config.RegexRoute, - Endpoint: "/ocs/v[12].php/cloud/user/signing-key", // only `user/signing-key` is left in ocis-ocs - Backend: "http://localhost:9110", - }, - { - Endpoint: "/ocs/", - Backend: "http://localhost:9140", - }, - { - Type: config.QueryRoute, - Endpoint: "/remote.php/?preview=1", - Backend: "http://localhost:9115", - }, - { - // TODO the actual REPORT goes to /dav/files/{username}, which is user specific ... how would this work in a spaces world? - // TODO what paths are returned? the href contains the full path so it should be possible to return urls from other spaces? - // TODO or we allow a REPORT on /dav/spaces to search all spaces and /dav/space/{spaceid} to search a specific space - // send webdav REPORT requests to search service - Method: "REPORT", - Endpoint: "/remote.php/dav/", - Backend: "http://localhost:9115", // TODO use registry? - }, - { - Type: config.QueryRoute, - Endpoint: "/dav/?preview=1", - Backend: "http://localhost:9115", - }, - { - Type: config.QueryRoute, - Endpoint: "/webdav/?preview=1", - Backend: "http://localhost:9115", - }, - { - Endpoint: "/remote.php/", - Service: "com.owncloud.web.ocdav", - }, - { - Endpoint: "/dav/", - Service: "com.owncloud.web.ocdav", - }, - { - Endpoint: "/webdav/", - Service: "com.owncloud.web.ocdav", - }, - { - Endpoint: "/status", - Service: "com.owncloud.web.ocdav", - }, - { - Endpoint: "/status.php", - Service: "com.owncloud.web.ocdav", - }, - { - Endpoint: "/index.php/", - Service: "com.owncloud.web.ocdav", - }, - { - Endpoint: "/apps/", - Service: "com.owncloud.web.ocdav", - }, - { - Endpoint: "/data", - Backend: "http://localhost:9140", - }, - { - Endpoint: "/app/", // /app or /apps? ocdav only handles /apps - Backend: "http://localhost:9140", - }, - { - Endpoint: "/graph/", - Backend: "http://localhost:9120", - }, - { - Endpoint: "/graph-explorer", - Backend: "http://localhost:9135", - }, - { - Endpoint: "/api/v0/settings", - Backend: "http://localhost:9190", - }, - { - Endpoint: "/settings.js", - Backend: "http://localhost:9190", - }, - }, - }, - } -} - -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.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{} - } - - if cfg.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" { - cfg.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey - } - - 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{} - } -} - -func Sanitize(cfg *config.Config) { - // sanitize config - if cfg.Policies == nil { - cfg.Policies = DefaultPolicies() - } - - if cfg.PolicySelector == nil { - cfg.PolicySelector = &config.PolicySelector{ - Static: &config.StaticSelectorConf{ - Policy: "ocis", - }, - } - } - - if cfg.HTTP.Root != "/" { - cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") - } -} diff --git a/extensions/proxy/pkg/config/parser/parse.go b/extensions/proxy/pkg/config/parser/parse.go deleted file mode 100644 index 02f20229bba..00000000000 --- a/extensions/proxy/pkg/config/parser/parse.go +++ /dev/null @@ -1,45 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - if cfg.TokenManager.JWTSecret == "" { - return shared.MissingJWTTokenError(cfg.Service.Name) - } - - if cfg.MachineAuthAPIKey == "" { - return shared.MissingMachineAuthApiKeyError(cfg.Service.Name) - } - - return nil -} diff --git a/extensions/proxy/pkg/logging/logging.go b/extensions/proxy/pkg/logging/logging.go deleted file mode 100644 index c89befa1b58..00000000000 --- a/extensions/proxy/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config" - "github.com/owncloud/ocis/v2/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/proxy/pkg/proxy/option.go b/extensions/proxy/pkg/proxy/option.go deleted file mode 100644 index 13d8b461e24..00000000000 --- a/extensions/proxy/pkg/proxy/option.go +++ /dev/null @@ -1,40 +0,0 @@ -package proxy - -import ( - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config" - "github.com/owncloud/ocis/v2/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 - 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 - } -} - -// 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/proxy/pkg/server/debug/option.go b/extensions/proxy/pkg/server/debug/option.go deleted file mode 100644 index ac8d8dc7e20..00000000000 --- a/extensions/proxy/pkg/server/debug/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config" - "github.com/owncloud/ocis/v2/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/proxy/pkg/server/debug/server.go b/extensions/proxy/pkg/server/debug/server.go deleted file mode 100644 index 02d7d3f0b60..00000000000 --- a/extensions/proxy/pkg/server/debug/server.go +++ /dev/null @@ -1,75 +0,0 @@ -package debug - -import ( - "encoding/json" - "io" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/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.GetString()), - 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.ConfigDump(configDump(options.Config)), - ), 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) - } - } -} - -// configDump implements the config dump -func configDump(cfg *config.Config) func(http.ResponseWriter, *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - - b, err := json.Marshal(cfg) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - } - - _, _ = w.Write(b) - } -} diff --git a/extensions/proxy/pkg/server/http/option.go b/extensions/proxy/pkg/server/http/option.go deleted file mode 100644 index ebe89721b6a..00000000000 --- a/extensions/proxy/pkg/server/http/option.go +++ /dev/null @@ -1,86 +0,0 @@ -package http - -import ( - "context" - "net/http" - - "github.com/justinas/alice" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/metrics" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/urfave/cli/v2" -) - -// 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 - Handler http.Handler - Metrics *metrics.Metrics - Flags []cli.Flag - Middlewares alice.Chain -} - -// 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 - } -} - -// Metrics provides a function to set the metrics option. -func Metrics(val *metrics.Metrics) Option { - return func(o *Options) { - o.Metrics = val - } -} - -// Flags provides a function to set the flags option. -func Flags(val []cli.Flag) Option { - return func(o *Options) { - o.Flags = append(o.Flags, val...) - } -} - -// Handler provides a function to set the Handler option. -func Handler(h http.Handler) Option { - return func(o *Options) { - o.Handler = h - } -} - -// Middlewares provides a function to register middlewares -func Middlewares(val alice.Chain) Option { - return func(o *Options) { - o.Middlewares = val - } -} diff --git a/extensions/proxy/pkg/tracing/tracing.go b/extensions/proxy/pkg/tracing/tracing.go deleted file mode 100644 index 7b6db6d58a1..00000000000 --- a/extensions/proxy/pkg/tracing/tracing.go +++ /dev/null @@ -1,23 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config" - pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" - "go.opentelemetry.io/otel/trace" -) - -var ( - // TraceProvider is the global trace provider for the proxy service. - TraceProvider = trace.NewNoopTracerProvider() -) - -func Configure(cfg *config.Config) error { - var err error - if cfg.Tracing.Enabled { - if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { - return err - } - } - - return nil -} diff --git a/extensions/proxy/pkg/user/backend/cs3.go b/extensions/proxy/pkg/user/backend/cs3.go deleted file mode 100644 index 57fbb552b59..00000000000 --- a/extensions/proxy/pkg/user/backend/cs3.go +++ /dev/null @@ -1,327 +0,0 @@ -package backend - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - - gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" - cs3 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/cs3org/reva/v2/pkg/auth/scope" - revactx "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/cs3org/reva/v2/pkg/token" - libregraph "github.com/owncloud/libre-graph-api-go" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/service/v0/errorcode" - settingsService "github.com/owncloud/ocis/v2/extensions/settings/pkg/service/v0" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/owncloud/ocis/v2/ocis-pkg/oidc" - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" - merrors "go-micro.dev/v4/errors" - "go-micro.dev/v4/selector" -) - -type cs3backend struct { - graphSelector selector.Selector - settingsRoleService settingssvc.RoleService - authProvider RevaAuthenticator - oidcISS string - machineAuthAPIKey string - tokenManager token.Manager - logger log.Logger -} - -// NewCS3UserBackend creates a user-provider which fetches users from a CS3 UserBackend -func NewCS3UserBackend(rs settingssvc.RoleService, ap RevaAuthenticator, machineAuthAPIKey string, oidcISS string, tokenManager token.Manager, logger log.Logger) UserBackend { - reg := registry.GetRegistry() - sel := selector.NewSelector(selector.Registry(reg)) - return &cs3backend{ - graphSelector: sel, - settingsRoleService: rs, - authProvider: ap, - oidcISS: oidcISS, - machineAuthAPIKey: machineAuthAPIKey, - tokenManager: tokenManager, - logger: logger, - } -} - -func (c *cs3backend) GetUserByClaims(ctx context.Context, claim, value string, withRoles bool) (*cs3.User, string, error) { - res, err := c.authProvider.Authenticate(ctx, &gateway.AuthenticateRequest{ - Type: "machine", - ClientId: claim + ":" + value, - ClientSecret: c.machineAuthAPIKey, - }) - - switch { - case err != nil: - return nil, "", fmt.Errorf("could not get user by claim %v with value %v: %w", claim, value, err) - case res.Status.Code != rpcv1beta1.Code_CODE_OK: - if res.Status.Code == rpcv1beta1.Code_CODE_NOT_FOUND { - return nil, "", ErrAccountNotFound - } - return nil, "", fmt.Errorf("could not get user by claim %v with value %v : %w ", claim, value, err) - } - - user := res.User - - if !withRoles { - return user, res.Token, nil - } - - var roleIDs []string - if user.Id.Type != cs3.UserType_USER_TYPE_LIGHTWEIGHT { - roleIDs, err = loadRolesIDs(ctx, user.Id.OpaqueId, c.settingsRoleService) - if err != nil { - var merr *merrors.Error - if errors.As(err, &merr) && merr.Code == http.StatusNotFound { - // This user doesn't have a role assignment yet. Assign a - // default user role. At least until proper roles are provided. See - // https://github.com/owncloud/ocis/v2/issues/1825 for more context. - if user.Id.Type == cs3.UserType_USER_TYPE_PRIMARY { - c.logger.Info().Str("userid", user.Id.OpaqueId).Msg("user has no role assigned, assigning default user role") - _, err := c.settingsRoleService.AssignRoleToUser(ctx, &settingssvc.AssignRoleToUserRequest{ - AccountUuid: user.Id.OpaqueId, - RoleId: settingsService.BundleUUIDRoleUser, - }) - if err != nil { - c.logger.Error().Err(err).Msg("Could not add default role") - return nil, "", err - } - roleIDs = append(roleIDs, settingsService.BundleUUIDRoleUser) - } - } else { - c.logger.Error().Err(err).Msgf("Could not load roles") - return nil, "", err - } - } - } - - enc, err := encodeRoleIDs(roleIDs) - if err != nil { - c.logger.Error().Err(err).Msg("Could not encode loaded roles") - } - - if user.Opaque == nil { - user.Opaque = &types.Opaque{ - Map: map[string]*types.OpaqueEntry{ - "roles": enc, - }, - } - } else { - user.Opaque.Map["roles"] = enc - } - - return user, res.Token, nil -} - -func (c *cs3backend) Authenticate(ctx context.Context, username string, password string) (*cs3.User, string, error) { - res, err := c.authProvider.Authenticate(ctx, &gateway.AuthenticateRequest{ - Type: "basic", - ClientId: username, - ClientSecret: password, - }) - - switch { - case err != nil: - return nil, "", fmt.Errorf("could not authenticate with username and password user: %s, %w", username, err) - case res.Status.Code != rpcv1beta1.Code_CODE_OK: - return nil, "", fmt.Errorf("could not authenticate with username and password user: %s, got code: %d", username, res.Status.Code) - } - - return res.User, res.Token, nil -} - -// CreateUserFromClaims creates a new user via libregraph users API, taking the -// attributes from the provided `claims` map. On success it returns the new -// user. If the user already exist this is not considered an error and the -// function will just return the existing user. -func (c *cs3backend) CreateUserFromClaims(ctx context.Context, claims map[string]interface{}) (*cs3.User, error) { - newctx := context.Background() - token, err := c.generateAutoProvisionAdminToken(newctx) - if err != nil { - c.logger.Error().Err(err).Msg("Error generating token for autoprovisioning user.") - return nil, err - } - lgClient, err := c.setupLibregraphClient(ctx, token) - if err != nil { - c.logger.Error().Err(err).Msg("Error setting up libregraph client.") - return nil, err - } - - newUser, err := c.libregraphUserFromClaims(newctx, claims) - if err != nil { - c.logger.Error().Err(err).Interface("claims", claims).Msg("Error creating user from claims") - return nil, fmt.Errorf("Error creating user from claims: %w", err) - } - - req := lgClient.UsersApi.CreateUser(newctx).User(newUser) - - created, resp, err := req.Execute() - var reread bool - if err != nil { - if resp == nil { - return nil, err - } - - // If the user already exists here, some other request did already create it in parallel. - // So just issue a Debug message and ignore the libregraph error otherwise - var lerr error - if reread, lerr = c.isAlreadyExists(resp); lerr != nil { - c.logger.Error().Err(lerr).Msg("extracting error from ibregraph response body failed.") - return nil, err - } - if !reread { - c.logger.Error().Err(err).Msg("Error creating user") - return nil, err - } - } - - // User has been created meanwhile, re-read it to get the user id - if reread { - c.logger.Debug().Msg("User already exist, re-reading via libregraph") - gureq := lgClient.UserApi.GetUser(newctx, newUser.GetOnPremisesSamAccountName()) - created, resp, err = gureq.Execute() - if err != nil { - c.logger.Error().Err(err).Msg("Error trying to re-read user from graphAPI") - return nil, err - } - } - - cs3UserCreated := c.cs3UserFromLibregraph(newctx, created) - - return &cs3UserCreated, nil -} - -func (c cs3backend) GetUserGroups(ctx context.Context, userID string) { - panic("implement me") -} - -func (c cs3backend) setupLibregraphClient(ctx context.Context, cs3token string) (*libregraph.APIClient, error) { - // Use micro registry to resolve next graph service endpoint - next, err := c.graphSelector.Select("com.owncloud.graph.graph") - if err != nil { - c.logger.Debug().Err(err).Msg("setupLibregraphClient: error during Select") - return nil, err - } - node, err := next() - if err != nil { - c.logger.Debug().Err(err).Msg("setupLibregraphClient: error getting next Node") - return nil, err - } - lgconf := libregraph.NewConfiguration() - lgconf.Servers = libregraph.ServerConfigurations{ - { - URL: fmt.Sprintf("%s://%s/graph/v1.0", node.Metadata["protocol"], node.Address), - }, - } - - lgconf.DefaultHeader = map[string]string{revactx.TokenHeader: cs3token} - return libregraph.NewAPIClient(lgconf), nil -} - -func (c cs3backend) isAlreadyExists(resp *http.Response) (bool, error) { - oDataErr := libregraph.NewOdataErrorWithDefaults() - body, err := io.ReadAll(resp.Body) - if err != nil { - c.logger.Debug().Err(err).Msg("Error trying to read libregraph response") - return false, err - } - err = json.Unmarshal(body, oDataErr) - if err != nil { - c.logger.Debug().Err(err).Msg("Error unmarshalling libregraph response") - return false, err - } - - if oDataErr.Error.Code == errorcode.NameAlreadyExists.String() { - return true, nil - } - return false, nil -} - -func (c cs3backend) libregraphUserFromClaims(ctx context.Context, claims map[string]interface{}) (libregraph.User, error) { - var ok bool - var dn, mail, username string - user := libregraph.User{} - if dn, ok = claims[oidc.Name].(string); !ok { - return user, fmt.Errorf("Missing claim '%s'", oidc.Name) - } - if mail, ok = claims[oidc.Email].(string); !ok { - return user, fmt.Errorf("Missing claim '%s'", oidc.Email) - } - if username, ok = claims[oidc.PreferredUsername].(string); !ok { - c.logger.Warn().Str("claim", oidc.PreferredUsername).Msg("Missing claim for username, falling back to email address") - username = mail - } - user.DisplayName = &dn - user.OnPremisesSamAccountName = &username - user.Mail = &mail - return user, nil -} - -func (c cs3backend) cs3UserFromLibregraph(ctx context.Context, lu *libregraph.User) cs3.User { - cs3id := cs3.UserId{ - Type: cs3.UserType_USER_TYPE_PRIMARY, - Idp: c.oidcISS, - } - - cs3id.OpaqueId = lu.GetId() - - cs3user := cs3.User{ - Id: &cs3id, - } - cs3user.Username = lu.GetOnPremisesSamAccountName() - cs3user.DisplayName = lu.GetDisplayName() - cs3user.Mail = lu.GetMail() - return cs3user -} - -// This returns an hardcoded internal User, that is privileged to create new User via -// the Graph API. This user is needed for autoprovisioning of users from incoming OIDC -// claims. -func getAutoProvisionUserCreator() (*cs3.User, error) { - encRoleID, err := encodeRoleIDs([]string{settingsService.BundleUUIDRoleAdmin}) - if err != nil { - return nil, err - } - - autoProvisionUserCreator := &cs3.User{ - DisplayName: "Autoprovision User", - Username: "autoprovisioner", - Id: &cs3.UserId{ - Idp: "internal", - OpaqueId: "autoprov-user-id00-0000-000000000000", - }, - Opaque: &types.Opaque{ - Map: map[string]*types.OpaqueEntry{ - "roles": encRoleID, - }, - }, - } - return autoProvisionUserCreator, nil -} - -func (c cs3backend) generateAutoProvisionAdminToken(ctx context.Context) (string, error) { - userCreator, err := getAutoProvisionUserCreator() - if err != nil { - return "", err - } - - s, err := scope.AddOwnerScope(nil) - if err != nil { - c.logger.Error().Err(err).Msg("could not get owner scope") - return "", err - } - - token, err := c.tokenManager.MintToken(ctx, userCreator, s) - if err != nil { - c.logger.Error().Err(err).Msg("could not mint token") - return "", err - } - return token, nil -} diff --git a/extensions/search/cmd/search/main.go b/extensions/search/cmd/search/main.go deleted file mode 100644 index c2e4061d8ac..00000000000 --- a/extensions/search/cmd/search/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/search/pkg/command" - "github.com/owncloud/ocis/v2/extensions/search/pkg/config/defaults" -) - -func main() { - if err := command.Execute(defaults.DefaultConfig()); err != nil { - os.Exit(1) - } -} diff --git a/extensions/search/pkg/command/health.go b/extensions/search/pkg/command/health.go deleted file mode 100644 index 9ce5a68dd3c..00000000000 --- a/extensions/search/pkg/command/health.go +++ /dev/null @@ -1,53 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/search/pkg/config" - "github.com/owncloud/ocis/v2/extensions/search/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/search/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 { - return parser.ParseConfig(cfg) - }, - 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/search/pkg/command/root.go b/extensions/search/pkg/command/root.go deleted file mode 100644 index 5bc7db0e429..00000000000 --- a/extensions/search/pkg/command/root.go +++ /dev/null @@ -1,65 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - "github.com/thejerf/suture/v4" - - "github.com/owncloud/ocis/v2/extensions/search/pkg/config" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "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 - Index(cfg), - - // infos about this service - Health(cfg), - Version(cfg), - } -} - -// Execute is the entry point for the ocis-search command. -func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "search", - Usage: "Serve search 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 search command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new search.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Search.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Search, - } -} - -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/search/pkg/command/server.go b/extensions/search/pkg/command/server.go deleted file mode 100644 index 2c72c8e07fa..00000000000 --- a/extensions/search/pkg/command/server.go +++ /dev/null @@ -1,85 +0,0 @@ -package command - -import ( - "context" - "fmt" - "os" - - "github.com/oklog/run" - "github.com/owncloud/ocis/v2/extensions/search/pkg/config" - "github.com/owncloud/ocis/v2/extensions/search/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/search/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/search/pkg/metrics" - "github.com/owncloud/ocis/v2/extensions/search/pkg/server/debug" - "github.com/owncloud/ocis/v2/extensions/search/pkg/server/grpc" - "github.com/owncloud/ocis/v2/extensions/search/pkg/tracing" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - "github.com/urfave/cli/v2" -) - -// Server is the entrypoint 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) - os.Exit(1) - } - return err - }, - Action: func(c *cli.Context) error { - logger := logging.Configure(cfg.Service.Name, cfg.Log) - err := tracing.Configure(cfg) - if err != nil { - return err - } - - 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() - - mtrcs := metrics.New() - mtrcs.BuildInfo.WithLabelValues(version.GetString()).Set(1) - - grpcServer := grpc.Server( - grpc.Config(cfg), - grpc.Logger(logger), - grpc.Name(cfg.Service.Name), - grpc.Context(ctx), - grpc.Metrics(mtrcs), - ) - - gr.Add(grpcServer.Run, func(_ error) { - logger.Info().Str("server", "grpc").Msg("shutting down server") - cancel() - }) - - server, err := debug.Server( - debug.Logger(logger), - debug.Context(ctx), - debug.Config(cfg), - ) - - if err != nil { - logger.Info().Err(err).Str("transport", "debug").Msg("Failed to initialize server") - return err - } - - gr.Add(server.ListenAndServe, func(_ error) { - _ = server.Shutdown(ctx) - cancel() - }) - - return gr.Run() - }, - } -} diff --git a/extensions/search/pkg/command/version.go b/extensions/search/pkg/command/version.go deleted file mode 100644 index ac5ca55abda..00000000000 --- a/extensions/search/pkg/command/version.go +++ /dev/null @@ -1,50 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - tw "github.com/olekukonko/tablewriter" - "github.com/owncloud/ocis/v2/extensions/search/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.GetString()) - 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/search/pkg/config/defaults/defaultconfig.go b/extensions/search/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index 8eed2a21665..00000000000 --- a/extensions/search/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,75 +0,0 @@ -package defaults - -import ( - "path" - - "github.com/owncloud/ocis/v2/extensions/search/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" -) - -func FullDefaultConfig() *config.Config { - cfg := DefaultConfig() - - EnsureDefaults(cfg) - - return cfg -} - -func DefaultConfig() *config.Config { - return &config.Config{ - Debug: config.Debug{ - Addr: "127.0.0.1:9224", - Token: "", - }, - GRPC: config.GRPC{ - Addr: "127.0.0.1:9220", - Namespace: "com.owncloud.api", - }, - Service: config.Service{ - Name: "search", - }, - Datapath: path.Join(defaults.BaseDataPath(), "search"), - Reva: config.Reva{ - Address: "127.0.0.1:9142", - }, - Events: config.Events{ - Endpoint: "127.0.0.1:9233", - Cluster: "ocis-cluster", - ConsumerGroup: "search", - }, - MachineAuthAPIKey: "", - } -} - -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.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" { - cfg.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey - } -} - -func Sanitize(cfg *config.Config) { - // no http endpoint to be sanitized -} diff --git a/extensions/search/pkg/config/parser/parse.go b/extensions/search/pkg/config/parser/parse.go deleted file mode 100644 index 8be7eb621bf..00000000000 --- a/extensions/search/pkg/config/parser/parse.go +++ /dev/null @@ -1,41 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/search/pkg/config" - "github.com/owncloud/ocis/v2/extensions/search/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - if cfg.MachineAuthAPIKey == "" { - return shared.MissingMachineAuthApiKeyError(cfg.Service.Name) - } - return nil -} diff --git a/extensions/search/pkg/logging/logging.go b/extensions/search/pkg/logging/logging.go deleted file mode 100644 index 3fff7ec87ad..00000000000 --- a/extensions/search/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/search/pkg/config" - "github.com/owncloud/ocis/v2/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/search/pkg/server/debug/option.go b/extensions/search/pkg/server/debug/option.go deleted file mode 100644 index 5b18313cec4..00000000000 --- a/extensions/search/pkg/server/debug/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/search/pkg/config" - "github.com/owncloud/ocis/v2/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/search/pkg/server/debug/server.go b/extensions/search/pkg/server/debug/server.go deleted file mode 100644 index 43d2caeee6c..00000000000 --- a/extensions/search/pkg/server/debug/server.go +++ /dev/null @@ -1,59 +0,0 @@ -package debug - -import ( - "io" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/search/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/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.GetString()), - 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)), - ), 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/search/pkg/server/grpc/option.go b/extensions/search/pkg/server/grpc/option.go deleted file mode 100644 index ddd6eab8887..00000000000 --- a/extensions/search/pkg/server/grpc/option.go +++ /dev/null @@ -1,85 +0,0 @@ -package grpc - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/search/pkg/config" - "github.com/owncloud/ocis/v2/extensions/search/pkg/metrics" - svc "github.com/owncloud/ocis/v2/extensions/search/pkg/service/v0" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/urfave/cli/v2" -) - -// Option defines a single option function. -type Option func(o *Options) - -// Options defines the available options for this package. -type Options struct { - Name string - Logger log.Logger - Context context.Context - Config *config.Config - Metrics *metrics.Metrics - Flags []cli.Flag - Handler *svc.Service -} - -// newOptions initializes the available default options. -func newOptions(opts ...Option) Options { - opt := Options{} - - for _, o := range opts { - o(&opt) - } - - return opt -} - -// Name provides a name for the service. -func Name(val string) Option { - return func(o *Options) { - o.Name = val - } -} - -// 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 - } -} - -// Metrics provides a function to set the metrics option. -func Metrics(val *metrics.Metrics) Option { - return func(o *Options) { - o.Metrics = val - } -} - -// Flags provides a function to set the flags option. -func Flags(val []cli.Flag) Option { - return func(o *Options) { - o.Flags = append(o.Flags, val...) - } -} - -// Handler provides a function to set the handler option. -func Handler(val *svc.Service) Option { - return func(o *Options) { - o.Handler = val - } -} diff --git a/extensions/search/pkg/server/grpc/server.go b/extensions/search/pkg/server/grpc/server.go deleted file mode 100644 index a604223d885..00000000000 --- a/extensions/search/pkg/server/grpc/server.go +++ /dev/null @@ -1,39 +0,0 @@ -package grpc - -import ( - svc "github.com/owncloud/ocis/v2/extensions/search/pkg/service/v0" - "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - searchsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/search/v0" -) - -// Server initializes a new go-micro service ready to run -func Server(opts ...Option) grpc.Service { - options := newOptions(opts...) - - service := grpc.NewService( - grpc.Name(options.Config.Service.Name), - grpc.Context(options.Context), - grpc.Address(options.Config.GRPC.Addr), - grpc.Namespace(options.Config.GRPC.Namespace), - grpc.Logger(options.Logger), - grpc.Flags(options.Flags...), - grpc.Version(version.GetString()), - ) - - handle, err := svc.NewHandler( - svc.Config(options.Config), - svc.Logger(options.Logger), - ) - if err != nil { - options.Logger.Error(). - Err(err). - Msg("Error initializing search service") - return grpc.Service{} - } - _ = searchsvc.RegisterSearchProviderHandler( - service.Server(), - handle, - ) - return service -} diff --git a/extensions/search/pkg/service/v0/option.go b/extensions/search/pkg/service/v0/option.go deleted file mode 100644 index cb4e4bdca62..00000000000 --- a/extensions/search/pkg/service/v0/option.go +++ /dev/null @@ -1,39 +0,0 @@ -package service - -import ( - "github.com/owncloud/ocis/v2/extensions/search/pkg/config" - "github.com/owncloud/ocis/v2/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 - Config *config.Config -} - -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 - } -} - -// 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/search/pkg/service/v0/service.go b/extensions/search/pkg/service/v0/service.go deleted file mode 100644 index 235b5610191..00000000000 --- a/extensions/search/pkg/service/v0/service.go +++ /dev/null @@ -1,109 +0,0 @@ -package service - -import ( - "context" - "errors" - "path/filepath" - - "github.com/blevesearch/bleve/v2" - revactx "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/cs3org/reva/v2/pkg/events" - "github.com/cs3org/reva/v2/pkg/events/server" - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" - "github.com/go-micro/plugins/v4/events/natsjs" - "go-micro.dev/v4/metadata" - grpcmetadata "google.golang.org/grpc/metadata" - - "github.com/owncloud/ocis/v2/extensions/search/pkg/config" - "github.com/owncloud/ocis/v2/extensions/search/pkg/search" - "github.com/owncloud/ocis/v2/extensions/search/pkg/search/index" - searchprovider "github.com/owncloud/ocis/v2/extensions/search/pkg/search/provider" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - searchsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/search/v0" -) - -// NewHandler returns a service implementation for Service. -func NewHandler(opts ...Option) (searchsvc.SearchProviderHandler, error) { - options := newOptions(opts...) - logger := options.Logger - cfg := options.Config - - // Connect to nats to listen for changes that need to trigger an index update - evtsCfg := cfg.Events - client, err := server.NewNatsStream( - natsjs.Address(evtsCfg.Endpoint), - natsjs.ClusterID(evtsCfg.Cluster), - ) - if err != nil { - return nil, err - } - evts, err := events.Consume(client, evtsCfg.ConsumerGroup, searchprovider.ListenEvents...) - if err != nil { - return nil, err - } - - indexDir := filepath.Join(cfg.Datapath, "index.bleve") - bleveIndex, err := bleve.Open(indexDir) - if err != nil { - mapping, err := index.BuildMapping() - if err != nil { - return nil, err - } - bleveIndex, err = bleve.New(indexDir, mapping) - if err != nil { - return nil, err - } - } - index, err := index.New(bleveIndex) - if err != nil { - return nil, err - } - - gwclient, err := pool.GetGatewayServiceClient(cfg.Reva.Address) - if err != nil { - logger.Fatal().Err(err).Str("addr", cfg.Reva.Address).Msg("could not get reva client") - } - - provider := searchprovider.New(gwclient, index, cfg.MachineAuthAPIKey, evts, logger) - - return &Service{ - id: cfg.GRPC.Namespace + "." + cfg.Service.Name, - log: logger, - Config: cfg, - provider: provider, - }, nil -} - -// Service implements the searchServiceHandler interface -type Service struct { - id string - log log.Logger - Config *config.Config - provider search.ProviderClient -} - -func (s Service) Search(ctx context.Context, in *searchsvc.SearchRequest, out *searchsvc.SearchResponse) error { - // Get token from the context (go-micro) and make it known to the reva client too (grpc) - t, ok := metadata.Get(ctx, revactx.TokenHeader) - if !ok { - s.log.Error().Msg("Could not get token from context") - return errors.New("could not get token from context") - } - ctx = grpcmetadata.AppendToOutgoingContext(ctx, revactx.TokenHeader, t) - - res, err := s.provider.Search(ctx, &searchsvc.SearchRequest{ - Query: in.Query, - }) - if err != nil { - return err - } - - out.Matches = res.Matches - out.NextPageToken = res.NextPageToken - return nil -} - -func (s Service) IndexSpace(ctx context.Context, in *searchsvc.IndexSpaceRequest, out *searchsvc.IndexSpaceResponse) error { - _, err := s.provider.IndexSpace(ctx, in) - return err -} diff --git a/extensions/search/pkg/tracing/tracing.go b/extensions/search/pkg/tracing/tracing.go deleted file mode 100644 index fda0ae37342..00000000000 --- a/extensions/search/pkg/tracing/tracing.go +++ /dev/null @@ -1,23 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/v2/extensions/search/pkg/config" - pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" - "go.opentelemetry.io/otel/trace" -) - -var ( - // TraceProvider is the global trace provider for the proxy service. - TraceProvider = trace.NewNoopTracerProvider() -) - -func Configure(cfg *config.Config) error { - var err error - if cfg.Tracing.Enabled { - if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { - return err - } - } - - return nil -} diff --git a/extensions/settings/cmd/settings/main.go b/extensions/settings/cmd/settings/main.go deleted file mode 100644 index 89d04ef65d8..00000000000 --- a/extensions/settings/cmd/settings/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/settings/pkg/command" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/config/defaults" -) - -func main() { - if err := command.Execute(defaults.DefaultConfig()); err != nil { - os.Exit(1) - } -} diff --git a/extensions/settings/pkg/assets/option.go b/extensions/settings/pkg/assets/option.go deleted file mode 100644 index 0ea16cb54ce..00000000000 --- a/extensions/settings/pkg/assets/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package assets - -import ( - "net/http" - - "github.com/owncloud/ocis/v2/extensions/settings" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/assetsfs" - "github.com/owncloud/ocis/v2/ocis-pkg/log" -) - -// New returns a new http filesystem to serve assets. -func New(opts ...Option) http.FileSystem { - options := newOptions(opts...) - return assetsfs.New(settings.Assets, options.Config.Asset.Path, options.Logger) -} - -// 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 - 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 - } -} - -// 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/settings/pkg/command/health.go b/extensions/settings/pkg/command/health.go deleted file mode 100644 index e0e7397ef4d..00000000000 --- a/extensions/settings/pkg/command/health.go +++ /dev/null @@ -1,56 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/settings/pkg/config" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/settings/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", - 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/settings/pkg/command/root.go b/extensions/settings/pkg/command/root.go deleted file mode 100644 index 152045ecd88..00000000000 --- a/extensions/settings/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/settings/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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-settings command. -func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "settings", - Usage: "Provide settings and permissions 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 settings command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new settings.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Settings.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Settings, - } -} - -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/settings/pkg/command/server.go b/extensions/settings/pkg/command/server.go deleted file mode 100644 index 26f863b18e3..00000000000 --- a/extensions/settings/pkg/command/server.go +++ /dev/null @@ -1,89 +0,0 @@ -package command - -import ( - "context" - "fmt" - "os" - - "github.com/oklog/run" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/config" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/metrics" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/server/debug" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/server/grpc" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/server/http" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/tracing" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - "github.com/urfave/cli/v2" -) - -// Server is the entrypoint 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) - os.Exit(1) - } - return err - }, - Action: func(c *cli.Context) error { - logger := logging.Configure(cfg.Service.Name, cfg.Log) - err := tracing.Configure(cfg) - if err != nil { - return err - } - - servers := 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() - - mtrcs := metrics.New() - mtrcs.BuildInfo.WithLabelValues(version.GetString()).Set(1) - - // prepare an HTTP server and add it to the group run. - httpServer := http.Server( - http.Name(cfg.Service.Name), - http.Logger(logger), - http.Context(ctx), - http.Config(cfg), - http.Metrics(mtrcs), - ) - servers.Add(httpServer.Run, func(_ error) { - logger.Info().Str("server", "http").Msg("Shutting down server") - cancel() - }) - - // prepare a gRPC server and add it to the group run. - grpcServer := grpc.Server(grpc.Name(cfg.Service.Name), grpc.Logger(logger), grpc.Context(ctx), grpc.Config(cfg), grpc.Metrics(mtrcs)) - servers.Add(grpcServer.Run, func(_ error) { - logger.Info().Str("server", "grpc").Msg("Shutting down server") - cancel() - }) - - // prepare a debug server and add it to the group run. - debugServer, err := debug.Server(debug.Logger(logger), debug.Context(ctx), debug.Config(cfg)) - if err != nil { - logger.Error().Err(err).Str("server", "debug").Msg("Failed to initialize server") - return err - } - - servers.Add(debugServer.ListenAndServe, func(_ error) { - _ = debugServer.Shutdown(ctx) - cancel() - }) - - return servers.Run() - }, - } -} diff --git a/extensions/settings/pkg/command/version.go b/extensions/settings/pkg/command/version.go deleted file mode 100644 index d496dadeac0..00000000000 --- a/extensions/settings/pkg/command/version.go +++ /dev/null @@ -1,50 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - tw "github.com/olekukonko/tablewriter" - "github.com/owncloud/ocis/v2/extensions/settings/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.GetString()) - 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/settings/pkg/config/defaults/defaultconfig.go b/extensions/settings/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index 66ec6ceb3c2..00000000000 --- a/extensions/settings/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,110 +0,0 @@ -package defaults - -import ( - "path" - "strings" - - "github.com/owncloud/ocis/v2/extensions/settings/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" -) - -func FullDefaultConfig() *config.Config { - cfg := DefaultConfig() - EnsureDefaults(cfg) - Sanitize(cfg) - return cfg -} - -// DefaultConfig returns the default config -func DefaultConfig() *config.Config { - return &config.Config{ - Service: config.Service{ - Name: "settings", - }, - Debug: config.Debug{ - Addr: "127.0.0.1:9194", - Token: "", - Pprof: false, - Zpages: false, - }, - HTTP: config.HTTP{ - Addr: "127.0.0.1:9190", - Namespace: "com.owncloud.web", - Root: "/", - CacheTTL: 604800, // 7 days - CORS: config.CORS{ - AllowedOrigins: []string{"*"}, - AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, - AllowedHeaders: []string{"Authorization", "Origin", "Content-Type", "Accept", "X-Requested-With"}, - AllowCredentials: true, - }, - }, - GRPC: config.GRPC{ - Addr: "127.0.0.1:9191", - Namespace: "com.owncloud.api", - }, - StoreType: "metadata", // use metadata or filesystem - DataPath: path.Join(defaults.BaseDataPath(), "settings"), - Asset: config.Asset{ - Path: "", - }, - SetupDefaultAssignments: false, - Metadata: config.Metadata{ - GatewayAddress: "127.0.0.1:9215", // system storage - StorageAddress: "127.0.0.1:9215", - SystemUserIDP: "internal", - }, - } -} - -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.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{} - } - - if cfg.Metadata.SystemUserAPIKey == "" && cfg.Commons != nil && cfg.Commons.SystemUserAPIKey != "" { - cfg.Metadata.SystemUserAPIKey = cfg.Commons.SystemUserAPIKey - } - - if cfg.Metadata.SystemUserID == "" && cfg.Commons != nil && cfg.Commons.SystemUserID != "" { - cfg.Metadata.SystemUserID = cfg.Commons.SystemUserID - } - - if cfg.AdminUserID == "" && cfg.Commons != nil { - cfg.AdminUserID = cfg.Commons.AdminUserID - } -} - -func Sanitize(cfg *config.Config) { - // sanitize config - if cfg.HTTP.Root != "/" { - cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") - } -} diff --git a/extensions/settings/pkg/config/parser/parse.go b/extensions/settings/pkg/config/parser/parse.go deleted file mode 100644 index acf9626093a..00000000000 --- a/extensions/settings/pkg/config/parser/parse.go +++ /dev/null @@ -1,49 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/settings/pkg/config" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - if cfg.TokenManager.JWTSecret == "" { - return shared.MissingJWTTokenError(cfg.Service.Name) - } - - if cfg.Metadata.SystemUserAPIKey == "" { - return shared.MissingSystemUserApiKeyError(cfg.Service.Name) - } - - if cfg.AdminUserID == "" { - return shared.MissingAdminUserID(cfg.Service.Name) - } - - return nil -} diff --git a/extensions/settings/pkg/logging/logging.go b/extensions/settings/pkg/logging/logging.go deleted file mode 100644 index d8e711fc67f..00000000000 --- a/extensions/settings/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/settings/pkg/config" - "github.com/owncloud/ocis/v2/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/settings/pkg/server/debug/option.go b/extensions/settings/pkg/server/debug/option.go deleted file mode 100644 index 688d997d574..00000000000 --- a/extensions/settings/pkg/server/debug/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/settings/pkg/config" - "github.com/owncloud/ocis/v2/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/settings/pkg/server/debug/server.go b/extensions/settings/pkg/server/debug/server.go deleted file mode 100644 index 6a46abc3b8e..00000000000 --- a/extensions/settings/pkg/server/debug/server.go +++ /dev/null @@ -1,63 +0,0 @@ -package debug - -import ( - "io" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/settings/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/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.GetString()), - 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/settings/pkg/server/grpc/option.go b/extensions/settings/pkg/server/grpc/option.go deleted file mode 100644 index 6268f4be93f..00000000000 --- a/extensions/settings/pkg/server/grpc/option.go +++ /dev/null @@ -1,76 +0,0 @@ -package grpc - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/settings/pkg/config" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/metrics" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/urfave/cli/v2" -) - -// Option defines a single option function. -type Option func(o *Options) - -// Options defines the available options for this package. -type Options struct { - Name string - Logger log.Logger - Context context.Context - Config *config.Config - Metrics *metrics.Metrics - Flags []cli.Flag -} - -// 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 - } -} - -// Name provides a name for the service. -func Name(val string) Option { - return func(o *Options) { - o.Name = 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 - } -} - -// Metrics provides a function to set the metrics option. -func Metrics(val *metrics.Metrics) Option { - return func(o *Options) { - o.Metrics = val - } -} - -// Flags provides a function to set the flags option. -func Flags(val []cli.Flag) Option { - return func(o *Options) { - o.Flags = append(o.Flags, val...) - } -} diff --git a/extensions/settings/pkg/server/grpc/server.go b/extensions/settings/pkg/server/grpc/server.go deleted file mode 100644 index 7d2469606dd..00000000000 --- a/extensions/settings/pkg/server/grpc/server.go +++ /dev/null @@ -1,78 +0,0 @@ -package grpc - -import ( - "context" - - permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" - svc "github.com/owncloud/ocis/v2/extensions/settings/pkg/service/v0" - "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" - "go-micro.dev/v4/api" - "go-micro.dev/v4/server" -) - -// Server initializes a new go-micro service ready to run -func Server(opts ...Option) grpc.Service { - options := newOptions(opts...) - - service := grpc.NewService( - grpc.Logger(options.Logger), - grpc.Name(options.Name), - grpc.Version(version.GetString()), - grpc.Address(options.Config.GRPC.Addr), - grpc.Namespace(options.Config.GRPC.Namespace), - grpc.Context(options.Context), - grpc.Flags(options.Flags...), - ) - - handle := svc.NewService(options.Config, options.Logger) - if err := settingssvc.RegisterBundleServiceHandler(service.Server(), handle); err != nil { - options.Logger.Fatal().Err(err).Msg("could not register Bundle service handler") - } - if err := settingssvc.RegisterValueServiceHandler(service.Server(), handle); err != nil { - options.Logger.Fatal().Err(err).Msg("could not register Value service handler") - } - if err := settingssvc.RegisterRoleServiceHandler(service.Server(), handle); err != nil { - options.Logger.Fatal().Err(err).Msg("could not register Role service handler") - } - if err := settingssvc.RegisterPermissionServiceHandler(service.Server(), handle); err != nil { - options.Logger.Fatal().Err(err).Msg("could not register Permission service handler") - } - - if err := RegisterCS3PermissionsServiceHandler(service.Server(), handle); err != nil { - options.Logger.Fatal().Err(err).Msg("could not register CS3 Permission service handler") - } - - return service -} - -func RegisterCS3PermissionsServiceHandler(s server.Server, hdlr permissions.PermissionsAPIServer, opts ...server.HandlerOption) error { - type permissionsService interface { - CheckPermission(context.Context, *permissions.CheckPermissionRequest, *permissions.CheckPermissionResponse) error - } - type PermissionsAPI struct { - permissionsService - } - h := &permissionsServiceHandler{hdlr} - opts = append(opts, api.WithEndpoint(&api.Endpoint{ - Name: "PermissionsService.Checkpermission", - Path: []string{"/api/v0/permissions/check-permission"}, - Method: []string{"POST"}, - Body: "*", - Handler: "rpc", - })) - return s.Handle(s.NewHandler(&PermissionsAPI{h}, opts...)) -} - -type permissionsServiceHandler struct { - api permissions.PermissionsAPIServer -} - -func (h *permissionsServiceHandler) CheckPermission(ctx context.Context, req *permissions.CheckPermissionRequest, res *permissions.CheckPermissionResponse) error { - r, err := h.api.CheckPermission(ctx, req) - if r != nil { - *res = *r - } - return err -} diff --git a/extensions/settings/pkg/server/http/option.go b/extensions/settings/pkg/server/http/option.go deleted file mode 100644 index 6d1e81f5e69..00000000000 --- a/extensions/settings/pkg/server/http/option.go +++ /dev/null @@ -1,76 +0,0 @@ -package http - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/settings/pkg/config" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/metrics" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/urfave/cli/v2" -) - -// Option defines a single option function. -type Option func(o *Options) - -// Options defines the available options for this package. -type Options struct { - Name string - Logger log.Logger - Context context.Context - Config *config.Config - Metrics *metrics.Metrics - Flags []cli.Flag -} - -// 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 - } -} - -// Name provides a name for the service. -func Name(val string) Option { - return func(o *Options) { - o.Name = 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 - } -} - -// Metrics provides a function to set the metrics option. -func Metrics(val *metrics.Metrics) Option { - return func(o *Options) { - o.Metrics = val - } -} - -// Flags provides a function to set the flags option. -func Flags(val []cli.Flag) Option { - return func(o *Options) { - o.Flags = append(o.Flags, val...) - } -} diff --git a/extensions/settings/pkg/server/http/server.go b/extensions/settings/pkg/server/http/server.go deleted file mode 100644 index d8b173a8bb2..00000000000 --- a/extensions/settings/pkg/server/http/server.go +++ /dev/null @@ -1,85 +0,0 @@ -package http - -import ( - "github.com/go-chi/chi/v5" - chimiddleware "github.com/go-chi/chi/v5/middleware" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/assets" - svc "github.com/owncloud/ocis/v2/extensions/settings/pkg/service/v0" - "github.com/owncloud/ocis/v2/ocis-pkg/account" - "github.com/owncloud/ocis/v2/ocis-pkg/cors" - "github.com/owncloud/ocis/v2/ocis-pkg/middleware" - "github.com/owncloud/ocis/v2/ocis-pkg/service/http" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" - "go-micro.dev/v4" -) - -// Server initializes the http service and server. -func Server(opts ...Option) http.Service { - options := newOptions(opts...) - - service := http.NewService( - http.Logger(options.Logger), - http.Name(options.Name), - http.Version(version.GetString()), - http.Address(options.Config.HTTP.Addr), - http.Namespace(options.Config.HTTP.Namespace), - http.Context(options.Context), - http.Flags(options.Flags...), - ) - - handle := svc.NewService(options.Config, options.Logger) - - { - handle = svc.NewInstrument(handle, options.Metrics) - handle = svc.NewLogging(handle, options.Logger) - handle = svc.NewTracing(handle) - } - - mux := chi.NewMux() - - mux.Use(chimiddleware.RealIP) - mux.Use(chimiddleware.RequestID) - mux.Use(middleware.NoCache) - mux.Use(middleware.Cors( - cors.Logger(options.Logger), - cors.AllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins), - cors.AllowedMethods(options.Config.HTTP.CORS.AllowedMethods), - cors.AllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders), - cors.AllowCredentials(options.Config.HTTP.CORS.AllowCredentials), - )) - mux.Use(middleware.Secure) - mux.Use(middleware.ExtractAccountUUID( - account.Logger(options.Logger), - account.JWTSecret(options.Config.TokenManager.JWTSecret)), - ) - - mux.Use(middleware.Version( - options.Name, - version.GetString(), - )) - - mux.Use(middleware.Logger( - options.Logger, - )) - - mux.Use(middleware.Static( - options.Config.HTTP.Root, - assets.New( - assets.Logger(options.Logger), - assets.Config(options.Config), - ), - options.Config.HTTP.CacheTTL, - )) - - mux.Route(options.Config.HTTP.Root, func(r chi.Router) { - settingssvc.RegisterBundleServiceWeb(r, handle) - settingssvc.RegisterValueServiceWeb(r, handle) - settingssvc.RegisterRoleServiceWeb(r, handle) - settingssvc.RegisterPermissionServiceWeb(r, handle) - }) - - micro.RegisterHandler(service.Server(), mux) - - return service -} diff --git a/extensions/settings/pkg/service/v0/instrument.go b/extensions/settings/pkg/service/v0/instrument.go deleted file mode 100644 index d5d13961c04..00000000000 --- a/extensions/settings/pkg/service/v0/instrument.go +++ /dev/null @@ -1,13 +0,0 @@ -package svc - -import ( - "github.com/owncloud/ocis/v2/extensions/settings/pkg/metrics" -) - -// NewInstrument returns a service that instruments metrics. -func NewInstrument(next Service, metrics *metrics.Metrics) Service { - return Service{ - manager: next.manager, - config: next.config, - } -} diff --git a/extensions/settings/pkg/service/v0/option.go b/extensions/settings/pkg/service/v0/option.go deleted file mode 100644 index 7aa6e1afbb3..00000000000 --- a/extensions/settings/pkg/service/v0/option.go +++ /dev/null @@ -1,39 +0,0 @@ -package svc - -import ( - "net/http" - - "github.com/owncloud/ocis/v2/extensions/settings/pkg/config" - "github.com/owncloud/ocis/v2/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 - Config *config.Config - Middleware []func(http.Handler) http.Handler -} - -// Logger provides a function to set the logger option. -func Logger(val log.Logger) Option { - return func(o *Options) { - o.Logger = val - } -} - -// Config provides a function to set the config option. -func Config(val *config.Config) Option { - return func(o *Options) { - o.Config = val - } -} - -// Middleware provides a function to set the middleware option. -func Middleware(val ...func(http.Handler) http.Handler) Option { - return func(o *Options) { - o.Middleware = val - } -} diff --git a/extensions/settings/pkg/service/v0/service.go b/extensions/settings/pkg/service/v0/service.go deleted file mode 100644 index 7142ebc0965..00000000000 --- a/extensions/settings/pkg/service/v0/service.go +++ /dev/null @@ -1,540 +0,0 @@ -package svc - -import ( - "context" - "errors" - "fmt" - - permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" - rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - "github.com/cs3org/reva/v2/pkg/rgrpc/status" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/config" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/settings" - filestore "github.com/owncloud/ocis/v2/extensions/settings/pkg/store/filesystem" - metastore "github.com/owncloud/ocis/v2/extensions/settings/pkg/store/metadata" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/owncloud/ocis/v2/ocis-pkg/middleware" - "github.com/owncloud/ocis/v2/ocis-pkg/roles" - settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" - settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" - merrors "go-micro.dev/v4/errors" - "go-micro.dev/v4/metadata" - "google.golang.org/protobuf/types/known/emptypb" -) - -// Service represents a service. -type Service struct { - id string - config *config.Config - logger log.Logger - manager settings.Manager -} - -// NewService returns a service implementation for Service. -func NewService(cfg *config.Config, logger log.Logger) Service { - service := Service{ - id: "ocis-settings", - config: cfg, - logger: logger, - } - - switch cfg.StoreType { - default: - fallthrough - case "metadata": - service.manager = metastore.New(cfg) - case "filesystem": - service.manager = filestore.New(cfg) - // TODO: if we want to further support filesystem store it should use default permissions from store/defaults/defaults.go instead using this duplicate - service.RegisterDefaultRoles() - } - return service -} - -func (g Service) CheckPermission(ctx context.Context, req *permissions.CheckPermissionRequest) (*permissions.CheckPermissionResponse, error) { - spec := req.SubjectRef.Spec - - var accountID string - switch ref := spec.(type) { - case *permissions.SubjectReference_UserId: - accountID = ref.UserId.OpaqueId - case *permissions.SubjectReference_GroupId: - accountID = ref.GroupId.OpaqueId - } - - assignments, err := g.manager.ListRoleAssignments(accountID) - if err != nil { - return &permissions.CheckPermissionResponse{ - Status: status.NewInternal(ctx, err.Error()), - }, nil - } - - roleIDs := make([]string, 0, len(assignments)) - for _, a := range assignments { - roleIDs = append(roleIDs, a.RoleId) - } - - permission, err := g.manager.ReadPermissionByName(req.Permission, roleIDs) - if err != nil { - if !errors.Is(err, settings.ErrPermissionNotFound) { - return &permissions.CheckPermissionResponse{ - Status: status.NewInternal(ctx, err.Error()), - }, nil - } - } - - if permission == nil { - return &permissions.CheckPermissionResponse{ - Status: &rpcv1beta1.Status{ - Code: rpcv1beta1.Code_CODE_PERMISSION_DENIED, - }, - }, nil - } - - return &permissions.CheckPermissionResponse{ - Status: status.NewOK(ctx), - }, nil -} - -// RegisterDefaultRoles composes default roles and saves them. Skipped if the roles already exist. -func (g Service) RegisterDefaultRoles() { - // FIXME: we're writing default roles per service start (i.e. twice at the moment, for http and grpc server). has to happen only once. - for _, role := range generateBundlesDefaultRoles() { - bundleID := role.Extension + "." + role.Id - // check if the role already exists - bundle, _ := g.manager.ReadBundle(role.Id) - if bundle != nil { - g.logger.Debug().Str("bundleID", bundleID).Msg("bundle already exists. skipping.") - continue - } - // create the role - _, err := g.manager.WriteBundle(role) - if err != nil { - g.logger.Error().Err(err).Str("bundleID", bundleID).Msg("failed to register bundle") - } - g.logger.Debug().Str("bundleID", bundleID).Msg("successfully registered bundle") - } - - for _, req := range generatePermissionRequests() { - _, err := g.manager.AddSettingToBundle(req.GetBundleId(), req.GetSetting()) - if err != nil { - g.logger.Error(). - Err(err). - Str("bundleID", req.GetBundleId()). - Interface("setting", req.GetSetting()). - Msg("failed to register permission") - } - } - - if g.config.SetupDefaultAssignments { - for _, req := range g.defaultRoleAssignments() { - if _, err := g.manager.WriteRoleAssignment(req.AccountUuid, req.RoleId); err != nil { - g.logger.Error().Err(err).Msg("failed to register role assignment") - } - } - } -} - -// TODO: check permissions on every request - -// SaveBundle implements the BundleServiceHandler interface -func (g Service) SaveBundle(ctx context.Context, req *settingssvc.SaveBundleRequest, res *settingssvc.SaveBundleResponse) error { - cleanUpResource(ctx, req.Bundle.Resource) - if err := g.checkStaticPermissionsByBundleType(ctx, req.Bundle.Type); err != nil { - return err - } - if validationError := validateSaveBundle(req); validationError != nil { - return merrors.BadRequest(g.id, "%s", validationError) - } - - r, err := g.manager.WriteBundle(req.Bundle) - if err != nil { - return merrors.BadRequest(g.id, "%s", err) - } - res.Bundle = r - return nil -} - -// GetBundle implements the BundleServiceHandler interface -func (g Service) GetBundle(ctx context.Context, req *settingssvc.GetBundleRequest, res *settingssvc.GetBundleResponse) error { - if validationError := validateGetBundle(req); validationError != nil { - return merrors.BadRequest(g.id, "%s", validationError) - } - bundle, err := g.manager.ReadBundle(req.BundleId) - if err != nil { - return merrors.NotFound(g.id, "%s", err) - } - filteredBundle := g.getFilteredBundle(g.getRoleIDs(ctx), bundle) - if len(filteredBundle.Settings) == 0 { - err = fmt.Errorf("could not read bundle: %s", req.BundleId) - return merrors.NotFound(g.id, "%s", err) - } - res.Bundle = filteredBundle - return nil -} - -// ListBundles implements the BundleServiceHandler interface -func (g Service) ListBundles(ctx context.Context, req *settingssvc.ListBundlesRequest, res *settingssvc.ListBundlesResponse) error { - // fetch all bundles - if validationError := validateListBundles(req); validationError != nil { - return merrors.BadRequest(g.id, "%s", validationError) - } - bundles, err := g.manager.ListBundles(settingsmsg.Bundle_TYPE_DEFAULT, req.BundleIds) - if err != nil { - return merrors.NotFound(g.id, "%s", err) - } - roleIDs := g.getRoleIDs(ctx) - - // filter settings in bundles that are allowed according to roles - var filteredBundles []*settingsmsg.Bundle - for _, bundle := range bundles { - filteredBundle := g.getFilteredBundle(roleIDs, bundle) - if len(filteredBundle.Settings) > 0 { - filteredBundles = append(filteredBundles, filteredBundle) - } - } - - res.Bundles = filteredBundles - return nil -} - -func (g Service) getFilteredBundle(roleIDs []string, bundle *settingsmsg.Bundle) *settingsmsg.Bundle { - // check if full bundle is whitelisted - bundleResource := &settingsmsg.Resource{ - Type: settingsmsg.Resource_TYPE_BUNDLE, - Id: bundle.Id, - } - if g.hasPermission( - roleIDs, - bundleResource, - []settingsmsg.Permission_Operation{settingsmsg.Permission_OPERATION_READ, settingsmsg.Permission_OPERATION_READWRITE}, - settingsmsg.Permission_CONSTRAINT_OWN, - ) { - return bundle - } - - // filter settings based on permissions - var filteredSettings []*settingsmsg.Setting - for _, setting := range bundle.Settings { - settingResource := &settingsmsg.Resource{ - Type: settingsmsg.Resource_TYPE_SETTING, - Id: setting.Id, - } - if g.hasPermission( - roleIDs, - settingResource, - []settingsmsg.Permission_Operation{settingsmsg.Permission_OPERATION_READ, settingsmsg.Permission_OPERATION_READWRITE}, - settingsmsg.Permission_CONSTRAINT_OWN, - ) { - filteredSettings = append(filteredSettings, setting) - } - } - bundle.Settings = filteredSettings - return bundle -} - -// AddSettingToBundle implements the BundleServiceHandler interface -func (g Service) AddSettingToBundle(ctx context.Context, req *settingssvc.AddSettingToBundleRequest, res *settingssvc.AddSettingToBundleResponse) error { - cleanUpResource(ctx, req.Setting.Resource) - if err := g.checkStaticPermissionsByBundleID(ctx, req.BundleId); err != nil { - return err - } - if validationError := validateAddSettingToBundle(req); validationError != nil { - return merrors.BadRequest(g.id, "%s", validationError) - } - - r, err := g.manager.AddSettingToBundle(req.BundleId, req.Setting) - if err != nil { - return merrors.BadRequest(g.id, "%s", err) - } - res.Setting = r - return nil -} - -// RemoveSettingFromBundle implements the BundleServiceHandler interface -func (g Service) RemoveSettingFromBundle(ctx context.Context, req *settingssvc.RemoveSettingFromBundleRequest, _ *emptypb.Empty) error { - if err := g.checkStaticPermissionsByBundleID(ctx, req.BundleId); err != nil { - return err - } - if validationError := validateRemoveSettingFromBundle(req); validationError != nil { - return merrors.BadRequest(g.id, "%s", validationError) - } - - if err := g.manager.RemoveSettingFromBundle(req.BundleId, req.SettingId); err != nil { - return merrors.BadRequest(g.id, "%s", err) - } - - return nil -} - -// SaveValue implements the ValueServiceHandler interface -func (g Service) SaveValue(ctx context.Context, req *settingssvc.SaveValueRequest, res *settingssvc.SaveValueResponse) error { - req.Value.AccountUuid = getValidatedAccountUUID(ctx, req.Value.AccountUuid) - cleanUpResource(ctx, req.Value.Resource) - // TODO: we need to check, if the authenticated user has permission to write the value for the specified resource (e.g. global, file with id xy, ...) - if validationError := validateSaveValue(req); validationError != nil { - return merrors.BadRequest(g.id, "%s", validationError) - } - r, err := g.manager.WriteValue(req.Value) - if err != nil { - return merrors.BadRequest(g.id, "%s", err) - } - valueWithIdentifier, err := g.getValueWithIdentifier(r) - if err != nil { - return merrors.NotFound(g.id, "%s", err) - } - res.Value = valueWithIdentifier - return nil -} - -// GetValue implements the ValueServiceHandler interface -func (g Service) GetValue(ctx context.Context, req *settingssvc.GetValueRequest, res *settingssvc.GetValueResponse) error { - if validationError := validateGetValue(req); validationError != nil { - return merrors.BadRequest(g.id, "%s", validationError) - } - r, err := g.manager.ReadValue(req.Id) - if err != nil { - return merrors.NotFound(g.id, "%s", err) - } - valueWithIdentifier, err := g.getValueWithIdentifier(r) - if err != nil { - return merrors.NotFound(g.id, "%s", err) - } - res.Value = valueWithIdentifier - return nil -} - -// GetValueByUniqueIdentifiers implements the ValueService interface -func (g Service) GetValueByUniqueIdentifiers(ctx context.Context, req *settingssvc.GetValueByUniqueIdentifiersRequest, res *settingssvc.GetValueResponse) error { - if validationError := validateGetValueByUniqueIdentifiers(req); validationError != nil { - return merrors.BadRequest(g.id, "%s", validationError) - } - v, err := g.manager.ReadValueByUniqueIdentifiers(req.AccountUuid, req.SettingId) - if err != nil { - return merrors.NotFound(g.id, "%s", err) - } - - if v.BundleId != "" { - valueWithIdentifier, err := g.getValueWithIdentifier(v) - if err != nil { - return merrors.NotFound(g.id, "%s", err) - } - - res.Value = valueWithIdentifier - } - return nil -} - -// ListValues implements the ValueServiceHandler interface -func (g Service) ListValues(ctx context.Context, req *settingssvc.ListValuesRequest, res *settingssvc.ListValuesResponse) error { - req.AccountUuid = getValidatedAccountUUID(ctx, req.AccountUuid) - if validationError := validateListValues(req); validationError != nil { - return merrors.BadRequest(g.id, "%s", validationError) - } - r, err := g.manager.ListValues(req.BundleId, req.AccountUuid) - if err != nil { - return merrors.NotFound(g.id, "%s", err) - } - var result []*settingsmsg.ValueWithIdentifier - for _, value := range r { - valueWithIdentifier, err := g.getValueWithIdentifier(value) - if err == nil { - result = append(result, valueWithIdentifier) - } - } - res.Values = result - return nil -} - -// ListRoles implements the RoleServiceHandler interface -func (g Service) ListRoles(c context.Context, req *settingssvc.ListBundlesRequest, res *settingssvc.ListBundlesResponse) error { - //accountUUID := getValidatedAccountUUID(c, "me") - if validationError := validateListRoles(req); validationError != nil { - return merrors.BadRequest(g.id, "%s", validationError) - } - r, err := g.manager.ListBundles(settingsmsg.Bundle_TYPE_ROLE, req.BundleIds) - if err != nil { - return merrors.NotFound(g.id, "%s", err) - } - // TODO: only allow to list roles when user has account/role/... management permissions - res.Bundles = r - return nil -} - -// ListRoleAssignments implements the RoleServiceHandler interface -func (g Service) ListRoleAssignments(ctx context.Context, req *settingssvc.ListRoleAssignmentsRequest, res *settingssvc.ListRoleAssignmentsResponse) error { - req.AccountUuid = getValidatedAccountUUID(ctx, req.AccountUuid) - if validationError := validateListRoleAssignments(req); validationError != nil { - return merrors.BadRequest(g.id, "%s", validationError) - } - r, err := g.manager.ListRoleAssignments(req.AccountUuid) - if err != nil { - return merrors.NotFound(g.id, "%s", err) - } - res.Assignments = r - return nil -} - -// AssignRoleToUser implements the RoleServiceHandler interface -func (g Service) AssignRoleToUser(ctx context.Context, req *settingssvc.AssignRoleToUserRequest, res *settingssvc.AssignRoleToUserResponse) error { - if err := g.checkStaticPermissionsByBundleType(ctx, settingsmsg.Bundle_TYPE_ROLE); err != nil { - return err - } - - req.AccountUuid = getValidatedAccountUUID(ctx, req.AccountUuid) - if validationError := validateAssignRoleToUser(req); validationError != nil { - return merrors.BadRequest(g.id, "%s", validationError) - } - r, err := g.manager.WriteRoleAssignment(req.AccountUuid, req.RoleId) - if err != nil { - return merrors.BadRequest(g.id, "%s", err) - } - res.Assignment = r - return nil -} - -// RemoveRoleFromUser implements the RoleServiceHandler interface -func (g Service) RemoveRoleFromUser(ctx context.Context, req *settingssvc.RemoveRoleFromUserRequest, _ *emptypb.Empty) error { - if err := g.checkStaticPermissionsByBundleType(ctx, settingsmsg.Bundle_TYPE_ROLE); err != nil { - return err - } - - if validationError := validateRemoveRoleFromUser(req); validationError != nil { - return merrors.BadRequest(g.id, "%s", validationError) - } - if err := g.manager.RemoveRoleAssignment(req.Id); err != nil { - return merrors.BadRequest(g.id, "%s", err) - } - return nil -} - -// ListPermissionsByResource implements the PermissionServiceHandler interface -func (g Service) ListPermissionsByResource(ctx context.Context, req *settingssvc.ListPermissionsByResourceRequest, res *settingssvc.ListPermissionsByResourceResponse) error { - if validationError := validateListPermissionsByResource(req); validationError != nil { - return merrors.BadRequest(g.id, "%s", validationError) - } - permissions, err := g.manager.ListPermissionsByResource(req.Resource, g.getRoleIDs(ctx)) - if err != nil { - return merrors.BadRequest(g.id, "%s", err) - } - res.Permissions = permissions - return nil -} - -// GetPermissionByID implements the PermissionServiceHandler interface -func (g Service) GetPermissionByID(ctx context.Context, req *settingssvc.GetPermissionByIDRequest, res *settingssvc.GetPermissionByIDResponse) error { - if validationError := validateGetPermissionByID(req); validationError != nil { - return merrors.BadRequest(g.id, "%s", validationError) - } - permission, err := g.manager.ReadPermissionByID(req.PermissionId, g.getRoleIDs(ctx)) - if err != nil { - return merrors.BadRequest(g.id, "%s", err) - } - if permission == nil { - return merrors.NotFound(g.id, "%s", fmt.Errorf("permission %s not found in roles", req.PermissionId)) - } - res.Permission = permission - return nil -} - -// cleanUpResource makes sure that the account uuid of the authenticated user is injected if needed. -func cleanUpResource(ctx context.Context, resource *settingsmsg.Resource) { - if resource != nil && resource.Type == settingsmsg.Resource_TYPE_USER { - resource.Id = getValidatedAccountUUID(ctx, resource.Id) - } -} - -// getValidatedAccountUUID converts `me` into an actual account uuid from the context, if possible. -// the result of this function will always be a valid lower-case UUID or an empty string. -func getValidatedAccountUUID(ctx context.Context, accountUUID string) string { - if accountUUID == "me" { - if ownAccountUUID, ok := metadata.Get(ctx, middleware.AccountID); ok { - accountUUID = ownAccountUUID - } - } - if accountUUID == "me" { - // no matter what happens above, an accountUUID of `me` must not be passed on. Clear it instead. - accountUUID = "" - } - return accountUUID -} - -// getRoleIDs extracts the roleIDs of the authenticated user from the context. -func (g Service) getRoleIDs(ctx context.Context) []string { - var ownRoleIDs []string - if ownRoleIDs, ok := roles.ReadRoleIDsFromContext(ctx); ok { - return ownRoleIDs - } - if accountID, ok := metadata.Get(ctx, middleware.AccountID); ok { - assignments, err := g.manager.ListRoleAssignments(accountID) - if err != nil { - g.logger.Info().Err(err).Str("userid", accountID).Msg("failed to get roles for user") - return []string{} - } - - for _, a := range assignments { - ownRoleIDs = append(ownRoleIDs, a.RoleId) - } - return ownRoleIDs - } - g.logger.Info().Msg("failed to get accountID from context") - return []string{} -} - -func (g Service) getValueWithIdentifier(value *settingsmsg.Value) (*settingsmsg.ValueWithIdentifier, error) { - bundle, err := g.manager.ReadBundle(value.BundleId) - if err != nil { - return nil, err - } - setting, err := g.manager.ReadSetting(value.SettingId) - if err != nil { - return nil, err - } - return &settingsmsg.ValueWithIdentifier{ - Identifier: &settingsmsg.Identifier{ - Extension: bundle.Extension, - Bundle: bundle.Name, - Setting: setting.Name, - }, - Value: value, - }, nil -} - -func (g Service) hasStaticPermission(ctx context.Context, permissionID string) bool { - roleIDs, ok := roles.ReadRoleIDsFromContext(ctx) - if !ok { - /** - * FIXME: with this we are skipping permission checks on all requests that are coming in without roleIDs in the - * metadata context. This is a huge security impairment, as that's the case not only for grpc requests but also - * for unauthenticated http requests and http requests coming in without hitting the ocis-proxy first. - */ - // TODO add system role for internal requests. - // - at least the proxy needs to look up account info - // - glauth needs to make bind requests - // tracked as OCIS-454 - return true - } - p, err := g.manager.ReadPermissionByID(permissionID, roleIDs) - return err == nil && p != nil -} - -func (g Service) checkStaticPermissionsByBundleID(ctx context.Context, bundleID string) error { - bundle, err := g.manager.ReadBundle(bundleID) - if err != nil { - return merrors.NotFound(g.id, "bundle not found: %s", err) - } - return g.checkStaticPermissionsByBundleType(ctx, bundle.Type) -} - -func (g Service) checkStaticPermissionsByBundleType(ctx context.Context, bundleType settingsmsg.Bundle_Type) error { - if bundleType == settingsmsg.Bundle_TYPE_ROLE { - if !g.hasStaticPermission(ctx, RoleManagementPermissionID) { - return merrors.Forbidden(g.id, "user has no role management permission") - } - return nil - } - if !g.hasStaticPermission(ctx, SettingsManagementPermissionID) { - return merrors.Forbidden(g.id, "user has no settings management permission") - } - return nil -} diff --git a/extensions/settings/pkg/store/filesystem/bundles.go b/extensions/settings/pkg/store/filesystem/bundles.go deleted file mode 100644 index 3b5430d42de..00000000000 --- a/extensions/settings/pkg/store/filesystem/bundles.go +++ /dev/null @@ -1,180 +0,0 @@ -// Package store implements the go-micro store interface -package store - -import ( - "fmt" - "io/ioutil" - "path/filepath" - "sync" - - "github.com/gofrs/uuid" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/store/errortypes" - settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" -) - -var m = &sync.RWMutex{} - -// ListBundles returns all bundles in the dataPath folder that match the given type. -func (s Store) ListBundles(bundleType settingsmsg.Bundle_Type, bundleIDs []string) ([]*settingsmsg.Bundle, error) { - // FIXME: list requests should be ran against a cache, not FS - m.RLock() - defer m.RUnlock() - - bundlesFolder := s.buildFolderPathForBundles(false) - bundleFiles, err := ioutil.ReadDir(bundlesFolder) - if err != nil { - return []*settingsmsg.Bundle{}, nil - } - - records := make([]*settingsmsg.Bundle, 0, len(bundleFiles)) - for _, bundleFile := range bundleFiles { - record := settingsmsg.Bundle{} - err = s.parseRecordFromFile(&record, filepath.Join(bundlesFolder, bundleFile.Name())) - if err != nil { - s.Logger.Warn().Msgf("error reading %v", bundleFile) - continue - } - if record.Type != bundleType { - continue - } - if len(bundleIDs) > 0 && !containsStr(record.Id, bundleIDs) { - continue - } - records = append(records, &record) - } - - return records, nil -} - -// containsStr checks if the strs slice contains str -func containsStr(str string, strs []string) bool { - for _, s := range strs { - if s == str { - return true - } - } - return false -} - -// ReadBundle tries to find a bundle by the given id within the dataPath. -func (s Store) ReadBundle(bundleID string) (*settingsmsg.Bundle, error) { - m.RLock() - defer m.RUnlock() - - filePath := s.buildFilePathForBundle(bundleID, false) - record := settingsmsg.Bundle{} - if err := s.parseRecordFromFile(&record, filePath); err != nil { - return nil, err - } - - s.Logger.Debug().Msgf("read contents from file: %v", filePath) - return &record, nil -} - -// ReadSetting tries to find a setting by the given id within the dataPath. -func (s Store) ReadSetting(settingID string) (*settingsmsg.Setting, error) { - m.RLock() - defer m.RUnlock() - - bundles, err := s.ListBundles(settingsmsg.Bundle_TYPE_DEFAULT, []string{}) - if err != nil { - return nil, err - } - for _, bundle := range bundles { - for _, setting := range bundle.Settings { - if setting.Id == settingID { - return setting, nil - } - } - } - return nil, fmt.Errorf("could not read setting: %v", settingID) -} - -// WriteBundle writes the given record into a file within the dataPath. -func (s Store) WriteBundle(record *settingsmsg.Bundle) (*settingsmsg.Bundle, error) { - // FIXME: locking should happen on the file here, not globally. - m.Lock() - defer m.Unlock() - if record.Id == "" { - record.Id = uuid.Must(uuid.NewV4()).String() - } - filePath := s.buildFilePathForBundle(record.Id, true) - if err := s.writeRecordToFile(record, filePath); err != nil { - return nil, err - } - - s.Logger.Debug().Msgf("request contents written to file: %v", filePath) - return record, nil -} - -// AddSettingToBundle adds the given setting to the bundle with the given bundleID. -func (s Store) AddSettingToBundle(bundleID string, setting *settingsmsg.Setting) (*settingsmsg.Setting, error) { - bundle, err := s.ReadBundle(bundleID) - if err != nil { - if _, notFound := err.(errortypes.BundleNotFound); !notFound { - return nil, err - } - bundle = new(settingsmsg.Bundle) - bundle.Id = bundleID - bundle.Type = settingsmsg.Bundle_TYPE_DEFAULT - } - if setting.Id == "" { - setting.Id = uuid.Must(uuid.NewV4()).String() - } - setSetting(bundle, setting) - _, err = s.WriteBundle(bundle) - if err != nil { - return nil, err - } - return setting, nil -} - -// RemoveSettingFromBundle removes the setting from the bundle with the given ids. -func (s Store) RemoveSettingFromBundle(bundleID string, settingID string) error { - bundle, err := s.ReadBundle(bundleID) - if err != nil { - return nil - } - if ok := removeSetting(bundle, settingID); ok { - if _, err := s.WriteBundle(bundle); err != nil { - return err - } - } - return nil -} - -// indexOfSetting finds the index of the given setting within the given bundle. -// returns -1 if the setting was not found. -func indexOfSetting(bundle *settingsmsg.Bundle, settingID string) int { - for index := range bundle.Settings { - s := bundle.Settings[index] - if s.Id == settingID { - return index - } - } - return -1 -} - -// setSetting will append or overwrite the given setting within the given bundle -func setSetting(bundle *settingsmsg.Bundle, setting *settingsmsg.Setting) { - m.Lock() - defer m.Unlock() - index := indexOfSetting(bundle, setting.Id) - if index == -1 { - bundle.Settings = append(bundle.Settings, setting) - } else { - bundle.Settings[index] = setting - } -} - -// removeSetting will remove the given setting from the given bundle -func removeSetting(bundle *settingsmsg.Bundle, settingID string) bool { - m.Lock() - defer m.Unlock() - index := indexOfSetting(bundle, settingID) - if index == -1 { - return false - } - bundle.Settings = append(bundle.Settings[:index], bundle.Settings[index+1:]...) - return true -} diff --git a/extensions/settings/pkg/store/filesystem/permissions.go b/extensions/settings/pkg/store/filesystem/permissions.go deleted file mode 100644 index a84886202d6..00000000000 --- a/extensions/settings/pkg/store/filesystem/permissions.go +++ /dev/null @@ -1,72 +0,0 @@ -package store - -import ( - "github.com/owncloud/ocis/v2/extensions/settings/pkg/settings" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/util" - settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" -) - -// ListPermissionsByResource collects all permissions from the provided roleIDs that match the requested resource -func (s Store) ListPermissionsByResource(resource *settingsmsg.Resource, roleIDs []string) ([]*settingsmsg.Permission, error) { - records := make([]*settingsmsg.Permission, 0) - for _, roleID := range roleIDs { - role, err := s.ReadBundle(roleID) - if err != nil { - s.Logger.Debug().Str("roleID", roleID).Msg("role not found, skipping") - continue - } - records = append(records, extractPermissionsByResource(resource, role)...) - } - return records, nil -} - -// ReadPermissionByID finds the permission in the roles, specified by the provided roleIDs -func (s Store) ReadPermissionByID(permissionID string, roleIDs []string) (*settingsmsg.Permission, error) { - for _, roleID := range roleIDs { - role, err := s.ReadBundle(roleID) - if err != nil { - s.Logger.Debug().Str("roleID", roleID).Msg("role not found, skipping") - continue - } - for _, permission := range role.Settings { - if permission.Id == permissionID { - if value, ok := permission.Value.(*settingsmsg.Setting_PermissionValue); ok { - return value.PermissionValue, nil - } - } - } - } - return nil, nil -} - -// ReadPermissionByName finds the permission in the roles, specified by the provided roleIDs -func (s Store) ReadPermissionByName(name string, roleIDs []string) (*settingsmsg.Permission, error) { - for _, roleID := range roleIDs { - role, err := s.ReadBundle(roleID) - if err != nil { - s.Logger.Debug().Str("roleID", roleID).Msg("role not found, skipping") - continue - } - for _, permission := range role.Settings { - if permission.Name == name { - if value, ok := permission.Value.(*settingsmsg.Setting_PermissionValue); ok { - return value.PermissionValue, nil - } - } - } - } - return nil, settings.ErrPermissionNotFound -} - -// extractPermissionsByResource collects all permissions from the provided role that match the requested resource -func extractPermissionsByResource(resource *settingsmsg.Resource, role *settingsmsg.Bundle) []*settingsmsg.Permission { - permissions := make([]*settingsmsg.Permission, 0) - for _, setting := range role.Settings { - if value, ok := setting.Value.(*settingsmsg.Setting_PermissionValue); ok { - if util.IsResourceMatched(setting.Resource, resource) { - permissions = append(permissions, value.PermissionValue) - } - } - } - return permissions -} diff --git a/extensions/settings/pkg/store/filesystem/store.go b/extensions/settings/pkg/store/filesystem/store.go deleted file mode 100644 index 4782648fe5c..00000000000 --- a/extensions/settings/pkg/store/filesystem/store.go +++ /dev/null @@ -1,50 +0,0 @@ -// Package store implements the go-micro store interface -package store - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/settings/pkg/config" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/settings" - olog "github.com/owncloud/ocis/v2/ocis-pkg/log" -) - -var ( - // Name is the default name for the settings store - Name = "ocis-settings" - managerName = "filesystem" -) - -// Store interacts with the filesystem to manage settings information -type Store struct { - dataPath string - Logger olog.Logger -} - -// New creates a new store -func New(cfg *config.Config) settings.Manager { - s := Store{ - //Logger: olog.NewLogger( - // olog.Color(cfg.Log.Color), - // olog.Pretty(cfg.Log.Pretty), - // olog.Level(cfg.Log.Level), - // olog.File(cfg.Log.File), - //), - } - - if _, err := os.Stat(cfg.DataPath); err != nil { - s.Logger.Info().Msgf("creating container on %v", cfg.DataPath) - err = os.MkdirAll(cfg.DataPath, 0700) - - if err != nil { - s.Logger.Err(err).Msgf("providing container on %v", cfg.DataPath) - } - } - - s.dataPath = cfg.DataPath - return &s -} - -func init() { - settings.Registry[managerName] = New -} diff --git a/extensions/settings/pkg/store/metadata/bundles.go b/extensions/settings/pkg/store/metadata/bundles.go deleted file mode 100644 index d55b756c05c..00000000000 --- a/extensions/settings/pkg/store/metadata/bundles.go +++ /dev/null @@ -1,146 +0,0 @@ -// Package store implements the go-micro store interface -package store - -import ( - "context" - "encoding/json" - "errors" - "fmt" - - "github.com/gofrs/uuid" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/store/defaults" - settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" -) - -// ListBundles returns all bundles in the dataPath folder that match the given type. -func (s *Store) ListBundles(bundleType settingsmsg.Bundle_Type, bundleIDs []string) ([]*settingsmsg.Bundle, error) { - // TODO: this is needed for initialization - we need to find a better way to fix this - if s.mdc == nil && len(bundleIDs) == 1 { - return defaultBundle(bundleType, bundleIDs[0]), nil - } - s.Init() - ctx := context.TODO() - - if len(bundleIDs) == 0 { - bIDs, err := s.mdc.ReadDir(ctx, bundleFolderLocation) - if err != nil { - return nil, err - } - - bundleIDs = bIDs - } - var bundles []*settingsmsg.Bundle - for _, id := range bundleIDs { - b, err := s.mdc.SimpleDownload(ctx, bundlePath(id)) - if err != nil { - return nil, err - } - - bundle := &settingsmsg.Bundle{} - err = json.Unmarshal(b, bundle) - if err != nil { - return nil, err - } - - if bundle.Type == bundleType { - bundles = append(bundles, bundle) - } - - } - return bundles, nil -} - -// ReadBundle tries to find a bundle by the given id from the metadata service -func (s *Store) ReadBundle(bundleID string) (*settingsmsg.Bundle, error) { - if s.mdc == nil { - return defaultBundle(settingsmsg.Bundle_TYPE_ROLE, bundleID)[0], nil - } - s.Init() - ctx := context.TODO() - b, err := s.mdc.SimpleDownload(ctx, bundlePath(bundleID)) - if err != nil { - return nil, err - } - - bundle := &settingsmsg.Bundle{} - return bundle, json.Unmarshal(b, bundle) -} - -// ReadSetting tries to find a setting by the given id from the metadata service -func (s *Store) ReadSetting(settingID string) (*settingsmsg.Setting, error) { - s.Init() - ctx := context.TODO() - - ids, err := s.mdc.ReadDir(ctx, bundleFolderLocation) - if err != nil { - return nil, err - } - - // TODO: avoid spamming metadata service - for _, id := range ids { - b, err := s.ReadBundle(id) - if err != nil { - return nil, err - } - - for _, setting := range b.Settings { - if setting.Id == settingID { - return setting, nil - } - } - - } - return nil, fmt.Errorf("setting '%s' not found", settingID) -} - -// WriteBundle sends the givens record to the metadataclient. returns `record` for legacy reasons -func (s *Store) WriteBundle(record *settingsmsg.Bundle) (*settingsmsg.Bundle, error) { - s.Init() - ctx := context.TODO() - - b, err := json.Marshal(record) - if err != nil { - return nil, err - } - return record, s.mdc.SimpleUpload(ctx, bundlePath(record.Id), b) -} - -// AddSettingToBundle adds the given setting to the bundle with the given bundleID. -func (s *Store) AddSettingToBundle(bundleID string, setting *settingsmsg.Setting) (*settingsmsg.Setting, error) { - s.Init() - b, err := s.ReadBundle(bundleID) - if err != nil { - // TODO: How to differentiate 'not found'? - b = new(settingsmsg.Bundle) - b.Id = bundleID - b.Type = settingsmsg.Bundle_TYPE_DEFAULT - } - - if setting.Id == "" { - setting.Id = uuid.Must(uuid.NewV4()).String() - } - - b.Settings = append(b.Settings, setting) - _, err = s.WriteBundle(b) - return setting, err -} - -// RemoveSettingFromBundle removes the setting from the bundle with the given ids. -func (s *Store) RemoveSettingFromBundle(bundleID string, settingID string) error { - fmt.Println("RemoveSettingFromBundle not implemented") - return errors.New("not implemented") -} - -func bundlePath(id string) string { - return fmt.Sprintf("%s/%s", bundleFolderLocation, id) -} - -func defaultBundle(bundleType settingsmsg.Bundle_Type, bundleID string) []*settingsmsg.Bundle { - var bundles []*settingsmsg.Bundle - for _, b := range defaults.GenerateBundlesDefaultRoles() { - if b.Type == bundleType && b.Id == bundleID { - bundles = append(bundles, b) - } - } - return bundles -} diff --git a/extensions/settings/pkg/store/metadata/permissions.go b/extensions/settings/pkg/store/metadata/permissions.go deleted file mode 100644 index d70c7dbcee9..00000000000 --- a/extensions/settings/pkg/store/metadata/permissions.go +++ /dev/null @@ -1,72 +0,0 @@ -package store - -import ( - "github.com/owncloud/ocis/v2/extensions/settings/pkg/settings" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/util" - settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" -) - -// ListPermissionsByResource collects all permissions from the provided roleIDs that match the requested resource -func (s *Store) ListPermissionsByResource(resource *settingsmsg.Resource, roleIDs []string) ([]*settingsmsg.Permission, error) { - records := make([]*settingsmsg.Permission, 0) - for _, roleID := range roleIDs { - role, err := s.ReadBundle(roleID) - if err != nil { - s.Logger.Debug().Str("roleID", roleID).Msg("role not found, skipping") - continue - } - records = append(records, extractPermissionsByResource(resource, role)...) - } - return records, nil -} - -// ReadPermissionByID finds the permission in the roles, specified by the provided roleIDs -func (s *Store) ReadPermissionByID(permissionID string, roleIDs []string) (*settingsmsg.Permission, error) { - for _, roleID := range roleIDs { - role, err := s.ReadBundle(roleID) - if err != nil { - s.Logger.Debug().Str("roleID", roleID).Msg("role not found, skipping") - continue - } - for _, permission := range role.Settings { - if permission.Id == permissionID { - if value, ok := permission.Value.(*settingsmsg.Setting_PermissionValue); ok { - return value.PermissionValue, nil - } - } - } - } - return nil, nil -} - -// ReadPermissionByName finds the permission in the roles, specified by the provided roleIDs -func (s *Store) ReadPermissionByName(name string, roleIDs []string) (*settingsmsg.Permission, error) { - for _, roleID := range roleIDs { - role, err := s.ReadBundle(roleID) - if err != nil { - s.Logger.Debug().Str("roleID", roleID).Msg("role not found, skipping") - continue - } - for _, permission := range role.Settings { - if permission.Name == name { - if value, ok := permission.Value.(*settingsmsg.Setting_PermissionValue); ok { - return value.PermissionValue, nil - } - } - } - } - return nil, settings.ErrPermissionNotFound -} - -// extractPermissionsByResource collects all permissions from the provided role that match the requested resource -func extractPermissionsByResource(resource *settingsmsg.Resource, role *settingsmsg.Bundle) []*settingsmsg.Permission { - permissions := make([]*settingsmsg.Permission, 0) - for _, setting := range role.Settings { - if value, ok := setting.Value.(*settingsmsg.Setting_PermissionValue); ok { - if util.IsResourceMatched(setting.Resource, resource) { - permissions = append(permissions, value.PermissionValue) - } - } - } - return permissions -} diff --git a/extensions/settings/pkg/store/metadata/store.go b/extensions/settings/pkg/store/metadata/store.go deleted file mode 100644 index 3e37d5d52cb..00000000000 --- a/extensions/settings/pkg/store/metadata/store.go +++ /dev/null @@ -1,155 +0,0 @@ -// Package store implements the go-micro store interface -package store - -import ( - "context" - "encoding/json" - "log" - "sync" - - "github.com/cs3org/reva/v2/pkg/storage/utils/metadata" - "github.com/gofrs/uuid" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/config" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/settings" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/store/defaults" - olog "github.com/owncloud/ocis/v2/ocis-pkg/log" - settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" -) - -var ( - // Name is the default name for the settings store - Name = "ocis-settings" - managerName = "metadata" - settingsSpaceID = "f1bdd61a-da7c-49fc-8203-0558109d1b4f" // uuid.Must(uuid.NewV4()).String() - rootFolderLocation = "settings" - bundleFolderLocation = "settings/bundles" - accountsFolderLocation = "settings/accounts" - valuesFolderLocation = "settings/values" -) - -// MetadataClient is the interface to talk to metadata service -type MetadataClient interface { - SimpleDownload(ctx context.Context, id string) ([]byte, error) - SimpleUpload(ctx context.Context, id string, content []byte) error - Delete(ctx context.Context, id string) error - ReadDir(ctx context.Context, id string) ([]string, error) - MakeDirIfNotExist(ctx context.Context, id string) error - Init(ctx context.Context, id string) error -} - -// Store interacts with the filesystem to manage settings information -type Store struct { - Logger olog.Logger - - mdc MetadataClient - cfg *config.Config - - l *sync.Mutex -} - -// Init initialize the store once, later calls are noops -func (s *Store) Init() { - if s.mdc != nil { - return - } - - s.l.Lock() - defer s.l.Unlock() - - if s.mdc != nil { - return - } - - mdc := &CachedMDC{next: NewMetadataClient(s.cfg.Metadata)} - if err := s.initMetadataClient(mdc); err != nil { - s.Logger.Error().Err(err).Msg("error initializing metadata client") - } -} - -// New creates a new store -func New(cfg *config.Config) settings.Manager { - s := Store{ - Logger: olog.NewLogger( - olog.Color(cfg.Log.Color), - olog.Pretty(cfg.Log.Pretty), - olog.Level(cfg.Log.Level), - olog.File(cfg.Log.File), - ), - cfg: cfg, - l: &sync.Mutex{}, - } - - return &s -} - -// NewMetadataClient returns the MetadataClient -func NewMetadataClient(cfg config.Metadata) MetadataClient { - mdc, err := metadata.NewCS3Storage(cfg.GatewayAddress, cfg.StorageAddress, cfg.SystemUserID, cfg.SystemUserIDP, cfg.SystemUserAPIKey) - if err != nil { - log.Fatal("error connecting to mdc:", err) - } - return mdc - -} - -// we need to lazy initialize the MetadataClient because metadata service might not be ready -func (s *Store) initMetadataClient(mdc MetadataClient) error { - ctx := context.TODO() - err := mdc.Init(ctx, settingsSpaceID) - if err != nil { - return err - } - - for _, p := range []string{ - rootFolderLocation, - accountsFolderLocation, - bundleFolderLocation, - valuesFolderLocation, - } { - err = mdc.MakeDirIfNotExist(ctx, p) - if err != nil { - return err - } - } - - for _, p := range defaults.GenerateBundlesDefaultRoles() { - b, err := json.Marshal(p) - if err != nil { - return err - } - err = mdc.SimpleUpload(ctx, bundlePath(p.Id), b) - if err != nil { - return err - } - } - - for _, p := range defaults.DefaultRoleAssignments(s.cfg) { - accountUUID := p.AccountUuid - roleID := p.RoleId - err = mdc.MakeDirIfNotExist(ctx, accountPath(accountUUID)) - if err != nil { - return err - } - - ass := &settingsmsg.UserRoleAssignment{ - Id: uuid.Must(uuid.NewV4()).String(), - AccountUuid: accountUUID, - RoleId: roleID, - } - b, err := json.Marshal(ass) - if err != nil { - return err - } - err = mdc.SimpleUpload(ctx, assignmentPath(accountUUID, ass.Id), b) - if err != nil { - return err - } - } - - s.mdc = mdc - return nil -} - -func init() { - settings.Registry[managerName] = New -} diff --git a/extensions/settings/pkg/store/registry.go b/extensions/settings/pkg/store/registry.go deleted file mode 100644 index d82af83bd6a..00000000000 --- a/extensions/settings/pkg/store/registry.go +++ /dev/null @@ -1,7 +0,0 @@ -package store - -import ( - // init filesystem store - _ "github.com/owncloud/ocis/v2/extensions/settings/pkg/store/filesystem" - _ "github.com/owncloud/ocis/v2/extensions/settings/pkg/store/metadata" -) diff --git a/extensions/settings/pkg/tracing/tracing.go b/extensions/settings/pkg/tracing/tracing.go deleted file mode 100644 index 9532fd2972e..00000000000 --- a/extensions/settings/pkg/tracing/tracing.go +++ /dev/null @@ -1,23 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/v2/extensions/settings/pkg/config" - pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" - "go.opentelemetry.io/otel/trace" -) - -var ( - // TraceProvider is the global trace provider for the settings service. - TraceProvider = trace.NewNoopTracerProvider() -) - -func Configure(cfg *config.Config) error { - var err error - if cfg.Tracing.Enabled { - if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { - return err - } - } - - return nil -} diff --git a/extensions/sharing/cmd/sharing/main.go b/extensions/sharing/cmd/sharing/main.go deleted file mode 100644 index 63938d147e5..00000000000 --- a/extensions/sharing/cmd/sharing/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/sharing/pkg/command" - "github.com/owncloud/ocis/v2/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/health.go b/extensions/sharing/pkg/command/health.go deleted file mode 100644 index f0303cc8a7e..00000000000 --- a/extensions/sharing/pkg/command/health.go +++ /dev/null @@ -1,57 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/sharing/pkg/config" - "github.com/owncloud/ocis/v2/extensions/sharing/pkg/config/parser" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 38e6cd0e30e..00000000000 --- a/extensions/sharing/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/sharing/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 3b9470c1a3a..00000000000 --- a/extensions/sharing/pkg/command/server.go +++ /dev/null @@ -1,121 +0,0 @@ -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/v2/extensions/sharing/pkg/config" - "github.com/owncloud/ocis/v2/extensions/sharing/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/sharing/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/sharing/pkg/revaconfig" - "github.com/owncloud/ocis/v2/extensions/sharing/pkg/server/debug" - "github.com/owncloud/ocis/v2/extensions/sharing/pkg/tracing" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" - "github.com/owncloud/ocis/v2/ocis-pkg/sync" - "github.com/owncloud/ocis/v2/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) - os.Exit(1) - } - 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.GetString(), - 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 deleted file mode 100644 index 6e4842fb194..00000000000 --- a/extensions/sharing/pkg/command/version.go +++ /dev/null @@ -1,50 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - tw "github.com/olekukonko/tablewriter" - "github.com/owncloud/ocis/v2/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.GetString()) - 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/defaults/defaultconfig.go b/extensions/sharing/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index 54513c932b0..00000000000 --- a/extensions/sharing/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,129 +0,0 @@ -package defaults - -import ( - "path/filepath" - - "github.com/owncloud/ocis/v2/extensions/sharing/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" -) - -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:9151", - Token: "", - Pprof: false, - Zpages: false, - }, - GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9150", - Namespace: "com.owncloud.api", - Protocol: "tcp", - }, - Service: config.Service{ - Name: "sharing", - }, - Reva: &config.Reva{ - Address: "127.0.0.1:9142", - }, - UserSharingDriver: "cs3", - UserSharingDrivers: config.UserSharingDrivers{ - JSON: config.UserSharingJSONDriver{ - File: filepath.Join(defaults.BaseDataPath(), "storage", "shares.json"), - }, - CS3: config.UserSharingCS3Driver{ - ProviderAddr: "127.0.0.1:9215", // system storage - SystemUserIDP: "internal", - }, - OwnCloudSQL: config.UserSharingOwnCloudSQLDriver{ - DBUsername: "owncloud", - DBHost: "mysql", - DBPort: 3306, - DBName: "owncloud", - }, - }, - PublicSharingDriver: "cs3", - PublicSharingDrivers: config.PublicSharingDrivers{ - JSON: config.PublicSharingJSONDriver{ - File: filepath.Join(defaults.BaseDataPath(), "storage", "publicshares.json"), - }, - CS3: config.PublicSharingCS3Driver{ - ProviderAddr: "127.0.0.1:9215", // system storage - SystemUserIDP: "internal", - }, - // TODO implement and add owncloudsql publicshare driver - }, - Events: config.Events{ - Addr: "127.0.0.1:9233", - ClusterID: "ocis-cluster", - }, - } -} - -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{} - } - - if cfg.UserSharingDrivers.CS3.SystemUserAPIKey == "" && cfg.Commons != nil && cfg.Commons.SystemUserAPIKey != "" { - cfg.UserSharingDrivers.CS3.SystemUserAPIKey = cfg.Commons.SystemUserAPIKey - } - - if cfg.UserSharingDrivers.CS3.SystemUserID == "" && cfg.Commons != nil && cfg.Commons.SystemUserID != "" { - cfg.UserSharingDrivers.CS3.SystemUserID = cfg.Commons.SystemUserID - } - - if cfg.PublicSharingDrivers.CS3.SystemUserAPIKey == "" && cfg.Commons != nil && cfg.Commons.SystemUserAPIKey != "" { - cfg.PublicSharingDrivers.CS3.SystemUserAPIKey = cfg.Commons.SystemUserAPIKey - } - - if cfg.PublicSharingDrivers.CS3.SystemUserID == "" && cfg.Commons != nil && cfg.Commons.SystemUserID != "" { - cfg.PublicSharingDrivers.CS3.SystemUserID = cfg.Commons.SystemUserID - } -} - -func Sanitize(cfg *config.Config) { - // nothing to sanitize here atm -} diff --git a/extensions/sharing/pkg/config/parser/parse.go b/extensions/sharing/pkg/config/parser/parse.go deleted file mode 100644 index f6a43b2b762..00000000000 --- a/extensions/sharing/pkg/config/parser/parse.go +++ /dev/null @@ -1,58 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/sharing/pkg/config" - "github.com/owncloud/ocis/v2/extensions/sharing/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - if cfg.TokenManager.JWTSecret == "" { - return shared.MissingJWTTokenError(cfg.Service.Name) - } - - if cfg.PublicSharingDriver == "cs3" && cfg.PublicSharingDrivers.CS3.SystemUserAPIKey == "" { - return shared.MissingSystemUserApiKeyError(cfg.Service.Name) - } - - if cfg.PublicSharingDriver == "cs3" && cfg.PublicSharingDrivers.CS3.SystemUserID == "" { - return shared.MissingSystemUserID(cfg.Service.Name) - } - - if cfg.UserSharingDriver == "cs3" && cfg.UserSharingDrivers.CS3.SystemUserAPIKey == "" { - return shared.MissingSystemUserApiKeyError(cfg.Service.Name) - } - - if cfg.UserSharingDriver == "cs3" && cfg.UserSharingDrivers.CS3.SystemUserID == "" { - return shared.MissingSystemUserID(cfg.Service.Name) - } - - return nil -} diff --git a/extensions/sharing/pkg/logging/logging.go b/extensions/sharing/pkg/logging/logging.go deleted file mode 100644 index 90114df284f..00000000000 --- a/extensions/sharing/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/sharing/pkg/config" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index efd5de22437..00000000000 --- a/extensions/sharing/pkg/revaconfig/config.go +++ /dev/null @@ -1,99 +0,0 @@ -package revaconfig - -import ( - "github.com/owncloud/ocis/v2/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{}{ - "gateway_addr": cfg.Reva.Address, - "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{}{ - "gateway_addr": cfg.UserSharingDrivers.CS3.ProviderAddr, - "provider_addr": cfg.UserSharingDrivers.CS3.ProviderAddr, - "service_user_id": cfg.UserSharingDrivers.CS3.SystemUserID, - "service_user_idp": cfg.UserSharingDrivers.CS3.SystemUserIDP, - "machine_auth_apikey": cfg.UserSharingDrivers.CS3.SystemUserAPIKey, - }, - }, - }, - "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{}{ - "gateway_addr": cfg.PublicSharingDrivers.CS3.ProviderAddr, - "provider_addr": cfg.PublicSharingDrivers.CS3.ProviderAddr, - "service_user_id": cfg.PublicSharingDrivers.CS3.SystemUserID, - "service_user_idp": cfg.PublicSharingDrivers.CS3.SystemUserIDP, - "machine_auth_apikey": cfg.PublicSharingDrivers.CS3.SystemUserAPIKey, - }, - }, - }, - }, - "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 deleted file mode 100644 index 2c59af9067d..00000000000 --- a/extensions/sharing/pkg/server/debug/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/sharing/pkg/config" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index c9e262a4236..00000000000 --- a/extensions/sharing/pkg/server/debug/server.go +++ /dev/null @@ -1,63 +0,0 @@ -package debug - -import ( - "io" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/sharing/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/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.GetString()), - 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 deleted file mode 100644 index f69064d4f02..00000000000 --- a/extensions/sharing/pkg/tracing/tracing.go +++ /dev/null @@ -1,18 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/v2/extensions/sharing/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/owncloud/ocis/v2/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/cmd/storage-publiclink/main.go b/extensions/storage-publiclink/cmd/storage-publiclink/main.go deleted file mode 100644 index d620d0e65b3..00000000000 --- a/extensions/storage-publiclink/cmd/storage-publiclink/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/storage-publiclink/pkg/command" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 679d663f9fb..00000000000 --- a/extensions/storage-publiclink/pkg/command/health.go +++ /dev/null @@ -1,57 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/storage-publiclink/pkg/config" - "github.com/owncloud/ocis/v2/extensions/storage-publiclink/pkg/config/parser" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index c49903d06ef..00000000000 --- a/extensions/storage-publiclink/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/storage-publiclink/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 9a49633fd22..00000000000 --- a/extensions/storage-publiclink/pkg/command/server.go +++ /dev/null @@ -1,108 +0,0 @@ -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/v2/extensions/storage-publiclink/pkg/config" - "github.com/owncloud/ocis/v2/extensions/storage-publiclink/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/storage-publiclink/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/storage-publiclink/pkg/revaconfig" - "github.com/owncloud/ocis/v2/extensions/storage-publiclink/pkg/server/debug" - "github.com/owncloud/ocis/v2/extensions/storage-publiclink/pkg/tracing" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" - "github.com/owncloud/ocis/v2/ocis-pkg/sync" - "github.com/owncloud/ocis/v2/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) - os.Exit(1) - } - 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.GetString(), - 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/version.go b/extensions/storage-publiclink/pkg/command/version.go deleted file mode 100644 index cd3ab196b57..00000000000 --- a/extensions/storage-publiclink/pkg/command/version.go +++ /dev/null @@ -1,50 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - tw "github.com/olekukonko/tablewriter" - "github.com/owncloud/ocis/v2/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.GetString()) - 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/defaults/defaultconfig.go b/extensions/storage-publiclink/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index 0845d0a0243..00000000000 --- a/extensions/storage-publiclink/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,82 +0,0 @@ -package defaults - -import ( - "github.com/owncloud/ocis/v2/extensions/storage-publiclink/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:9179", - Token: "", - Pprof: false, - Zpages: false, - }, - GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9178", - Namespace: "com.owncloud.api", - Protocol: "tcp", - }, - Service: config.Service{ - Name: "storage-publiclink", - }, - Reva: &config.Reva{ - Address: "127.0.0.1:9142", - }, - StorageProvider: config.StorageProvider{ - 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.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-publiclink/pkg/config/parser/parse.go b/extensions/storage-publiclink/pkg/config/parser/parse.go deleted file mode 100644 index 03549280f27..00000000000 --- a/extensions/storage-publiclink/pkg/config/parser/parse.go +++ /dev/null @@ -1,42 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/storage-publiclink/pkg/config" - "github.com/owncloud/ocis/v2/extensions/storage-publiclink/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - if cfg.TokenManager.JWTSecret == "" { - return shared.MissingJWTTokenError(cfg.Service.Name) - } - - return nil -} diff --git a/extensions/storage-publiclink/pkg/logging/logging.go b/extensions/storage-publiclink/pkg/logging/logging.go deleted file mode 100644 index 1ee5114d9ab..00000000000 --- a/extensions/storage-publiclink/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/storage-publiclink/pkg/config" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 8a0f0285ed9..00000000000 --- a/extensions/storage-publiclink/pkg/revaconfig/config.go +++ /dev/null @@ -1,44 +0,0 @@ -package revaconfig - -import ( - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index cae95d69009..00000000000 --- a/extensions/storage-publiclink/pkg/server/debug/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/storage-publiclink/pkg/config" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 14a690a0b99..00000000000 --- a/extensions/storage-publiclink/pkg/server/debug/server.go +++ /dev/null @@ -1,63 +0,0 @@ -package debug - -import ( - "io" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/storage-publiclink/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/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.GetString()), - 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 deleted file mode 100644 index cc5342080e5..00000000000 --- a/extensions/storage-publiclink/pkg/tracing/tracing.go +++ /dev/null @@ -1,18 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/v2/extensions/storage-publiclink/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/owncloud/ocis/v2/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/cmd/storage-shares/main.go b/extensions/storage-shares/cmd/storage-shares/main.go deleted file mode 100644 index e663319fc9a..00000000000 --- a/extensions/storage-shares/cmd/storage-shares/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/storage-shares/pkg/command" - "github.com/owncloud/ocis/v2/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/health.go b/extensions/storage-shares/pkg/command/health.go deleted file mode 100644 index 0be6749a48d..00000000000 --- a/extensions/storage-shares/pkg/command/health.go +++ /dev/null @@ -1,57 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/storage-shares/pkg/config" - "github.com/owncloud/ocis/v2/extensions/storage-shares/pkg/config/parser" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 062a22e673c..00000000000 --- a/extensions/storage-shares/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/storage-shares/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index a782f0e3934..00000000000 --- a/extensions/storage-shares/pkg/command/server.go +++ /dev/null @@ -1,108 +0,0 @@ -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/v2/extensions/storage-shares/pkg/config" - "github.com/owncloud/ocis/v2/extensions/storage-shares/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/storage-shares/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/storage-shares/pkg/revaconfig" - "github.com/owncloud/ocis/v2/extensions/storage-shares/pkg/server/debug" - "github.com/owncloud/ocis/v2/extensions/storage-shares/pkg/tracing" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" - "github.com/owncloud/ocis/v2/ocis-pkg/sync" - "github.com/owncloud/ocis/v2/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) - os.Exit(1) - } - 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.GetString(), - 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 deleted file mode 100644 index c7c66653f0e..00000000000 --- a/extensions/storage-shares/pkg/command/version.go +++ /dev/null @@ -1,50 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - tw "github.com/olekukonko/tablewriter" - "github.com/owncloud/ocis/v2/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.GetString()) - 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/defaults/defaultconfig.go b/extensions/storage-shares/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index 9569e344010..00000000000 --- a/extensions/storage-shares/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,82 +0,0 @@ -package defaults - -import ( - "github.com/owncloud/ocis/v2/extensions/storage-shares/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:9156", - Token: "", - Pprof: false, - Zpages: false, - }, - GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9154", - Namespace: "com.owncloud.api", - Protocol: "tcp", - }, - Service: config.Service{ - Name: "storage-shares", - }, - Reva: &config.Reva{ - Address: "127.0.0.1:9142", - }, - MountID: "7639e57c-4433-4a12-8201-722fd0009154", - ReadOnly: false, - SharesProviderEndpoint: "localhost:9150", - } -} - -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-shares/pkg/config/parser/parse.go b/extensions/storage-shares/pkg/config/parser/parse.go deleted file mode 100644 index fb0bbf230bf..00000000000 --- a/extensions/storage-shares/pkg/config/parser/parse.go +++ /dev/null @@ -1,42 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/storage-shares/pkg/config" - "github.com/owncloud/ocis/v2/extensions/storage-shares/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - if cfg.TokenManager.JWTSecret == "" { - return shared.MissingJWTTokenError(cfg.Service.Name) - } - - return nil -} diff --git a/extensions/storage-shares/pkg/logging/logging.go b/extensions/storage-shares/pkg/logging/logging.go deleted file mode 100644 index b585f564b90..00000000000 --- a/extensions/storage-shares/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/storage-shares/pkg/config" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index c0155462217..00000000000 --- a/extensions/storage-shares/pkg/revaconfig/config.go +++ /dev/null @@ -1,39 +0,0 @@ -package revaconfig - -import ( - "github.com/owncloud/ocis/v2/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, - "mount_id": cfg.MountID, - }, - }, - }, - } - 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-shares/pkg/server/debug/option.go b/extensions/storage-shares/pkg/server/debug/option.go deleted file mode 100644 index a9e1af69fec..00000000000 --- a/extensions/storage-shares/pkg/server/debug/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/storage-shares/pkg/config" - "github.com/owncloud/ocis/v2/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-shares/pkg/server/debug/server.go b/extensions/storage-shares/pkg/server/debug/server.go deleted file mode 100644 index cf6a1af7023..00000000000 --- a/extensions/storage-shares/pkg/server/debug/server.go +++ /dev/null @@ -1,63 +0,0 @@ -package debug - -import ( - "io" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/storage-shares/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/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.GetString()), - 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 deleted file mode 100644 index d09325e024b..00000000000 --- a/extensions/storage-shares/pkg/tracing/tracing.go +++ /dev/null @@ -1,18 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/v2/extensions/storage-shares/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/owncloud/ocis/v2/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-system/cmd/storage-system/main.go b/extensions/storage-system/cmd/storage-system/main.go deleted file mode 100644 index 1eabece4702..00000000000 --- a/extensions/storage-system/cmd/storage-system/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/storage-system/pkg/command" - "github.com/owncloud/ocis/v2/extensions/storage-system/pkg/config/defaults" -) - -func main() { - if err := command.Execute(defaults.DefaultConfig()); err != nil { - os.Exit(1) - } -} diff --git a/extensions/storage-system/pkg/command/health.go b/extensions/storage-system/pkg/command/health.go deleted file mode 100644 index 52dc14efb5d..00000000000 --- a/extensions/storage-system/pkg/command/health.go +++ /dev/null @@ -1,57 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/storage-system/pkg/config" - "github.com/owncloud/ocis/v2/extensions/storage-system/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/storage-system/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-system/pkg/command/root.go b/extensions/storage-system/pkg/command/root.go deleted file mode 100644 index 4cc90487d0d..00000000000 --- a/extensions/storage-system/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/storage-system/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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-system command. -func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "storage-system", - Usage: "Provide system 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-system command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new storage-system.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.StorageSystem.Commons = cfg.Commons - return SutureService{ - cfg: cfg.StorageSystem, - } -} - -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-system/pkg/command/server.go b/extensions/storage-system/pkg/command/server.go deleted file mode 100644 index 3ece430e41f..00000000000 --- a/extensions/storage-system/pkg/command/server.go +++ /dev/null @@ -1,119 +0,0 @@ -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/v2/extensions/storage-system/pkg/config" - "github.com/owncloud/ocis/v2/extensions/storage-system/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/storage-system/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/storage-system/pkg/revaconfig" - "github.com/owncloud/ocis/v2/extensions/storage-system/pkg/server/debug" - "github.com/owncloud/ocis/v2/extensions/storage-system/pkg/tracing" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" - "github.com/owncloud/ocis/v2/ocis-pkg/sync" - "github.com/owncloud/ocis/v2/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) - os.Exit(1) - } - 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.StorageSystemFromStruct(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.GetString(), - 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.GetString(), - 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-system/pkg/command/version.go b/extensions/storage-system/pkg/command/version.go deleted file mode 100644 index 77c9277164c..00000000000 --- a/extensions/storage-system/pkg/command/version.go +++ /dev/null @@ -1,50 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - tw "github.com/olekukonko/tablewriter" - "github.com/owncloud/ocis/v2/extensions/storage-system/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.GetString()) - 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-system/pkg/config/defaults/defaultconfig.go b/extensions/storage-system/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index 7074e8f58b2..00000000000 --- a/extensions/storage-system/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,104 +0,0 @@ -package defaults - -import ( - "path/filepath" - - "github.com/owncloud/ocis/v2/extensions/storage-system/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" -) - -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:9217", - Token: "", - Pprof: false, - Zpages: false, - }, - GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9215", - Namespace: "com.owncloud.api", - Protocol: "tcp", - }, - HTTP: config.HTTPConfig{ - Addr: "127.0.0.1:9216", - Namespace: "com.owncloud.web", - Protocol: "tcp", - }, - Service: config.Service{ - Name: "storage-system", - }, - Reva: &config.Reva{ - Address: "127.0.0.1:9142", - }, - TempFolder: filepath.Join(defaults.BaseDataPath(), "tmp", "metadata"), - DataServerURL: "http://localhost:9216/data", - Driver: "ocis", - Drivers: config.Drivers{ - OCIS: config.OCISDriver{ - Root: filepath.Join(defaults.BaseDataPath(), "storage", "metadata"), - }, - }, - } -} - -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{} - } - - if cfg.SystemUserAPIKey == "" && cfg.Commons != nil && cfg.Commons.SystemUserAPIKey != "" { - cfg.SystemUserAPIKey = cfg.Commons.SystemUserAPIKey - } - - if cfg.SystemUserID == "" && cfg.Commons != nil && cfg.Commons.SystemUserID != "" { - cfg.SystemUserID = cfg.Commons.SystemUserID - } - -} - -func Sanitize(cfg *config.Config) { - // nothing to sanitize here atm -} diff --git a/extensions/storage-system/pkg/config/parser/parse.go b/extensions/storage-system/pkg/config/parser/parse.go deleted file mode 100644 index bb110555b60..00000000000 --- a/extensions/storage-system/pkg/config/parser/parse.go +++ /dev/null @@ -1,49 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/storage-system/pkg/config" - "github.com/owncloud/ocis/v2/extensions/storage-system/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - if cfg.TokenManager.JWTSecret == "" { - return shared.MissingJWTTokenError(cfg.Service.Name) - } - - if cfg.SystemUserAPIKey == "" { - return shared.MissingSystemUserApiKeyError(cfg.Service.Name) - } - - if cfg.SystemUserID == "" { - return shared.MissingSystemUserID(cfg.Service.Name) - } - return nil -} diff --git a/extensions/storage-system/pkg/logging/logging.go b/extensions/storage-system/pkg/logging/logging.go deleted file mode 100644 index 5071fda3ec3..00000000000 --- a/extensions/storage-system/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/storage-system/pkg/config" - "github.com/owncloud/ocis/v2/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-system/pkg/revaconfig/config.go b/extensions/storage-system/pkg/revaconfig/config.go deleted file mode 100644 index 0ef2b46b719..00000000000 --- a/extensions/storage-system/pkg/revaconfig/config.go +++ /dev/null @@ -1,130 +0,0 @@ -package revaconfig - -import ( - userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - "github.com/owncloud/ocis/v2/extensions/storage-system/pkg/config" -) - -// StorageSystemFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. -func StorageSystemFromStruct(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.SystemUserID, - "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.SystemUserAPIKey, - "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-system/pkg/server/debug/option.go b/extensions/storage-system/pkg/server/debug/option.go deleted file mode 100644 index a1b8225fcd9..00000000000 --- a/extensions/storage-system/pkg/server/debug/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/storage-system/pkg/config" - "github.com/owncloud/ocis/v2/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-system/pkg/server/debug/server.go b/extensions/storage-system/pkg/server/debug/server.go deleted file mode 100644 index 63d0348db3d..00000000000 --- a/extensions/storage-system/pkg/server/debug/server.go +++ /dev/null @@ -1,63 +0,0 @@ -package debug - -import ( - "io" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/storage-system/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/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.GetString()), - 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-system/pkg/tracing/tracing.go b/extensions/storage-system/pkg/tracing/tracing.go deleted file mode 100644 index 2a7b1de337e..00000000000 --- a/extensions/storage-system/pkg/tracing/tracing.go +++ /dev/null @@ -1,18 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/v2/extensions/storage-system/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/owncloud/ocis/v2/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/cmd/storage-users/main.go b/extensions/storage-users/cmd/storage-users/main.go deleted file mode 100644 index efd329fdfd9..00000000000 --- a/extensions/storage-users/cmd/storage-users/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/storage-users/pkg/command" - "github.com/owncloud/ocis/v2/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/health.go b/extensions/storage-users/pkg/command/health.go deleted file mode 100644 index fb8359bd518..00000000000 --- a/extensions/storage-users/pkg/command/health.go +++ /dev/null @@ -1,57 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/storage-users/pkg/config" - "github.com/owncloud/ocis/v2/extensions/storage-users/pkg/config/parser" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 9b22048030b..00000000000 --- a/extensions/storage-users/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/storage-users/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index e62f58f5c19..00000000000 --- a/extensions/storage-users/pkg/command/server.go +++ /dev/null @@ -1,108 +0,0 @@ -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/v2/extensions/storage-users/pkg/config" - "github.com/owncloud/ocis/v2/extensions/storage-users/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/storage-users/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/storage-users/pkg/revaconfig" - "github.com/owncloud/ocis/v2/extensions/storage-users/pkg/server/debug" - "github.com/owncloud/ocis/v2/extensions/storage-users/pkg/tracing" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" - "github.com/owncloud/ocis/v2/ocis-pkg/sync" - "github.com/owncloud/ocis/v2/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) - os.Exit(1) - } - 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.GetString(), - 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 deleted file mode 100644 index ff47a383b30..00000000000 --- a/extensions/storage-users/pkg/command/version.go +++ /dev/null @@ -1,50 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - tw "github.com/olekukonko/tablewriter" - "github.com/owncloud/ocis/v2/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.GetString()) - 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/defaults/defaultconfig.go b/extensions/storage-users/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index 3827096c9ca..00000000000 --- a/extensions/storage-users/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,127 +0,0 @@ -package defaults - -import ( - "path/filepath" - - "github.com/owncloud/ocis/v2/extensions/storage-users/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" -) - -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:9159", - Token: "", - Pprof: false, - Zpages: false, - }, - GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9157", - Namespace: "com.owncloud.api", - Protocol: "tcp", - }, - HTTP: config.HTTPConfig{ - Addr: "127.0.0.1:9158", - Namespace: "com.owncloud.web", - Protocol: "tcp", - Prefix: "data", - }, - Service: config.Service{ - Name: "storage-users", - }, - Reva: &config.Reva{ - Address: "127.0.0.1:9142", - }, - TempFolder: filepath.Join(defaults.BaseDataPath(), "tmp", "users"), - DataServerURL: "http://localhost:9158/data", - MountID: "1284d238-aa92-42ce-bdc4-0b0000009157", - Driver: "ocis", - Drivers: config.Drivers{ - OwnCloudSQL: config.OwnCloudSQLDriver{ - Root: filepath.Join(defaults.BaseDataPath(), "storage", "owncloud"), - ShareFolder: "/Shares", - UserLayout: "{{.Username}}", - UploadInfoDir: filepath.Join(defaults.BaseDataPath(), "storage", "uploadinfo"), - DBUsername: "owncloud", - DBPassword: "owncloud", - DBHost: "", - DBPort: 3306, - DBName: "owncloud", - UsersProviderEndpoint: "localhost:9144", - }, - S3NG: config.S3NGDriver{ - Root: filepath.Join(defaults.BaseDataPath(), "storage", "users"), - ShareFolder: "/Shares", - UserLayout: "{{.Id.OpaqueId}}", - Region: "default", - PersonalSpaceAliasTemplate: "{{.SpaceType}}/{{.User.Username | lower}}", - GeneralSpaceAliasTemplate: "{{.SpaceType}}/{{.SpaceName | replace \" \" \"-\" | lower}}", - PermissionsEndpoint: "127.0.0.1:9191", - }, - OCIS: config.OCISDriver{ - Root: filepath.Join(defaults.BaseDataPath(), "storage", "users"), - ShareFolder: "/Shares", - UserLayout: "{{.Id.OpaqueId}}", - PersonalSpaceAliasTemplate: "{{.SpaceType}}/{{.User.Username | lower}}", - GeneralSpaceAliasTemplate: "{{.SpaceType}}/{{.SpaceName | replace \" \" \"-\" | lower}}", - PermissionsEndpoint: "127.0.0.1:9191", - }, - }, - Events: config.Events{ - Addr: "127.0.0.1:9233", - ClusterID: "ocis-cluster", - }, - } -} - -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-users/pkg/config/parser/parse.go b/extensions/storage-users/pkg/config/parser/parse.go deleted file mode 100644 index ab352d004d5..00000000000 --- a/extensions/storage-users/pkg/config/parser/parse.go +++ /dev/null @@ -1,42 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/storage-users/pkg/config" - "github.com/owncloud/ocis/v2/extensions/storage-users/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - if cfg.TokenManager.JWTSecret == "" { - return shared.MissingJWTTokenError(cfg.Service.Name) - } - - return nil -} diff --git a/extensions/storage-users/pkg/logging/logging.go b/extensions/storage-users/pkg/logging/logging.go deleted file mode 100644 index d719f39b3ff..00000000000 --- a/extensions/storage-users/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/storage-users/pkg/config" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index 9dd8454f755..00000000000 --- a/extensions/storage-users/pkg/revaconfig/config.go +++ /dev/null @@ -1,69 +0,0 @@ -package revaconfig - -import ( - "github.com/owncloud/ocis/v2/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, - "nats_address": cfg.Events.Addr, - "nats_clusterID": cfg.Events.ClusterID, - }, - }, - }, - } - 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/server/debug/option.go b/extensions/storage-users/pkg/server/debug/option.go deleted file mode 100644 index 61ac5dd87cb..00000000000 --- a/extensions/storage-users/pkg/server/debug/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/storage-users/pkg/config" - "github.com/owncloud/ocis/v2/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 deleted file mode 100644 index feb67c0b4a9..00000000000 --- a/extensions/storage-users/pkg/server/debug/server.go +++ /dev/null @@ -1,63 +0,0 @@ -package debug - -import ( - "io" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/storage-users/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/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.GetString()), - 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 deleted file mode 100644 index 2806dd93b0c..00000000000 --- a/extensions/storage-users/pkg/tracing/tracing.go +++ /dev/null @@ -1,18 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/v2/extensions/storage-users/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/owncloud/ocis/v2/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/store/cmd/store/main.go b/extensions/store/cmd/store/main.go deleted file mode 100644 index db1f85bac01..00000000000 --- a/extensions/store/cmd/store/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/store/pkg/command" - "github.com/owncloud/ocis/v2/extensions/store/pkg/config/defaults" -) - -func main() { - if err := command.Execute(defaults.DefaultConfig()); err != nil { - os.Exit(1) - } -} diff --git a/extensions/store/pkg/command/health.go b/extensions/store/pkg/command/health.go deleted file mode 100644 index ccb7b8b7031..00000000000 --- a/extensions/store/pkg/command/health.go +++ /dev/null @@ -1,57 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/store/pkg/config" - "github.com/owncloud/ocis/v2/extensions/store/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/store/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/store/pkg/command/root.go b/extensions/store/pkg/command/root.go deleted file mode 100644 index 2d9a1d28800..00000000000 --- a/extensions/store/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/store/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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-store command. -func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "store", - Usage: "Service to store values for ocis extensions", - Commands: GetCommands(cfg), - }) - - cli.HelpFlag = &cli.BoolFlag{ - Name: "help,h", - Usage: "Show the help", - } - - return app.Run(os.Args) -} - -// SutureService allows for the store command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new store.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Store.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Store, - } -} - -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/store/pkg/command/server.go b/extensions/store/pkg/command/server.go deleted file mode 100644 index a6974086572..00000000000 --- a/extensions/store/pkg/command/server.go +++ /dev/null @@ -1,95 +0,0 @@ -package command - -import ( - "context" - "fmt" - "os" - - "github.com/oklog/run" - - "github.com/owncloud/ocis/v2/extensions/store/pkg/config" - "github.com/owncloud/ocis/v2/extensions/store/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/store/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/store/pkg/metrics" - "github.com/owncloud/ocis/v2/extensions/store/pkg/server/debug" - "github.com/owncloud/ocis/v2/extensions/store/pkg/server/grpc" - "github.com/owncloud/ocis/v2/extensions/store/pkg/tracing" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - "github.com/urfave/cli/v2" -) - -// Server is the entrypoint 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) - os.Exit(1) - } - return err - }, - Action: func(c *cli.Context) error { - logger := logging.Configure(cfg.Service.Name, cfg.Log) - err := tracing.Configure(cfg) - if err != nil { - return err - } - - var ( - gr = run.Group{} - ctx, cancel = func() (context.Context, context.CancelFunc) { - if cfg.Context == nil { - return context.WithCancel(context.Background()) - } - return context.WithCancel(cfg.Context) - }() - metrics = metrics.New() - ) - - defer cancel() - - metrics.BuildInfo.WithLabelValues(version.GetString()).Set(1) - - { - server := grpc.Server( - grpc.Logger(logger), - grpc.Context(ctx), - grpc.Config(cfg), - grpc.Metrics(metrics), - ) - - gr.Add(server.Run, func(_ error) { - logger.Info(). - Str("server", "grpc"). - Msg("Shutting down server") - - cancel() - }) - } - - { - server, err := debug.Server( - debug.Logger(logger), - debug.Context(ctx), - debug.Config(cfg), - ) - - if err != nil { - logger.Error().Err(err).Str("server", "debug").Msg("Failed to initialize server") - return err - } - - gr.Add(server.ListenAndServe, func(_ error) { - _ = server.Shutdown(ctx) - cancel() - }) - } - - return gr.Run() - }, - } -} diff --git a/extensions/store/pkg/command/version.go b/extensions/store/pkg/command/version.go deleted file mode 100644 index 5dcb649231c..00000000000 --- a/extensions/store/pkg/command/version.go +++ /dev/null @@ -1,50 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - tw "github.com/olekukonko/tablewriter" - "github.com/owncloud/ocis/v2/extensions/store/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.GetString()) - 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/store/pkg/config/defaults/defaultconfig.go b/extensions/store/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index 3f765cda5a0..00000000000 --- a/extensions/store/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,63 +0,0 @@ -package defaults - -import ( - "path" - - "github.com/owncloud/ocis/v2/extensions/store/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" -) - -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:9464", - Token: "", - Pprof: false, - Zpages: false, - }, - GRPC: config.GRPC{ - Addr: "127.0.0.1:9460", - Namespace: "com.owncloud.api", - }, - Service: config.Service{ - Name: "store", - }, - Datapath: path.Join(defaults.BaseDataPath(), "store"), - } -} - -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{} - } -} - -func Sanitize(cfg *config.Config) { - // nothing to sanitize here atm -} diff --git a/extensions/store/pkg/config/parser/parse.go b/extensions/store/pkg/config/parser/parse.go deleted file mode 100644 index 2729c98a1e8..00000000000 --- a/extensions/store/pkg/config/parser/parse.go +++ /dev/null @@ -1,38 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/store/pkg/config" - "github.com/owncloud/ocis/v2/extensions/store/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - // sanitize config - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - return nil -} diff --git a/extensions/store/pkg/logging/logging.go b/extensions/store/pkg/logging/logging.go deleted file mode 100644 index 62e29b14ab7..00000000000 --- a/extensions/store/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/store/pkg/config" - "github.com/owncloud/ocis/v2/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/store/pkg/server/debug/option.go b/extensions/store/pkg/server/debug/option.go deleted file mode 100644 index e0c72f37092..00000000000 --- a/extensions/store/pkg/server/debug/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/store/pkg/config" - "github.com/owncloud/ocis/v2/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/store/pkg/server/debug/server.go b/extensions/store/pkg/server/debug/server.go deleted file mode 100644 index e5f4e441675..00000000000 --- a/extensions/store/pkg/server/debug/server.go +++ /dev/null @@ -1,59 +0,0 @@ -package debug - -import ( - "io" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/store/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/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.GetString()), - 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)), - ), 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/store/pkg/server/grpc/option.go b/extensions/store/pkg/server/grpc/option.go deleted file mode 100644 index 2e3813dd40a..00000000000 --- a/extensions/store/pkg/server/grpc/option.go +++ /dev/null @@ -1,76 +0,0 @@ -package grpc - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/store/pkg/config" - "github.com/owncloud/ocis/v2/extensions/store/pkg/metrics" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/urfave/cli/v2" -) - -// Option defines a single option function. -type Option func(o *Options) - -// Options defines the available options for this package. -type Options struct { - Name string - Logger log.Logger - Context context.Context - Config *config.Config - Metrics *metrics.Metrics - Flags []cli.Flag -} - -// newOptions initializes the available default options. -func newOptions(opts ...Option) Options { - opt := Options{} - - for _, o := range opts { - o(&opt) - } - - return opt -} - -// Name provides a name for the service. -func Name(val string) Option { - return func(o *Options) { - o.Name = val - } -} - -// 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 - } -} - -// Metrics provides a function to set the metrics option. -func Metrics(val *metrics.Metrics) Option { - return func(o *Options) { - o.Metrics = val - } -} - -// Flags provides a function to set the flags option. -func Flags(val []cli.Flag) Option { - return func(o *Options) { - o.Flags = append(o.Flags, val...) - } -} diff --git a/extensions/store/pkg/server/grpc/server.go b/extensions/store/pkg/server/grpc/server.go deleted file mode 100644 index 2d7e918cb92..00000000000 --- a/extensions/store/pkg/server/grpc/server.go +++ /dev/null @@ -1,36 +0,0 @@ -package grpc - -import ( - svc "github.com/owncloud/ocis/v2/extensions/store/pkg/service/v0" - "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - storesvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/store/v0" -) - -// Server initializes a new go-micro service ready to run -func Server(opts ...Option) grpc.Service { - options := newOptions(opts...) - - service := grpc.NewService( - grpc.Namespace(options.Config.GRPC.Namespace), - grpc.Name(options.Config.Service.Name), - grpc.Version(version.GetString()), - grpc.Context(options.Context), - grpc.Address(options.Config.GRPC.Addr), - grpc.Logger(options.Logger), - grpc.Flags(options.Flags...), - ) - - hdlr, err := svc.New( - svc.Logger(options.Logger), - svc.Config(options.Config), - ) - if err != nil { - options.Logger.Fatal().Err(err).Msg("could not initialize service handler") - } - if err = storesvc.RegisterStoreHandler(service.Server(), hdlr); err != nil { - options.Logger.Fatal().Err(err).Msg("could not register service handler") - } - - return service -} diff --git a/extensions/store/pkg/service/v0/option.go b/extensions/store/pkg/service/v0/option.go deleted file mode 100644 index 7cd8f707896..00000000000 --- a/extensions/store/pkg/service/v0/option.go +++ /dev/null @@ -1,63 +0,0 @@ -package service - -import ( - "github.com/owncloud/ocis/v2/extensions/store/pkg/config" - "github.com/owncloud/ocis/v2/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 - Config *config.Config - - Database, Table string - Nodes []string -} - -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 - } -} - -// Database configures the database option. -func Database(val *config.Config) Option { - return func(o *Options) { - o.Config = val - } -} - -// Table configures the Table option. -func Table(val *config.Config) Option { - return func(o *Options) { - o.Config = val - } -} - -// Nodes configures the Nodes option. -func Nodes(val *config.Config) Option { - return func(o *Options) { - o.Config = val - } -} - -// Config configures the Config option. -func Config(val *config.Config) Option { - return func(o *Options) { - o.Config = val - } -} diff --git a/extensions/store/pkg/service/v0/service.go b/extensions/store/pkg/service/v0/service.go deleted file mode 100644 index 50919c2f0e7..00000000000 --- a/extensions/store/pkg/service/v0/service.go +++ /dev/null @@ -1,333 +0,0 @@ -package service - -import ( - "context" - "fmt" - "io/ioutil" - "os" - "path/filepath" - - "github.com/blevesearch/bleve/v2" - "github.com/blevesearch/bleve/v2/analysis/analyzer/keyword" - "github.com/owncloud/ocis/v2/extensions/store/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - storemsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/store/v0" - storesvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/store/v0" - merrors "go-micro.dev/v4/errors" - "google.golang.org/protobuf/encoding/protojson" -) - -// BleveDocument wraps the generated Record.Metadata and adds a property that is used to distinguish documents in the index. -type BleveDocument struct { - Metadata map[string]*storemsg.Field `json:"metadata"` - Database string `json:"database"` - Table string `json:"table"` -} - -// New returns a new instance of Service -func New(opts ...Option) (s *Service, err error) { - options := newOptions(opts...) - logger := options.Logger - cfg := options.Config - - recordsDir := filepath.Join(cfg.Datapath, "databases") - { - var fi os.FileInfo - if fi, err = os.Stat(recordsDir); err != nil { - if os.IsNotExist(err) { - // create store directory - if err = os.MkdirAll(recordsDir, 0700); err != nil { - return nil, err - } - } - } else if !fi.IsDir() { - return nil, fmt.Errorf("%s is not a directory", recordsDir) - } - } - - indexMapping := bleve.NewIndexMapping() - // keep all symbols in terms to allow exact matching, eg. emails - indexMapping.DefaultAnalyzer = keyword.Name - - s = &Service{ - id: cfg.GRPC.Namespace + "." + cfg.Service.Name, - log: logger, - Config: cfg, - } - - indexDir := filepath.Join(cfg.Datapath, "index.bleve") - // for now recreate index on every start - if err = os.RemoveAll(indexDir); err != nil { - return nil, err - } - if s.index, err = bleve.New(indexDir, indexMapping); err != nil { - return - } - if err = s.indexRecords(recordsDir); err != nil { - return nil, err - } - return -} - -// Service implements the AccountsServiceHandler interface -type Service struct { - id string - log log.Logger - Config *config.Config - index bleve.Index -} - -// Read implements the StoreHandler interface. -func (s *Service) Read(c context.Context, rreq *storesvc.ReadRequest, rres *storesvc.ReadResponse) error { - if len(rreq.Key) != 0 { - id := getID(rreq.Options.Database, rreq.Options.Table, rreq.Key) - file := filepath.Join(s.Config.Datapath, "databases", id) - - var data []byte - rec := &storemsg.Record{} - data, err := ioutil.ReadFile(file) - if err != nil { - return merrors.NotFound(s.id, "could not read record") - } - - if err = protojson.Unmarshal(data, rec); err != nil { - return merrors.InternalServerError(s.id, "could not unmarshal record") - } - - rres.Records = append(rres.Records, rec) - return nil - } - - s.log.Info().Interface("request", rreq).Msg("read request") - if rreq.Options.Where != nil { - // build bleve query - // execute search - // fetch the actual record if there's a hit - dtq := bleve.NewTermQuery(rreq.Options.Database) - ttq := bleve.NewTermQuery(rreq.Options.Table) - dtq.SetField("database") - ttq.SetField("table") - - query := bleve.NewConjunctionQuery(dtq, ttq) - for k, v := range rreq.Options.Where { - ntq := bleve.NewTermQuery(v.Value) - ntq.SetField("metadata." + k + ".value") - query.AddQuery(ntq) - } - - searchRequest := bleve.NewSearchRequest(query) - var searchResult *bleve.SearchResult - searchResult, err := s.index.Search(searchRequest) - if err != nil { - s.log.Error().Err(err).Msg("could not execute bleve search") - return merrors.InternalServerError(s.id, "could not execute bleve search: %v", err.Error()) - } - - for _, hit := range searchResult.Hits { - rec := &storemsg.Record{} - - dest := filepath.Join(s.Config.Datapath, "databases", hit.ID) - - var data []byte - data, err := ioutil.ReadFile(dest) - s.log.Info().Str("path", dest).Interface("hit", hit).Msgf("hit info") - if err != nil { - s.log.Info().Str("path", dest).Interface("hit", hit).Msgf("file not found") - return merrors.NotFound(s.id, "could not read record") - } - - if err = protojson.Unmarshal(data, rec); err != nil { - return merrors.InternalServerError(s.id, "could not unmarshal record") - } - - rres.Records = append(rres.Records, rec) - } - return nil - } - - return merrors.InternalServerError(s.id, "neither id nor metadata present") -} - -// Write implements the StoreHandler interface. -func (s *Service) Write(c context.Context, wreq *storesvc.WriteRequest, wres *storesvc.WriteResponse) error { - id := getID(wreq.Options.Database, wreq.Options.Table, wreq.Record.Key) - file := filepath.Join(s.Config.Datapath, "databases", id) - - var bytes []byte - bytes, err := protojson.Marshal(wreq.Record) - if err != nil { - return merrors.InternalServerError(s.id, "could not marshal record") - } - - err = os.MkdirAll(filepath.Dir(file), 0700) - if err != nil { - return err - } - err = ioutil.WriteFile(file, bytes, 0600) - if err != nil { - return merrors.InternalServerError(s.id, "could not write record") - } - - doc := BleveDocument{ - Metadata: wreq.Record.Metadata, - Database: wreq.Options.Database, - Table: wreq.Options.Table, - } - if err := s.index.Index(id, doc); err != nil { - s.log.Error().Err(err).Interface("document", doc).Msg("could not index record metadata") - return err - } - - return nil -} - -// Delete implements the StoreHandler interface. -func (s *Service) Delete(c context.Context, dreq *storesvc.DeleteRequest, dres *storesvc.DeleteResponse) error { - id := getID(dreq.Options.Database, dreq.Options.Table, dreq.Key) - file := filepath.Join(s.Config.Datapath, "databases", id) - if err := os.Remove(file); err != nil { - if os.IsNotExist(err) { - return merrors.NotFound(s.id, "could not find record") - } - - return merrors.InternalServerError(s.id, "could not delete record") - } - - if err := s.index.Delete(id); err != nil { - s.log.Error().Err(err).Str("id", id).Msg("could not remove record from index") - return merrors.InternalServerError(s.id, "could not remove record from index") - } - - return nil -} - -// List implements the StoreHandler interface. -func (s *Service) List(context.Context, *storesvc.ListRequest, storesvc.Store_ListStream) error { - return nil -} - -// Databases implements the StoreHandler interface. -func (s *Service) Databases(c context.Context, dbreq *storesvc.DatabasesRequest, dbres *storesvc.DatabasesResponse) error { - file := filepath.Join(s.Config.Datapath, "databases") - f, err := os.Open(file) - if err != nil { - return merrors.InternalServerError(s.id, "could not open database directory") - } - defer f.Close() - - dnames, err := f.Readdirnames(0) - if err != nil { - return merrors.InternalServerError(s.id, "could not read database directory") - } - - dbres.Databases = dnames - return nil -} - -// Tables implements the StoreHandler interface. -func (s *Service) Tables(ctx context.Context, in *storesvc.TablesRequest, out *storesvc.TablesResponse) error { - file := filepath.Join(s.Config.Datapath, "databases", in.Database) - f, err := os.Open(file) - if err != nil { - return merrors.InternalServerError(s.id, "could not open tables directory") - } - defer f.Close() - - tnames, err := f.Readdirnames(0) - if err != nil { - return merrors.InternalServerError(s.id, "could not read tables directory") - } - - out.Tables = tnames - return nil -} - -// TODO sanitize key. As it may contain invalid characters, such as slashes. -// file: /tmp/ocis-store/databases/{database}/{table}/{record.key}. -func getID(database string, table string, key string) string { - // TODO sanitize input. - return filepath.Join(database, table, key) -} - -func (s Service) indexRecords(recordsDir string) (err error) { - - // TODO use filepath.Walk to clean up code - rh, err := os.Open(recordsDir) - if err != nil { - return merrors.InternalServerError(s.id, "could not open database directory") - } - defer rh.Close() - - dbs, err := rh.Readdirnames(0) - if err != nil { - return merrors.InternalServerError(s.id, "could not read databases directory") - } - - for i := range dbs { - tp := filepath.Join(s.Config.Datapath, "databases", dbs[i]) - th, err := os.Open(tp) - if err != nil { - s.log.Error().Err(err).Str("database", dbs[i]).Msg("could not open database directory") - continue - } - defer th.Close() - - tables, err := th.Readdirnames(0) - if err != nil { - s.log.Error().Err(err).Str("database", dbs[i]).Msg("could not read database directory") - continue - } - - for j := range tables { - - tp := filepath.Join(s.Config.Datapath, "databases", dbs[i], tables[j]) - kh, err := os.Open(tp) - if err != nil { - s.log.Error().Err(err).Str("database", dbs[i]).Str("table", tables[i]).Msg("could not open table directory") - continue - } - defer kh.Close() - - keys, err := kh.Readdirnames(0) - if err != nil { - s.log.Error().Err(err).Str("database", dbs[i]).Str("table", tables[i]).Msg("could not read table directory") - continue - } - - for k := range keys { - - id := getID(dbs[i], tables[j], keys[k]) - kp := filepath.Join(s.Config.Datapath, "databases", id) - - // read record - var data []byte - rec := &storemsg.Record{} - data, err = ioutil.ReadFile(kp) - if err != nil { - s.log.Error().Err(err).Str("id", id).Msg("could not read record") - continue - } - - if err = protojson.Unmarshal(data, rec); err != nil { - s.log.Error().Err(err).Str("id", id).Msg("could not unmarshal record") - continue - } - - // index record - doc := BleveDocument{ - Metadata: rec.Metadata, - Database: dbs[i], - Table: tables[j], - } - if err := s.index.Index(id, doc); err != nil { - s.log.Error().Err(err).Interface("document", doc).Str("id", id).Msg("could not index record metadata") - continue - } - - s.log.Debug().Str("id", id).Msg("indexed record") - } - } - } - - return -} diff --git a/extensions/store/pkg/tracing/tracing.go b/extensions/store/pkg/tracing/tracing.go deleted file mode 100644 index 4bb2401ad3f..00000000000 --- a/extensions/store/pkg/tracing/tracing.go +++ /dev/null @@ -1,23 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/v2/extensions/store/pkg/config" - pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" - "go.opentelemetry.io/otel/trace" -) - -var ( - // TraceProvider is the global trace provider for the store service. - TraceProvider = trace.NewNoopTracerProvider() -) - -func Configure(cfg *config.Config) error { - var err error - if cfg.Tracing.Enabled { - if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { - return err - } - } - - return nil -} diff --git a/extensions/thumbnails/cmd/thumbnails/main.go b/extensions/thumbnails/cmd/thumbnails/main.go deleted file mode 100644 index 43d9153c763..00000000000 --- a/extensions/thumbnails/cmd/thumbnails/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/command" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/config/defaults" -) - -func main() { - if err := command.Execute(defaults.DefaultConfig()); err != nil { - os.Exit(1) - } -} diff --git a/extensions/thumbnails/pkg/command/health.go b/extensions/thumbnails/pkg/command/health.go deleted file mode 100644 index 94f2322bd44..00000000000 --- a/extensions/thumbnails/pkg/command/health.go +++ /dev/null @@ -1,57 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/config" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/thumbnails/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/thumbnails/pkg/command/root.go b/extensions/thumbnails/pkg/command/root.go deleted file mode 100644 index f270959d2ad..00000000000 --- a/extensions/thumbnails/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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-thumbnails command. -func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "thumbnails", - Usage: "Example usage", - Commands: GetCommands(cfg), - }) - - cli.HelpFlag = &cli.BoolFlag{ - Name: "help,h", - Usage: "Show the help", - } - - return app.Run(os.Args) -} - -// SutureService allows for the thumbnails command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new thumbnails.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Thumbnails.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Thumbnails, - } -} - -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/thumbnails/pkg/command/server.go b/extensions/thumbnails/pkg/command/server.go deleted file mode 100644 index 85efcc7f711..00000000000 --- a/extensions/thumbnails/pkg/command/server.go +++ /dev/null @@ -1,112 +0,0 @@ -package command - -import ( - "context" - "fmt" - "os" - - "github.com/oklog/run" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/config" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/metrics" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/server/debug" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/server/grpc" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/server/http" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/tracing" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - "github.com/urfave/cli/v2" -) - -// Server is the entrypoint 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) - os.Exit(1) - } - return err - }, - Action: func(c *cli.Context) error { - logger := logging.Configure(cfg.Service.Name, cfg.Log) - err := tracing.Configure(cfg) - if err != nil { - return err - } - - var ( - gr = run.Group{} - ctx, cancel = func() (context.Context, context.CancelFunc) { - if cfg.Context == nil { - return context.WithCancel(context.Background()) - } - return context.WithCancel(cfg.Context) - }() - metrics = metrics.New() - ) - - defer cancel() - - metrics.BuildInfo.WithLabelValues(version.GetString()).Set(1) - - service := grpc.NewService( - grpc.Logger(logger), - grpc.Context(ctx), - grpc.Config(cfg), - grpc.Name(cfg.Service.Name), - grpc.Namespace(cfg.GRPC.Namespace), - grpc.Address(cfg.GRPC.Addr), - grpc.Metrics(metrics), - ) - - gr.Add(service.Run, func(_ error) { - fmt.Println("shutting down grpc server") - cancel() - }) - - server, err := debug.Server( - debug.Logger(logger), - debug.Config(cfg), - ) - - if err != nil { - logger.Info().Err(err).Str("transport", "debug").Msg("Failed to initialize server") - return err - } - - gr.Add(server.ListenAndServe, func(_ error) { - _ = server.Shutdown(ctx) - cancel() - }) - - httpServer, err := http.Server( - http.Logger(logger), - http.Context(ctx), - http.Config(cfg), - http.Metrics(metrics), - http.Namespace(cfg.HTTP.Namespace), - ) - - if err != nil { - logger.Info(). - Err(err). - Str("transport", "http"). - Msg("Failed to initialize server") - - return err - } - - gr.Add(httpServer.Run, func(_ error) { - logger.Info().Str("server", "http").Msg("shutting down server") - cancel() - }) - - return gr.Run() - }, - } -} diff --git a/extensions/thumbnails/pkg/command/version.go b/extensions/thumbnails/pkg/command/version.go deleted file mode 100644 index ae4879b7fe9..00000000000 --- a/extensions/thumbnails/pkg/command/version.go +++ /dev/null @@ -1,50 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - tw "github.com/olekukonko/tablewriter" - "github.com/owncloud/ocis/v2/extensions/thumbnails/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.GetString()) - 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/thumbnails/pkg/config/defaults/defaultconfig.go b/extensions/thumbnails/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index 3a179e0f5e6..00000000000 --- a/extensions/thumbnails/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,81 +0,0 @@ -package defaults - -import ( - "path" - "strings" - - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" -) - -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:9189", - Token: "", - Pprof: false, - Zpages: false, - }, - GRPC: config.GRPC{ - Addr: "127.0.0.1:9185", - Namespace: "com.owncloud.api", - }, - HTTP: config.HTTP{ - Addr: "127.0.0.1:9186", - Root: "/thumbnails", - Namespace: "com.owncloud.web", - }, - Service: config.Service{ - Name: "thumbnails", - }, - Thumbnail: config.Thumbnail{ - Resolutions: []string{"16x16", "32x32", "64x64", "128x128", "1920x1080", "3840x2160", "7680x4320"}, - FileSystemStorage: config.FileSystemStorage{ - RootDirectory: path.Join(defaults.BaseDataPath(), "thumbnails"), - }, - WebdavAllowInsecure: false, - RevaGateway: "127.0.0.1:9142", - CS3AllowInsecure: false, - DataEndpoint: "http://127.0.0.1:9186/thumbnails/data", - }, - } -} - -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{} - } -} - -func Sanitize(cfg *config.Config) { - // nothing to sanitize here atm - if len(cfg.Thumbnail.Resolutions) == 1 && strings.Contains(cfg.Thumbnail.Resolutions[0], ",") { - cfg.Thumbnail.Resolutions = strings.Split(cfg.Thumbnail.Resolutions[0], ",") - } -} diff --git a/extensions/thumbnails/pkg/config/parser/parse.go b/extensions/thumbnails/pkg/config/parser/parse.go deleted file mode 100644 index 0a36b2a32bd..00000000000 --- a/extensions/thumbnails/pkg/config/parser/parse.go +++ /dev/null @@ -1,38 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/config" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - // sanitize config - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - return nil -} diff --git a/extensions/thumbnails/pkg/logging/logging.go b/extensions/thumbnails/pkg/logging/logging.go deleted file mode 100644 index acdafff1b87..00000000000 --- a/extensions/thumbnails/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/config" - "github.com/owncloud/ocis/v2/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/thumbnails/pkg/server/debug/option.go b/extensions/thumbnails/pkg/server/debug/option.go deleted file mode 100644 index dfced5ae678..00000000000 --- a/extensions/thumbnails/pkg/server/debug/option.go +++ /dev/null @@ -1,42 +0,0 @@ -package debug - -import ( - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/config" - "github.com/owncloud/ocis/v2/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 { - Name string - Address string - Logger log.Logger - 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 - } -} - -// 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/thumbnails/pkg/server/debug/server.go b/extensions/thumbnails/pkg/server/debug/server.go deleted file mode 100644 index 13123e33d99..00000000000 --- a/extensions/thumbnails/pkg/server/debug/server.go +++ /dev/null @@ -1,59 +0,0 @@ -package debug - -import ( - "io" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/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.GetString()), - 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)), - ), 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/thumbnails/pkg/server/grpc/option.go b/extensions/thumbnails/pkg/server/grpc/option.go deleted file mode 100644 index c5cd4f3e300..00000000000 --- a/extensions/thumbnails/pkg/server/grpc/option.go +++ /dev/null @@ -1,92 +0,0 @@ -package grpc - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/config" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/metrics" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/urfave/cli/v2" -) - -// Option defines a single option function. -type Option func(o *Options) - -// Options defines the available options for this package. -type Options struct { - Name string - Address string - Logger log.Logger - Context context.Context - Config *config.Config - Metrics *metrics.Metrics - Namespace string - Flags []cli.Flag -} - -// 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 - } -} - -// Name provides a name for the service. -func Name(val string) Option { - return func(o *Options) { - o.Name = val - } -} - -// Address provides an address for the service. -func Address(val string) Option { - return func(o *Options) { - o.Address = 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 - } -} - -// Metrics provides a function to set the metrics option. -func Metrics(val *metrics.Metrics) Option { - return func(o *Options) { - o.Metrics = val - } -} - -// Namespace provides a function to set the namespace option. -func Namespace(val string) Option { - return func(o *Options) { - o.Namespace = val - } -} - -// Flags provides a function to set the flags option. -func Flags(flags []cli.Flag) Option { - return func(o *Options) { - o.Flags = append(o.Flags, flags...) - } -} diff --git a/extensions/thumbnails/pkg/server/grpc/server.go b/extensions/thumbnails/pkg/server/grpc/server.go deleted file mode 100644 index 1eee17df2a3..00000000000 --- a/extensions/thumbnails/pkg/server/grpc/server.go +++ /dev/null @@ -1,60 +0,0 @@ -package grpc - -import ( - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" - svc "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/service/grpc/v0" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/service/grpc/v0/decorators" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/thumbnail/imgsource" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/thumbnail/storage" - "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - thumbnailssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/thumbnails/v0" -) - -// NewService initializes the grpc service and server. -func NewService(opts ...Option) grpc.Service { - options := newOptions(opts...) - - service := grpc.NewService( - grpc.Logger(options.Logger), - grpc.Namespace(options.Namespace), - grpc.Name(options.Name), - grpc.Version(version.GetString()), - grpc.Address(options.Address), - grpc.Context(options.Context), - grpc.Flags(options.Flags...), - grpc.Version(version.GetString()), - ) - tconf := options.Config.Thumbnail - gc, err := pool.GetGatewayServiceClient(tconf.RevaGateway) - if err != nil { - options.Logger.Error().Err(err).Msg("could not get gateway client") - return grpc.Service{} - } - var thumbnail decorators.DecoratedService - { - thumbnail = svc.NewService( - svc.Config(options.Config), - svc.Logger(options.Logger), - svc.ThumbnailSource(imgsource.NewWebDavSource(tconf)), - svc.ThumbnailStorage( - storage.NewFileSystemStorage( - tconf.FileSystemStorage, - options.Logger, - ), - ), - svc.CS3Source(imgsource.NewCS3Source(tconf, gc)), - svc.CS3Client(gc), - ) - thumbnail = decorators.NewInstrument(thumbnail, options.Metrics) - thumbnail = decorators.NewLogging(thumbnail, options.Logger) - thumbnail = decorators.NewTracing(thumbnail) - } - - _ = thumbnailssvc.RegisterThumbnailServiceHandler( - service.Server(), - thumbnail, - ) - - return service -} diff --git a/extensions/thumbnails/pkg/server/http/option.go b/extensions/thumbnails/pkg/server/http/option.go deleted file mode 100644 index 5cdd52aa83a..00000000000 --- a/extensions/thumbnails/pkg/server/http/option.go +++ /dev/null @@ -1,69 +0,0 @@ -package http - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/config" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/metrics" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/urfave/cli/v2" -) - -// Option defines a single option function. -type Option func(o *Options) - -// Options defines the available options for this package. -type Options struct { - Namespace string - Logger log.Logger - Context context.Context - Config *config.Config - Metrics *metrics.Metrics - Flags []cli.Flag -} - -// 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 - } -} - -// Metrics provides a function to set the metrics option. -func Metrics(val *metrics.Metrics) Option { - return func(o *Options) { - o.Metrics = val - } -} - -// Namespace provides a function to set the Namespace option. -func Namespace(val string) Option { - return func(o *Options) { - o.Namespace = val - } -} diff --git a/extensions/thumbnails/pkg/server/http/server.go b/extensions/thumbnails/pkg/server/http/server.go deleted file mode 100644 index 540dce15529..00000000000 --- a/extensions/thumbnails/pkg/server/http/server.go +++ /dev/null @@ -1,58 +0,0 @@ -package http - -import ( - "github.com/go-chi/chi/v5/middleware" - svc "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/service/http/v0" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/thumbnail/storage" - ocismiddleware "github.com/owncloud/ocis/v2/ocis-pkg/middleware" - "github.com/owncloud/ocis/v2/ocis-pkg/service/http" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - "go-micro.dev/v4" -) - -// Server initializes the http service and server. -func Server(opts ...Option) (http.Service, error) { - options := newOptions(opts...) - - service := http.NewService( - http.Logger(options.Logger), - http.Name(options.Config.Service.Name), - http.Version(version.GetString()), - http.Namespace(options.Config.HTTP.Namespace), - http.Address(options.Config.HTTP.Addr), - http.Context(options.Context), - ) - - handle := svc.NewService( - svc.Logger(options.Logger), - svc.Config(options.Config), - svc.Middleware( - middleware.RealIP, - middleware.RequestID, - // ocismiddleware.Secure, - ocismiddleware.Version( - options.Config.Service.Name, - version.GetString(), - ), - ocismiddleware.Logger(options.Logger), - ), - svc.ThumbnailStorage( - storage.NewFileSystemStorage( - options.Config.Thumbnail.FileSystemStorage, - options.Logger, - ), - ), - ) - - { - handle = svc.NewInstrument(handle, options.Metrics) - handle = svc.NewLogging(handle, options.Logger) - handle = svc.NewTracing(handle) - } - - if err := micro.RegisterHandler(service.Server(), handle); err != nil { - return http.Service{}, err - } - - return service, nil -} diff --git a/extensions/thumbnails/pkg/service/grpc/v0/decorators/instrument.go b/extensions/thumbnails/pkg/service/grpc/v0/decorators/instrument.go deleted file mode 100644 index df1359417ee..00000000000 --- a/extensions/thumbnails/pkg/service/grpc/v0/decorators/instrument.go +++ /dev/null @@ -1,39 +0,0 @@ -package decorators - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/metrics" - thumbnailssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/thumbnails/v0" - "github.com/prometheus/client_golang/prometheus" -) - -// NewInstrument returns a service that instruments metrics. -func NewInstrument(next DecoratedService, metrics *metrics.Metrics) DecoratedService { - return instrument{ - Decorator: Decorator{next: next}, - metrics: metrics, - } -} - -type instrument struct { - Decorator - metrics *metrics.Metrics -} - -// GetThumbnail implements the ThumbnailServiceHandler interface. -func (i instrument) GetThumbnail(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, rsp *thumbnailssvc.GetThumbnailResponse) error { - timer := prometheus.NewTimer(prometheus.ObserverFunc(func(v float64) { - us := v * 1000_000 - i.metrics.Latency.WithLabelValues().Observe(us) - i.metrics.Duration.WithLabelValues().Observe(v) - })) - defer timer.ObserveDuration() - - err := i.next.GetThumbnail(ctx, req, rsp) - - if err != nil { - i.metrics.Counter.WithLabelValues().Inc() - } - return err -} diff --git a/extensions/thumbnails/pkg/service/grpc/v0/decorators/tracing.go b/extensions/thumbnails/pkg/service/grpc/v0/decorators/tracing.go deleted file mode 100644 index 1f5f24dc235..00000000000 --- a/extensions/thumbnails/pkg/service/grpc/v0/decorators/tracing.go +++ /dev/null @@ -1,42 +0,0 @@ -package decorators - -import ( - "context" - - "go.opentelemetry.io/otel/trace" - - thumbnailsTracing "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/tracing" - thumbnailssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/thumbnails/v0" - "go.opentelemetry.io/otel/attribute" -) - -// NewTracing returns a service that instruments traces. -func NewTracing(next DecoratedService) DecoratedService { - return tracing{ - Decorator: Decorator{next: next}, - } -} - -type tracing struct { - Decorator -} - -// GetThumbnail implements the ThumbnailServiceHandler interface. -func (t tracing) GetThumbnail(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, rsp *thumbnailssvc.GetThumbnailResponse) error { - var span trace.Span - - if thumbnailsTracing.TraceProvider != nil { - tracer := thumbnailsTracing.TraceProvider.Tracer("thumbnails") - ctx, span = tracer.Start(ctx, "Thumbnails.GetThumbnail") - defer span.End() - - span.SetAttributes( - attribute.KeyValue{Key: "filepath", Value: attribute.StringValue(req.Filepath)}, - attribute.KeyValue{Key: "thumbnail_type", Value: attribute.StringValue(req.ThumbnailType.String())}, - attribute.KeyValue{Key: "width", Value: attribute.IntValue(int(req.Width))}, - attribute.KeyValue{Key: "height", Value: attribute.IntValue(int(req.Height))}, - ) - } - - return t.next.GetThumbnail(ctx, req, rsp) -} diff --git a/extensions/thumbnails/pkg/service/grpc/v0/option.go b/extensions/thumbnails/pkg/service/grpc/v0/option.go deleted file mode 100644 index e62d9613309..00000000000 --- a/extensions/thumbnails/pkg/service/grpc/v0/option.go +++ /dev/null @@ -1,84 +0,0 @@ -package svc - -import ( - "net/http" - - gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" - - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/config" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/thumbnail/imgsource" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/thumbnail/storage" - "github.com/owncloud/ocis/v2/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 - Config *config.Config - Middleware []func(http.Handler) http.Handler - ThumbnailStorage storage.Storage - ImageSource imgsource.Source - CS3Source imgsource.Source - CS3Client gateway.GatewayAPIClient -} - -// 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 - } -} - -// Config provides a function to set the config option. -func Config(val *config.Config) Option { - return func(o *Options) { - o.Config = val - } -} - -// Middleware provides a function to set the middleware option. -func Middleware(val ...func(http.Handler) http.Handler) Option { - return func(o *Options) { - o.Middleware = val - } -} - -// ThumbnailStorage provides a function to set the thumbnail storage option. -func ThumbnailStorage(val storage.Storage) Option { - return func(o *Options) { - o.ThumbnailStorage = val - } -} - -// ThumbnailSource provides a function to set the image source option. -func ThumbnailSource(val imgsource.Source) Option { - return func(o *Options) { - o.ImageSource = val - } -} - -func CS3Source(val imgsource.Source) Option { - return func(o *Options) { - o.CS3Source = val - } -} - -func CS3Client(c gateway.GatewayAPIClient) Option { - return func(o *Options) { - o.CS3Client = c - } -} diff --git a/extensions/thumbnails/pkg/service/grpc/v0/service.go b/extensions/thumbnails/pkg/service/grpc/v0/service.go deleted file mode 100644 index ba6c35f929d..00000000000 --- a/extensions/thumbnails/pkg/service/grpc/v0/service.go +++ /dev/null @@ -1,295 +0,0 @@ -package svc - -import ( - "context" - "image" - "net/url" - "path" - "strings" - "time" - - gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" - 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/v2/extensions/thumbnails/pkg/preprocessor" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/service/grpc/v0/decorators" - tjwt "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/service/jwt" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/thumbnail" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/thumbnail/imgsource" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - thumbnailsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/thumbnails/v0" - thumbnailssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/thumbnails/v0" - "github.com/pkg/errors" - merrors "go-micro.dev/v4/errors" - "google.golang.org/grpc/metadata" -) - -// NewService returns a service implementation for Service. -func NewService(opts ...Option) decorators.DecoratedService { - options := newOptions(opts...) - logger := options.Logger - resolutions, err := thumbnail.ParseResolutions(options.Config.Thumbnail.Resolutions) - if err != nil { - logger.Fatal().Err(err).Msg("resolutions not configured correctly") - } - svc := Thumbnail{ - serviceID: options.Config.GRPC.Namespace + "." + options.Config.Service.Name, - manager: thumbnail.NewSimpleManager( - resolutions, - options.ThumbnailStorage, - logger, - ), - webdavSource: options.ImageSource, - cs3Source: options.CS3Source, - logger: logger, - cs3Client: options.CS3Client, - preprocessorOpts: PreprocessorOpts{ - TxtFontFileMap: options.Config.Thumbnail.FontMapFile, - }, - dataEndpoint: options.Config.Thumbnail.DataEndpoint, - transferSecret: options.Config.Thumbnail.TransferSecret, - } - - return svc -} - -// Thumbnail implements the GRPC handler. -type Thumbnail struct { - serviceID string - dataEndpoint string - transferSecret string - manager thumbnail.Manager - webdavSource imgsource.Source - cs3Source imgsource.Source - logger log.Logger - cs3Client gateway.GatewayAPIClient - preprocessorOpts PreprocessorOpts -} - -type PreprocessorOpts struct { - TxtFontFileMap string -} - -// GetThumbnail retrieves a thumbnail for an image -func (g Thumbnail) GetThumbnail(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, rsp *thumbnailssvc.GetThumbnailResponse) error { - tType, ok := thumbnailsmsg.ThumbnailType_name[int32(req.ThumbnailType)] - if !ok { - g.logger.Debug().Str("thumbnail_type", tType).Msg("unsupported thumbnail type") - return nil - } - generator, err := thumbnail.GeneratorForType(tType) - if err != nil { - g.logger.Debug().Str("thumbnail_type", tType).Msg("unsupported thumbnail type") - return nil - } - encoder, err := thumbnail.EncoderForType(tType) - if err != nil { - g.logger.Debug().Str("thumbnail_type", tType).Msg("unsupported thumbnail type") - return nil - } - - var key string - switch { - case req.GetWebdavSource() != nil: - key, err = g.handleWebdavSource(ctx, req, generator, encoder) - case req.GetCs3Source() != nil: - key, err = g.handleCS3Source(ctx, req, generator, encoder) - default: - g.logger.Error().Msg("no image source provided") - return merrors.BadRequest(g.serviceID, "image source is missing") - } - if err != nil { - return err - } - - claims := tjwt.ThumbnailClaims{ - Key: key, - RegisteredClaims: jwt.RegisteredClaims{ - IssuedAt: jwt.NewNumericDate(time.Now()), - ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Minute)), - }, - } - - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - transferToken, err := token.SignedString([]byte(g.transferSecret)) - if err != nil { - g.logger.Error(). - Err(err). - Msg("GetThumbnail: failed to sign token") - return merrors.InternalServerError(g.serviceID, "couldn't finish request") - } - rsp.DataEndpoint = g.dataEndpoint - rsp.TransferToken = transferToken - rsp.Mimetype = encoder.MimeType() - return nil -} - -func (g Thumbnail) handleCS3Source(ctx context.Context, - req *thumbnailssvc.GetThumbnailRequest, - generator thumbnail.Generator, - encoder thumbnail.Encoder) (string, error) { - src := req.GetCs3Source() - sRes, err := g.stat(src.Path, src.Authorization) - if err != nil { - return "", err - } - - tr := thumbnail.Request{ - Resolution: image.Rect(0, 0, int(req.Width), int(req.Height)), - Generator: generator, - Encoder: encoder, - Checksum: sRes.GetInfo().GetChecksum().GetSum(), - } - - if key, exists := g.manager.CheckThumbnail(tr); exists { - return key, nil - } - - ctx = imgsource.ContextSetAuthorization(ctx, src.Authorization) - r, err := g.cs3Source.Get(ctx, src.Path) - if err != nil { - return "", merrors.InternalServerError(g.serviceID, "could not get image from source: %s", err.Error()) - } - defer r.Close() // nolint:errcheck - ppOpts := map[string]interface{}{ - "fontFileMap": g.preprocessorOpts.TxtFontFileMap, - } - pp := preprocessor.ForType(sRes.GetInfo().GetMimeType(), ppOpts) - img, err := pp.Convert(r) - if img == nil || err != nil { - return "", merrors.InternalServerError(g.serviceID, "could not get image") - } - - key, err := g.manager.Generate(tr, img) - if err != nil { - return "", err - } - return key, nil -} - -func (g Thumbnail) handleWebdavSource(ctx context.Context, - req *thumbnailssvc.GetThumbnailRequest, - generator thumbnail.Generator, - encoder thumbnail.Encoder) (string, error) { - src := req.GetWebdavSource() - imgURL, err := url.Parse(src.Url) - if err != nil { - return "", errors.Wrap(err, "source url is invalid") - } - - var auth, statPath string - - if src.IsPublicLink { - q := imgURL.Query() - var rsp *gateway.AuthenticateResponse - if q.Get("signature") != "" && q.Get("expiration") != "" { - // Handle pre-signed public links - sig := q.Get("signature") - exp := q.Get("expiration") - rsp, err = g.cs3Client.Authenticate(ctx, &gateway.AuthenticateRequest{ - Type: "publicshares", - ClientId: src.PublicLinkToken, - ClientSecret: strings.Join([]string{"signature", sig, exp}, "|"), - }) - } else { - rsp, err = g.cs3Client.Authenticate(ctx, &gateway.AuthenticateRequest{ - Type: "publicshares", - ClientId: src.PublicLinkToken, - // We pass an empty password because we expect non pre-signed public links - // to not be password protected - ClientSecret: "password|", - }) - } - - if err != nil { - return "", merrors.InternalServerError(g.serviceID, "could not authenticate: %s", err.Error()) - } - auth = rsp.Token - statPath = path.Join("/public", src.PublicLinkToken, req.Filepath) - } else { - auth = src.RevaAuthorization - statPath = req.Filepath - } - sRes, err := g.stat(statPath, auth) - if err != nil { - return "", err - } - tr := thumbnail.Request{ - Resolution: image.Rect(0, 0, int(req.Width), int(req.Height)), - Generator: generator, - Encoder: encoder, - Checksum: sRes.GetInfo().GetChecksum().GetSum(), - } - - if key, exists := g.manager.CheckThumbnail(tr); exists { - return key, nil - } - - if src.WebdavAuthorization != "" { - ctx = imgsource.ContextSetAuthorization(ctx, src.WebdavAuthorization) - } - imgURL.RawQuery = "" - r, err := g.webdavSource.Get(ctx, imgURL.String()) - if err != nil { - return "", merrors.InternalServerError(g.serviceID, "could not get image from source: %s", err.Error()) - } - defer r.Close() // nolint:errcheck - ppOpts := map[string]interface{}{ - "fontFileMap": g.preprocessorOpts.TxtFontFileMap, - } - pp := preprocessor.ForType(sRes.GetInfo().GetMimeType(), ppOpts) - img, err := pp.Convert(r) - if img == nil || err != nil { - return "", merrors.InternalServerError(g.serviceID, "could not get image") - } - - key, err := g.manager.Generate(tr, img) - if err != nil { - return "", err - } - return key, nil -} - -func (g Thumbnail) stat(path, auth string) (*provider.StatResponse, error) { - ctx := metadata.AppendToOutgoingContext(context.Background(), revactx.TokenHeader, auth) - - ref, err := storagespace.ParseReference(path) - if err != nil { - // If the path is not a spaces reference try to handle it like a plain - // path reference. - ref = provider.Reference{ - Path: path, - } - } - - req := &provider.StatRequest{Ref: &ref} - rsp, err := g.cs3Client.Stat(ctx, req) - if err != nil { - g.logger.Error().Err(err).Str("path", path).Msg("could not stat file") - return nil, merrors.InternalServerError(g.serviceID, "could not stat file: %s", err.Error()) - } - - if rsp.Status.Code != rpc.Code_CODE_OK { - switch rsp.Status.Code { - case rpc.Code_CODE_NOT_FOUND: - return nil, merrors.NotFound(g.serviceID, "could not stat file: %s", rsp.Status.Message) - default: - g.logger.Error().Str("status_message", rsp.Status.Message).Str("path", path).Msg("could not stat file") - return nil, merrors.InternalServerError(g.serviceID, "could not stat file: %s", rsp.Status.Message) - } - } - if rsp.Info.Type != provider.ResourceType_RESOURCE_TYPE_FILE { - return nil, merrors.BadRequest(g.serviceID, "Unsupported file type") - } - if rsp.Info.GetChecksum().GetSum() == "" { - g.logger.Error().Msg("resource info is missing checksum") - return nil, merrors.NotFound(g.serviceID, "resource info is missing a checksum") - } - if !thumbnail.IsMimeTypeSupported(rsp.Info.MimeType) { - return nil, merrors.NotFound(g.serviceID, "Unsupported file type") - } - return rsp, nil -} diff --git a/extensions/thumbnails/pkg/service/http/v0/instrument.go b/extensions/thumbnails/pkg/service/http/v0/instrument.go deleted file mode 100644 index 259a5dc4d6c..00000000000 --- a/extensions/thumbnails/pkg/service/http/v0/instrument.go +++ /dev/null @@ -1,30 +0,0 @@ -package svc - -import ( - "net/http" - - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/metrics" -) - -// NewInstrument returns a service that instruments metrics. -func NewInstrument(next Service, metrics *metrics.Metrics) Service { - return instrument{ - next: next, - metrics: metrics, - } -} - -type instrument struct { - next Service - metrics *metrics.Metrics -} - -// ServeHTTP implements the Service interface. -func (i instrument) ServeHTTP(w http.ResponseWriter, r *http.Request) { - i.next.ServeHTTP(w, r) -} - -// GetThumbnail implements the Service interface. -func (i instrument) GetThumbnail(w http.ResponseWriter, r *http.Request) { - i.next.GetThumbnail(w, r) -} diff --git a/extensions/thumbnails/pkg/service/http/v0/option.go b/extensions/thumbnails/pkg/service/http/v0/option.go deleted file mode 100644 index e3535ed66ac..00000000000 --- a/extensions/thumbnails/pkg/service/http/v0/option.go +++ /dev/null @@ -1,59 +0,0 @@ -package svc - -import ( - "net/http" - - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/config" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/thumbnail/storage" - "github.com/owncloud/ocis/v2/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 - Config *config.Config - Middleware []func(http.Handler) http.Handler - ThumbnailStorage storage.Storage -} - -// 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 - } -} - -// Config provides a function to set the config option. -func Config(val *config.Config) Option { - return func(o *Options) { - o.Config = val - } -} - -// Middleware provides a function to set the middleware option. -func Middleware(val ...func(http.Handler) http.Handler) Option { - return func(o *Options) { - o.Middleware = val - } -} - -// ThumbnailStorage provides a function to set the ThumbnailStorage option. -func ThumbnailStorage(storage storage.Storage) Option { - return func(o *Options) { - o.ThumbnailStorage = storage - } -} diff --git a/extensions/thumbnails/pkg/service/http/v0/service.go b/extensions/thumbnails/pkg/service/http/v0/service.go deleted file mode 100644 index b715dddd27c..00000000000 --- a/extensions/thumbnails/pkg/service/http/v0/service.go +++ /dev/null @@ -1,123 +0,0 @@ -package svc - -import ( - "context" - "fmt" - "net/http" - "strconv" - - "github.com/go-chi/chi/v5" - "github.com/golang-jwt/jwt/v4" - - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/config" - tjwt "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/service/jwt" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/thumbnail" - "github.com/owncloud/ocis/v2/ocis-pkg/log" -) - -type contextKey string - -const ( - keyContextKey contextKey = "key" -) - -// Service defines the extension handlers. -type Service interface { - ServeHTTP(http.ResponseWriter, *http.Request) - GetThumbnail(http.ResponseWriter, *http.Request) -} - -// NewService returns a service implementation for Service. -func NewService(opts ...Option) Service { - options := newOptions(opts...) - - m := chi.NewMux() - m.Use(options.Middleware...) - - logger := options.Logger - resolutions, err := thumbnail.ParseResolutions(options.Config.Thumbnail.Resolutions) - if err != nil { - logger.Fatal().Err(err).Msg("resolutions not configured correctly") - } - svc := Thumbnails{ - config: options.Config, - mux: m, - logger: options.Logger, - manager: thumbnail.NewSimpleManager( - resolutions, - options.ThumbnailStorage, - logger, - ), - } - - m.Route(options.Config.HTTP.Root, func(r chi.Router) { - r.Use(svc.TransferTokenValidator) - r.Get("/data", svc.GetThumbnail) - }) - - return svc -} - -// Thumbnails implements the business logic for Service. -type Thumbnails struct { - config *config.Config - logger log.Logger - mux *chi.Mux - manager thumbnail.Manager -} - -// ServeHTTP implements the Service interface. -func (s Thumbnails) ServeHTTP(w http.ResponseWriter, r *http.Request) { - s.mux.ServeHTTP(w, r) -} - -// GetThumbnail implements the Service interface. -func (s Thumbnails) GetThumbnail(w http.ResponseWriter, r *http.Request) { - key := r.Context().Value(keyContextKey).(string) - - thumbnail, err := s.manager.GetThumbnail(key) - if err != nil { - s.logger.Error(). - Err(err). - Str("key", key). - Msg("could not get the thumbnail") - w.WriteHeader(http.StatusInternalServerError) - return - } - - w.WriteHeader(http.StatusOK) - w.Header().Set("Content-Length", strconv.Itoa(len(thumbnail))) - if _, err = w.Write(thumbnail); err != nil { - s.logger.Error(). - Err(err). - Str("key", key). - Msg("could not write the thumbnail response") - } -} - -func (s Thumbnails) TransferTokenValidator(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - tokenString := r.Header.Get("Transfer-Token") - token, err := jwt.ParseWithClaims(tokenString, &tjwt.ThumbnailClaims{}, func(token *jwt.Token) (interface{}, error) { - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) - } - return []byte(s.config.Thumbnail.TransferSecret), nil - }) - if err != nil { - s.logger.Error(). - Err(err). - Str("transfer-token", tokenString). - Msg("failed to parse transfer token") - w.WriteHeader(http.StatusUnauthorized) - return - } - - if claims, ok := token.Claims.(*tjwt.ThumbnailClaims); ok && token.Valid { - ctx := context.WithValue(r.Context(), keyContextKey, claims.Key) - next.ServeHTTP(w, r.WithContext(ctx)) - return - } - w.WriteHeader(http.StatusUnauthorized) - }) -} diff --git a/extensions/thumbnails/pkg/thumbnail/imgsource/cs3.go b/extensions/thumbnails/pkg/thumbnail/imgsource/cs3.go deleted file mode 100644 index 40e9cbd4387..00000000000 --- a/extensions/thumbnails/pkg/thumbnail/imgsource/cs3.go +++ /dev/null @@ -1,97 +0,0 @@ -package imgsource - -import ( - "context" - "crypto/tls" - "fmt" - "io" - "net/http" - - gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" - 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/rhttp" - "github.com/cs3org/reva/v2/pkg/storagespace" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/config" - "github.com/pkg/errors" - "google.golang.org/grpc/metadata" -) - -const ( - // "github.com/cs3org/reva/v2/internal/http/services/datagateway" is internal so we redeclare it here - // TokenTransportHeader holds the header key for the reva transfer token - TokenTransportHeader = "X-Reva-Transfer" -) - -type CS3 struct { - client gateway.GatewayAPIClient - insecure bool -} - -func NewCS3Source(cfg config.Thumbnail, c gateway.GatewayAPIClient) CS3 { - return CS3{ - client: c, - insecure: cfg.CS3AllowInsecure, - } -} - -// Get downloads the file from a cs3 service -// The caller MUST make sure to close the returned ReadCloser -func (s CS3) Get(ctx context.Context, path string) (io.ReadCloser, error) { - auth, ok := ContextGetAuthorization(ctx) - if !ok { - return nil, errors.New("cs3source: authorization missing") - } - ref, err := storagespace.ParseReference(path) - if err != nil { - // If the path is not a spaces reference try to handle it like a plain - // path reference. - ref = provider.Reference{ - Path: path, - } - } - ctx = metadata.AppendToOutgoingContext(context.Background(), revactx.TokenHeader, auth) - rsp, err := s.client.InitiateFileDownload(ctx, &provider.InitiateFileDownloadRequest{Ref: &ref}) - - if err != nil { - return nil, err - } - - if rsp.Status.Code != rpc.Code_CODE_OK { - return nil, fmt.Errorf("could not load image: %s", rsp.Status.Message) - } - var ep, tk string - for _, p := range rsp.Protocols { - if p.Protocol == "spaces" { - ep, tk = p.DownloadEndpoint, p.Token - break - } - } - if (ep == "" || tk == "") && len(rsp.Protocols) > 0 { - ep, tk = rsp.Protocols[0].DownloadEndpoint, rsp.Protocols[0].Token - } - - httpReq, err := rhttp.NewRequest(ctx, "GET", ep, nil) - if err != nil { - return nil, err - } - httpReq.Header.Set(revactx.TokenHeader, auth) - httpReq.Header.Set(TokenTransportHeader, tk) - - http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{ - InsecureSkipVerify: s.insecure, //nolint:gosec - } - client := &http.Client{} - - resp, err := client.Do(httpReq) // nolint:bodyclose - if err != nil { - return nil, err - } - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("could not get the image \"%s\". Request returned with statuscode %d ", path, resp.StatusCode) - } - - return resp.Body, nil -} diff --git a/extensions/thumbnails/pkg/thumbnail/thumbnail.go b/extensions/thumbnails/pkg/thumbnail/thumbnail.go deleted file mode 100644 index 6a156410e81..00000000000 --- a/extensions/thumbnails/pkg/thumbnail/thumbnail.go +++ /dev/null @@ -1,111 +0,0 @@ -package thumbnail - -import ( - "bytes" - "image" - "image/gif" - "mime" - - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/thumbnail/storage" - "github.com/owncloud/ocis/v2/ocis-pkg/log" -) - -var ( - // SupportedMimeTypes contains a all mimetypes which are supported by the thumbnailer. - SupportedMimeTypes = map[string]struct{}{ - "image/png": {}, - "image/jpg": {}, - "image/jpeg": {}, - "image/gif": {}, - "text/plain": {}, - } -) - -// Request bundles information needed to generate a thumbnail for afile -type Request struct { - Resolution image.Rectangle - Encoder Encoder - Generator Generator - Checksum string -} - -// Manager is responsible for generating thumbnails -type Manager interface { - // Generate creates a thumbnail and stores it. - // The function returns a key with which the actual file can be retrieved. - Generate(Request, interface{}) (string, error) - // CheckThumbnail checks if a thumbnail with the requested attributes exists. - // The function will return a status if the file exists and the key to the file. - CheckThumbnail(Request) (string, bool) - // GetThumbnail will load the thumbnail from the storage and return its content. - GetThumbnail(key string) ([]byte, error) -} - -// NewSimpleManager creates a new instance of SimpleManager -func NewSimpleManager(resolutions Resolutions, storage storage.Storage, logger log.Logger) SimpleManager { - return SimpleManager{ - storage: storage, - logger: logger, - resolutions: resolutions, - } -} - -// SimpleManager is a simple implementation of Manager -type SimpleManager struct { - storage storage.Storage - logger log.Logger - resolutions Resolutions -} - -func (s SimpleManager) Generate(r Request, img interface{}) (string, error) { - var match image.Rectangle - switch m := img.(type) { - case *gif.GIF: - match = s.resolutions.ClosestMatch(r.Resolution, m.Image[0].Bounds()) - case image.Image: - match = s.resolutions.ClosestMatch(r.Resolution, m.Bounds()) - } - - thumbnail, err := r.Generator.GenerateThumbnail(match, img) - if err != nil { - return "", err - } - - buf := new(bytes.Buffer) - if err := r.Encoder.Encode(buf, thumbnail); err != nil { - return "", err - } - - k := s.storage.BuildKey(mapToStorageRequest(r)) - if err := s.storage.Put(k, buf.Bytes()); err != nil { - s.logger.Error().Err(err).Msg("could not store thumbnail") - return "", err - } - return k, nil -} - -func (s SimpleManager) CheckThumbnail(r Request) (string, bool) { - k := s.storage.BuildKey(mapToStorageRequest(r)) - return k, s.storage.Stat(k) -} - -func (s SimpleManager) GetThumbnail(key string) ([]byte, error) { - return s.storage.Get(key) -} - -func mapToStorageRequest(r Request) storage.Request { - return storage.Request{ - Checksum: r.Checksum, - Resolution: r.Resolution, - Types: r.Encoder.Types(), - } -} - -func IsMimeTypeSupported(m string) bool { - mimeType, _, err := mime.ParseMediaType(m) - if err != nil { - return false - } - _, supported := SupportedMimeTypes[mimeType] - return supported -} diff --git a/extensions/thumbnails/pkg/tracing/tracing.go b/extensions/thumbnails/pkg/tracing/tracing.go deleted file mode 100644 index 3b2ea32fbf3..00000000000 --- a/extensions/thumbnails/pkg/tracing/tracing.go +++ /dev/null @@ -1,23 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/config" - pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" - "go.opentelemetry.io/otel/trace" -) - -var ( - // TraceProvider is the global trace provider for the thumbnails service. - TraceProvider = trace.NewNoopTracerProvider() -) - -func Configure(cfg *config.Config) error { - var err error - if cfg.Tracing.Enabled { - if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { - return err - } - } - - return nil -} diff --git a/extensions/users/cmd/user/main.go b/extensions/users/cmd/user/main.go deleted file mode 100644 index f0f1152dc41..00000000000 --- a/extensions/users/cmd/user/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/users/pkg/command" - "github.com/owncloud/ocis/v2/extensions/users/pkg/config/defaults" -) - -func main() { - if err := command.Execute(defaults.DefaultConfig()); err != nil { - os.Exit(1) - } -} diff --git a/extensions/users/pkg/command/health.go b/extensions/users/pkg/command/health.go deleted file mode 100644 index 488565c1e3e..00000000000 --- a/extensions/users/pkg/command/health.go +++ /dev/null @@ -1,57 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/users/pkg/config" - "github.com/owncloud/ocis/v2/extensions/users/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/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/users/pkg/command/root.go b/extensions/users/pkg/command/root.go deleted file mode 100644 index d88536de460..00000000000 --- a/extensions/users/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/users/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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.Users.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Users, - } -} - -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/users/pkg/command/server.go b/extensions/users/pkg/command/server.go deleted file mode 100644 index bd1db9054d0..00000000000 --- a/extensions/users/pkg/command/server.go +++ /dev/null @@ -1,121 +0,0 @@ -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/v2/extensions/users/pkg/config" - "github.com/owncloud/ocis/v2/extensions/users/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/users/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/users/pkg/revaconfig" - "github.com/owncloud/ocis/v2/extensions/users/pkg/server/debug" - "github.com/owncloud/ocis/v2/extensions/users/pkg/tracing" - "github.com/owncloud/ocis/v2/ocis-pkg/ldap" - "github.com/owncloud/ocis/v2/ocis-pkg/service/external" - "github.com/owncloud/ocis/v2/ocis-pkg/sync" - "github.com/owncloud/ocis/v2/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) - os.Exit(1) - } - 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.GetString(), - 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/users/pkg/command/version.go b/extensions/users/pkg/command/version.go deleted file mode 100644 index 5985b232341..00000000000 --- a/extensions/users/pkg/command/version.go +++ /dev/null @@ -1,50 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - tw "github.com/olekukonko/tablewriter" - "github.com/owncloud/ocis/v2/extensions/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.GetString()) - 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/users/pkg/config/defaults/defaultconfig.go b/extensions/users/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index 89db09946f7..00000000000 --- a/extensions/users/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,126 +0,0 @@ -package defaults - -import ( - "path/filepath" - - "github.com/owncloud/ocis/v2/extensions/users/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" -) - -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:9145", - Token: "", - Pprof: false, - Zpages: false, - }, - GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9144", - Namespace: "com.owncloud.api", - Protocol: "tcp", - }, - Service: config.Service{ - Name: "users", - }, - Reva: &config.Reva{ - Address: "127.0.0.1:9142", - }, - Driver: "ldap", - Drivers: config.Drivers{ - LDAP: config.LDAPDriver{ - URI: "ldaps://localhost:9235", - CACert: filepath.Join(defaults.BaseDataPath(), "idm", "ldap.crt"), - Insecure: false, - UserBaseDN: "ou=users,o=libregraph-idm", - GroupBaseDN: "ou=groups,o=libregraph-idm", - UserScope: "sub", - GroupScope: "sub", - UserFilter: "", - GroupFilter: "", - UserObjectClass: "inetOrgPerson", - GroupObjectClass: "groupOfNames", - BindDN: "uid=reva,ou=sysusers,o=libregraph-idm", - IDP: "https://localhost:9200", - UserSchema: config.LDAPUserSchema{ - ID: "ownclouduuid", - Mail: "mail", - DisplayName: "displayname", - Username: "uid", - }, - GroupSchema: config.LDAPGroupSchema{ - ID: "ownclouduuid", - Mail: "mail", - DisplayName: "cn", - Groupname: "cn", - Member: "member", - }, - }, - JSON: config.JSONDriver{}, - OwnCloudSQL: config.OwnCloudSQLDriver{ - DBUsername: "owncloud", - DBPassword: "secret", - DBHost: "mysql", - DBPort: 3306, - DBName: "owncloud", - IDP: "https://localhost:9200", - Nobody: 90, - JoinUsername: false, - JoinOwnCloudUUID: false, - EnableMedialSearch: false, - }, - }, - } -} - -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/users/pkg/config/parser/parse.go b/extensions/users/pkg/config/parser/parse.go deleted file mode 100644 index f12fc1f9784..00000000000 --- a/extensions/users/pkg/config/parser/parse.go +++ /dev/null @@ -1,46 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/users/pkg/config" - "github.com/owncloud/ocis/v2/extensions/users/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - if cfg.TokenManager.JWTSecret == "" { - return shared.MissingJWTTokenError(cfg.Service.Name) - } - - if cfg.Driver == "ldap" && cfg.Drivers.LDAP.BindPassword == "" { - return shared.MissingLDAPBindPassword(cfg.Service.Name) - } - - return nil -} diff --git a/extensions/users/pkg/logging/logging.go b/extensions/users/pkg/logging/logging.go deleted file mode 100644 index 2fbc8a96c6f..00000000000 --- a/extensions/users/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/users/pkg/config" - "github.com/owncloud/ocis/v2/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/users/pkg/revaconfig/config.go b/extensions/users/pkg/revaconfig/config.go deleted file mode 100644 index 1c29ccd4ab0..00000000000 --- a/extensions/users/pkg/revaconfig/config.go +++ /dev/null @@ -1,85 +0,0 @@ -package revaconfig - -import ( - "github.com/owncloud/ocis/v2/extensions/users/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, - "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/users/pkg/server/debug/option.go b/extensions/users/pkg/server/debug/option.go deleted file mode 100644 index 7f59cbc36f8..00000000000 --- a/extensions/users/pkg/server/debug/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/users/pkg/config" - "github.com/owncloud/ocis/v2/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/users/pkg/server/debug/server.go b/extensions/users/pkg/server/debug/server.go deleted file mode 100644 index 362e1d2ef9b..00000000000 --- a/extensions/users/pkg/server/debug/server.go +++ /dev/null @@ -1,63 +0,0 @@ -package debug - -import ( - "io" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/users/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/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.GetString()), - 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/users/pkg/tracing/tracing.go b/extensions/users/pkg/tracing/tracing.go deleted file mode 100644 index 661aaff8653..00000000000 --- a/extensions/users/pkg/tracing/tracing.go +++ /dev/null @@ -1,18 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/v2/extensions/users/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/owncloud/ocis/v2/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/cmd/web/main.go b/extensions/web/cmd/web/main.go deleted file mode 100644 index 1c6a3e87fb7..00000000000 --- a/extensions/web/cmd/web/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/web/pkg/command" - "github.com/owncloud/ocis/v2/extensions/web/pkg/config/defaults" -) - -func main() { - if err := command.Execute(defaults.DefaultConfig()); err != nil { - os.Exit(1) - } -} diff --git a/extensions/web/pkg/assets/option.go b/extensions/web/pkg/assets/option.go deleted file mode 100644 index 78cdb2e994e..00000000000 --- a/extensions/web/pkg/assets/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package assets - -import ( - "net/http" - - "github.com/owncloud/ocis/v2/extensions/web" - "github.com/owncloud/ocis/v2/extensions/web/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/assetsfs" - "github.com/owncloud/ocis/v2/ocis-pkg/log" -) - -// New returns a new http filesystem to serve assets. -func New(opts ...Option) http.FileSystem { - options := newOptions(opts...) - return assetsfs.New(web.Assets, options.Config.Asset.Path, options.Logger) -} - -// 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 - 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 - } -} - -// 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/web/pkg/command/health.go b/extensions/web/pkg/command/health.go deleted file mode 100644 index 640ed8bd803..00000000000 --- a/extensions/web/pkg/command/health.go +++ /dev/null @@ -1,57 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/web/pkg/config" - "github.com/owncloud/ocis/v2/extensions/web/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/web/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/web/pkg/command/root.go b/extensions/web/pkg/command/root.go deleted file mode 100644 index 73381e194b1..00000000000 --- a/extensions/web/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/web/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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 web command. -func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "web", - Usage: "Serve ownCloud Web 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 web command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new web.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.Web.Commons = cfg.Commons - return SutureService{ - cfg: cfg.Web, - } -} - -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/web/pkg/command/server.go b/extensions/web/pkg/command/server.go deleted file mode 100644 index 40d2e0c9105..00000000000 --- a/extensions/web/pkg/command/server.go +++ /dev/null @@ -1,125 +0,0 @@ -package command - -import ( - "context" - "encoding/json" - "fmt" - "io/ioutil" - "os" - - "github.com/oklog/run" - "github.com/owncloud/ocis/v2/extensions/web/pkg/config" - "github.com/owncloud/ocis/v2/extensions/web/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/web/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/web/pkg/metrics" - "github.com/owncloud/ocis/v2/extensions/web/pkg/server/debug" - "github.com/owncloud/ocis/v2/extensions/web/pkg/server/http" - "github.com/owncloud/ocis/v2/extensions/web/pkg/tracing" - "github.com/urfave/cli/v2" -) - -// Server is the entrypoint 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) - os.Exit(1) - } - return err - }, - Action: func(c *cli.Context) error { - logger := logging.Configure(cfg.Service.Name, cfg.Log) - err := tracing.Configure(cfg) - if err != nil { - return err - } - - // actually read the contents of the config file and override defaults - if cfg.File != "" { - contents, err := ioutil.ReadFile(cfg.File) - if err != nil { - logger.Err(err).Msg("error opening config file") - return err - } - if err = json.Unmarshal(contents, &cfg.Web.Config); err != nil { - logger.Fatal().Err(err).Msg("error unmarshalling config file") - return err - } - } - - var ( - gr = run.Group{} - ctx, cancel = func() (context.Context, context.CancelFunc) { - if cfg.Context == nil { - return context.WithCancel(context.Background()) - } - return context.WithCancel(cfg.Context) - }() - metrics = metrics.New() - ) - - defer cancel() - - { - server, err := http.Server( - http.Logger(logger), - http.Context(ctx), - http.Namespace(cfg.HTTP.Namespace), - http.Config(cfg), - http.Metrics(metrics), - ) - - if err != nil { - logger.Info(). - Err(err). - Str("transport", "http"). - Msg("Failed to initialize server") - - return err - } - - gr.Add(func() error { - err := server.Run() - if err != nil { - logger.Error(). - Err(err). - Str("transport", "http"). - Msg("Failed to start server") - } - return err - }, func(_ error) { - logger.Info(). - Str("transport", "http"). - Msg("Shutting down server") - - cancel() - }) - } - - { - server, err := debug.Server( - debug.Logger(logger), - debug.Context(ctx), - debug.Config(cfg), - ) - - if err != nil { - logger.Info().Err(err).Str("transport", "debug").Msg("Failed to initialize server") - return err - } - - gr.Add(server.ListenAndServe, func(_ error) { - _ = server.Shutdown(ctx) - cancel() - }) - } - - return gr.Run() - }, - } -} diff --git a/extensions/web/pkg/command/version.go b/extensions/web/pkg/command/version.go deleted file mode 100644 index c792be769cf..00000000000 --- a/extensions/web/pkg/command/version.go +++ /dev/null @@ -1,50 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - tw "github.com/olekukonko/tablewriter" - "github.com/owncloud/ocis/v2/extensions/web/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.GetString()) - 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/web/pkg/config/defaults/defaultconfig.go b/extensions/web/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index 3fe1da708b5..00000000000 --- a/extensions/web/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,94 +0,0 @@ -package defaults - -import ( - "strings" - - "github.com/owncloud/ocis/v2/extensions/web/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:9104", - Token: "", - Pprof: false, - Zpages: false, - }, - HTTP: config.HTTP{ - Addr: "127.0.0.1:9100", - Root: "/", - Namespace: "com.owncloud.web", - CacheTTL: 604800, // 7 days - }, - Service: config.Service{ - Name: "web", - }, - Asset: config.Asset{ - Path: "", - }, - Web: config.Web{ - Path: "", - ThemeServer: "https://localhost:9200", - ThemePath: "/themes/owncloud/theme.json", - Config: config.WebConfig{ - Server: "https://localhost:9200", - Theme: "", - Version: "0.1.0", - OpenIDConnect: config.OIDC{ - MetadataURL: "", - Authority: "https://localhost:9200", - ClientID: "web", - ResponseType: "code", - Scope: "openid profile email", - }, - Apps: []string{"files", "search", "preview", "text-editor", "pdf-viewer", "external", "user-management"}, - Options: map[string]interface{}{ - "hideSearchBar": false, - }, - }, - }, - } -} - -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{} - } -} - -func Sanitize(cfg *config.Config) { - // sanitize config - if cfg.HTTP.Root != "/" { - cfg.HTTP.Root = strings.TrimRight(cfg.HTTP.Root, "/") - } - // build well known openid-configuration endpoint if it is not set - if cfg.Web.Config.OpenIDConnect.MetadataURL == "" { - cfg.Web.Config.OpenIDConnect.MetadataURL = strings.TrimRight(cfg.Web.Config.OpenIDConnect.Authority, "/") + "/.well-known/openid-configuration" - } -} diff --git a/extensions/web/pkg/config/parser/parse.go b/extensions/web/pkg/config/parser/parse.go deleted file mode 100644 index 2732411aadf..00000000000 --- a/extensions/web/pkg/config/parser/parse.go +++ /dev/null @@ -1,37 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/web/pkg/config" - "github.com/owncloud/ocis/v2/extensions/web/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - return nil -} diff --git a/extensions/web/pkg/logging/logging.go b/extensions/web/pkg/logging/logging.go deleted file mode 100644 index 29e1f8742f8..00000000000 --- a/extensions/web/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/web/pkg/config" - "github.com/owncloud/ocis/v2/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/web/pkg/server/debug/option.go b/extensions/web/pkg/server/debug/option.go deleted file mode 100644 index 360025b0000..00000000000 --- a/extensions/web/pkg/server/debug/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/web/pkg/config" - "github.com/owncloud/ocis/v2/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/web/pkg/server/debug/server.go b/extensions/web/pkg/server/debug/server.go deleted file mode 100644 index 85cd8127d1d..00000000000 --- a/extensions/web/pkg/server/debug/server.go +++ /dev/null @@ -1,59 +0,0 @@ -package debug - -import ( - "io" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/web/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/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.GetString()), - 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)), - ), 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/web/pkg/server/http/option.go b/extensions/web/pkg/server/http/option.go deleted file mode 100644 index 98462b72a6a..00000000000 --- a/extensions/web/pkg/server/http/option.go +++ /dev/null @@ -1,76 +0,0 @@ -package http - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/web/pkg/config" - "github.com/owncloud/ocis/v2/extensions/web/pkg/metrics" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/urfave/cli/v2" -) - -// 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 - Metrics *metrics.Metrics - Flags []cli.Flag - Namespace string -} - -// 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 - } -} - -// Metrics provides a function to set the metrics option. -func Metrics(val *metrics.Metrics) Option { - return func(o *Options) { - o.Metrics = val - } -} - -// Flags provides a function to set the flags option. -func Flags(val []cli.Flag) Option { - return func(o *Options) { - o.Flags = append(o.Flags, val...) - } -} - -// Namespace provides a function to set the Namespace option. -func Namespace(val string) Option { - return func(o *Options) { - o.Namespace = val - } -} diff --git a/extensions/web/pkg/server/http/server.go b/extensions/web/pkg/server/http/server.go deleted file mode 100644 index 8f0d964aad6..00000000000 --- a/extensions/web/pkg/server/http/server.go +++ /dev/null @@ -1,57 +0,0 @@ -package http - -import ( - chimiddleware "github.com/go-chi/chi/v5/middleware" - webmid "github.com/owncloud/ocis/v2/extensions/web/pkg/middleware" - svc "github.com/owncloud/ocis/v2/extensions/web/pkg/service/v0" - "github.com/owncloud/ocis/v2/ocis-pkg/middleware" - "github.com/owncloud/ocis/v2/ocis-pkg/service/http" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - "go-micro.dev/v4" -) - -// Server initializes the http service and server. -func Server(opts ...Option) (http.Service, error) { - options := newOptions(opts...) - - service := http.NewService( - http.Logger(options.Logger), - http.Namespace(options.Namespace), - http.Name("web"), - http.Version(version.GetString()), - http.Address(options.Config.HTTP.Addr), - http.Context(options.Context), - http.Flags(options.Flags...), - ) - - handle := svc.NewService( - svc.Logger(options.Logger), - svc.Config(options.Config), - svc.Middleware( - chimiddleware.RealIP, - chimiddleware.RequestID, - middleware.NoCache, - middleware.Secure, - webmid.SilentRefresh, - middleware.Version( - "web", - version.GetString(), - ), - middleware.Logger( - options.Logger, - ), - ), - ) - - { - handle = svc.NewInstrument(handle, options.Metrics) - handle = svc.NewLogging(handle, options.Logger) - handle = svc.NewTracing(handle) - } - - if err := micro.RegisterHandler(service.Server(), handle); err != nil { - return http.Service{}, err - } - - return service, nil -} diff --git a/extensions/web/pkg/service/v0/instrument.go b/extensions/web/pkg/service/v0/instrument.go deleted file mode 100644 index 7a625b7eb0f..00000000000 --- a/extensions/web/pkg/service/v0/instrument.go +++ /dev/null @@ -1,30 +0,0 @@ -package svc - -import ( - "net/http" - - "github.com/owncloud/ocis/v2/extensions/web/pkg/metrics" -) - -// NewInstrument returns a service that instruments metrics. -func NewInstrument(next Service, metrics *metrics.Metrics) Service { - return instrument{ - next: next, - metrics: metrics, - } -} - -type instrument struct { - next Service - metrics *metrics.Metrics -} - -// ServeHTTP implements the Service interface. -func (i instrument) ServeHTTP(w http.ResponseWriter, r *http.Request) { - i.next.ServeHTTP(w, r) -} - -// Config implements the Service interface. -func (i instrument) Config(w http.ResponseWriter, r *http.Request) { - i.next.Config(w, r) -} diff --git a/extensions/web/pkg/service/v0/option.go b/extensions/web/pkg/service/v0/option.go deleted file mode 100644 index ae058da847b..00000000000 --- a/extensions/web/pkg/service/v0/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package svc - -import ( - "net/http" - - "github.com/owncloud/ocis/v2/extensions/web/pkg/config" - "github.com/owncloud/ocis/v2/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 - Config *config.Config - Middleware []func(http.Handler) http.Handler -} - -// 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 - } -} - -// Config provides a function to set the config option. -func Config(val *config.Config) Option { - return func(o *Options) { - o.Config = val - } -} - -// Middleware provides a function to set the middleware option. -func Middleware(val ...func(http.Handler) http.Handler) Option { - return func(o *Options) { - o.Middleware = val - } -} diff --git a/extensions/web/pkg/service/v0/service.go b/extensions/web/pkg/service/v0/service.go deleted file mode 100644 index 32af6f5330b..00000000000 --- a/extensions/web/pkg/service/v0/service.go +++ /dev/null @@ -1,168 +0,0 @@ -package svc - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/url" - "os" - "strconv" - "strings" - "time" - - "github.com/go-chi/chi/v5" - "github.com/owncloud/ocis/v2/extensions/web/pkg/assets" - "github.com/owncloud/ocis/v2/extensions/web/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/log" -) - -var ( - // ErrConfigInvalid is returned when the config parse is invalid. - ErrConfigInvalid = `Invalid or missing config` -) - -// Service defines the extension handlers. -type Service interface { - ServeHTTP(http.ResponseWriter, *http.Request) - Config(http.ResponseWriter, *http.Request) -} - -// NewService returns a service implementation for Service. -func NewService(opts ...Option) Service { - options := newOptions(opts...) - - m := chi.NewMux() - m.Use(options.Middleware...) - - svc := Web{ - logger: options.Logger, - config: options.Config, - mux: m, - } - - m.Route(options.Config.HTTP.Root, func(r chi.Router) { - r.Get("/config.json", svc.Config) - r.Mount("/", svc.Static(options.Config.HTTP.CacheTTL)) - }) - - return svc -} - -// Web defines implements the business logic for Service. -type Web struct { - logger log.Logger - config *config.Config - mux *chi.Mux -} - -// ServeHTTP implements the Service interface. -func (p Web) ServeHTTP(w http.ResponseWriter, r *http.Request) { - p.mux.ServeHTTP(w, r) -} - -func (p Web) getPayload() (payload []byte, err error) { - - if p.config.Web.Path == "" { - // render dynamically using config - - // build theme url - if themeServer, err := url.Parse(p.config.Web.ThemeServer); err == nil { - p.config.Web.Config.Theme = themeServer.String() + p.config.Web.ThemePath - } else { - p.config.Web.Config.Theme = p.config.Web.ThemePath - } - - if p.config.Web.Config.ExternalApps == nil { - p.config.Web.Config.ExternalApps = []config.ExternalApp{ - { - ID: "settings", - Path: "/settings.js", - }, - } - } - - // make apps render as empty array if it is empty - // TODO remove once https://github.com/golang/go/issues/27589 is fixed - if len(p.config.Web.Config.Apps) == 0 { - p.config.Web.Config.Apps = make([]string, 0) - } - - return json.Marshal(p.config.Web.Config) - } - - // try loading from file - if _, err = os.Stat(p.config.Web.Path); os.IsNotExist(err) { - p.logger.Fatal(). - Err(err). - Str("config", p.config.Web.Path). - Msg("web config doesn't exist") - } - - payload, err = ioutil.ReadFile(p.config.Web.Path) - - if err != nil { - p.logger.Fatal(). - Err(err). - Str("config", p.config.Web.Path). - Msg("failed to read custom config") - } - return -} - -// Config implements the Service interface. -func (p Web) Config(w http.ResponseWriter, r *http.Request) { - - payload, err := p.getPayload() - if err != nil { - http.Error(w, ErrConfigInvalid, http.StatusUnprocessableEntity) - return - } - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - if _, err := w.Write(payload); err != nil { - p.logger.Error().Err(err).Msg("could not write config response") - } -} - -// Static simply serves all static files. -func (p Web) Static(ttl int) http.HandlerFunc { - rootWithSlash := p.config.HTTP.Root - - if !strings.HasSuffix(rootWithSlash, "/") { - rootWithSlash = rootWithSlash + "/" - } - - static := http.StripPrefix( - rootWithSlash, - assets.FileServer( - assets.New( - assets.Logger(p.logger), - assets.Config(p.config), - ), - ), - ) - - lastModified := time.Now().UTC().Format(http.TimeFormat) - expires := time.Now().Add(time.Second * time.Duration(ttl)).UTC().Format(http.TimeFormat) - - return func(w http.ResponseWriter, r *http.Request) { - if rootWithSlash != "/" && r.URL.Path == p.config.HTTP.Root { - http.Redirect( - w, - r, - rootWithSlash, - http.StatusMovedPermanently, - ) - return - } - - w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%s, must-revalidate", strconv.Itoa(ttl))) - w.Header().Set("Expires", expires) - w.Header().Set("Last-Modified", lastModified) - w.Header().Set("SameSite", "Strict") - - static.ServeHTTP(w, r) - } -} diff --git a/extensions/web/pkg/tracing/tracing.go b/extensions/web/pkg/tracing/tracing.go deleted file mode 100644 index b23c4605dc7..00000000000 --- a/extensions/web/pkg/tracing/tracing.go +++ /dev/null @@ -1,23 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/v2/extensions/web/pkg/config" - pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" - "go.opentelemetry.io/otel/trace" -) - -var ( - // TraceProvider is the global trace provider for the web service. - TraceProvider = trace.NewNoopTracerProvider() -) - -func Configure(cfg *config.Config) error { - var err error - if cfg.Tracing.Enabled { - if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { - return err - } - } - - return nil -} diff --git a/extensions/webdav/cmd/webdav/main.go b/extensions/webdav/cmd/webdav/main.go deleted file mode 100644 index 48bb27dc789..00000000000 --- a/extensions/webdav/cmd/webdav/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "os" - - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/command" - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/config/defaults" -) - -func main() { - if err := command.Execute(defaults.DefaultConfig()); err != nil { - os.Exit(1) - } -} diff --git a/extensions/webdav/pkg/command/health.go b/extensions/webdav/pkg/command/health.go deleted file mode 100644 index bf31f5c79d9..00000000000 --- a/extensions/webdav/pkg/command/health.go +++ /dev/null @@ -1,57 +0,0 @@ -package command - -import ( - "fmt" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/config" - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/webdav/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/webdav/pkg/command/root.go b/extensions/webdav/pkg/command/root.go deleted file mode 100644 index 9501820cdae..00000000000 --- a/extensions/webdav/pkg/command/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" - ociscfg "github.com/owncloud/ocis/v2/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-webdav command. -func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "webdav", - Usage: "Serve 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 webdav command to be embedded and supervised by a suture supervisor tree. -type SutureService struct { - cfg *config.Config -} - -// NewSutureService creates a new webdav.SutureService -func NewSutureService(cfg *ociscfg.Config) suture.Service { - cfg.WebDAV.Commons = cfg.Commons - return SutureService{ - cfg: cfg.WebDAV, - } -} - -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/webdav/pkg/command/server.go b/extensions/webdav/pkg/command/server.go deleted file mode 100644 index f83ed0de556..00000000000 --- a/extensions/webdav/pkg/command/server.go +++ /dev/null @@ -1,107 +0,0 @@ -package command - -import ( - "context" - "fmt" - "os" - - "github.com/oklog/run" - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/config" - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/config/parser" - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/logging" - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/metrics" - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/server/debug" - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/server/http" - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/tracing" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - "github.com/urfave/cli/v2" -) - -// Server is the entrypoint 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) - os.Exit(1) - } - return err - }, - Action: func(c *cli.Context) error { - logger := logging.Configure(cfg.Service.Name, cfg.Log) - err := tracing.Configure(cfg) - if err != nil { - return err - } - - var ( - gr = run.Group{} - ctx, cancel = func() (context.Context, context.CancelFunc) { - if cfg.Context == nil { - return context.WithCancel(context.Background()) - } - return context.WithCancel(cfg.Context) - }() - metrics = metrics.New() - ) - - defer cancel() - - metrics.BuildInfo.WithLabelValues(version.GetString()).Set(1) - - { - server, err := http.Server( - http.Logger(logger), - http.Context(ctx), - http.Config(cfg), - http.Metrics(metrics), - ) - - if err != nil { - logger.Info(). - Err(err). - Str("transport", "http"). - Msg("Failed to initialize server") - - return err - } - - gr.Add(func() error { - return server.Run() - }, func(err error) { - logger.Error().Err(err).Msg("error ") - logger.Info(). - Str("transport", "http"). - Msg("Shutting down server") - - cancel() - }) - } - - { - server, err := debug.Server( - debug.Logger(logger), - debug.Context(ctx), - debug.Config(cfg), - ) - - if err != nil { - logger.Info().Err(err).Str("transport", "debug").Msg("Failed to initialize server") - return err - } - - gr.Add(server.ListenAndServe, func(err error) { - logger.Error().Err(err) - _ = server.Shutdown(ctx) - cancel() - }) - } - - return gr.Run() - }, - } -} diff --git a/extensions/webdav/pkg/command/version.go b/extensions/webdav/pkg/command/version.go deleted file mode 100644 index 542a613c931..00000000000 --- a/extensions/webdav/pkg/command/version.go +++ /dev/null @@ -1,50 +0,0 @@ -package command - -import ( - "fmt" - "os" - - "github.com/owncloud/ocis/v2/ocis-pkg/registry" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - - tw "github.com/olekukonko/tablewriter" - "github.com/owncloud/ocis/v2/extensions/webdav/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.GetString()) - 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/webdav/pkg/config/defaults/defaultconfig.go b/extensions/webdav/pkg/config/defaults/defaultconfig.go deleted file mode 100644 index 57e8564e51a..00000000000 --- a/extensions/webdav/pkg/config/defaults/defaultconfig.go +++ /dev/null @@ -1,75 +0,0 @@ -package defaults - -import ( - "strings" - - "github.com/owncloud/ocis/v2/extensions/webdav/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:9119", - Token: "", - Pprof: false, - Zpages: false, - }, - HTTP: config.HTTP{ - Addr: "127.0.0.1:9115", - Root: "/", - Namespace: "com.owncloud.web", - CORS: config.CORS{ - AllowedOrigins: []string{"*"}, - AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, - AllowedHeaders: []string{"Authorization", "Origin", "Content-Type", "Accept", "X-Requested-With"}, - AllowCredentials: true, - }, - }, - Service: config.Service{ - Name: "webdav", - }, - OcisPublicURL: "https://127.0.0.1:9200", - WebdavNamespace: "/users/{{.Id.OpaqueId}}", - RevaGateway: "127.0.0.1:9142", - } -} - -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{} - } -} - -func Sanitize(cfg *config.Config) { - // sanitize config - if cfg.HTTP.Root != "/" { - cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") - } - -} diff --git a/extensions/webdav/pkg/config/parser/parse.go b/extensions/webdav/pkg/config/parser/parse.go deleted file mode 100644 index 898249ef25a..00000000000 --- a/extensions/webdav/pkg/config/parser/parse.go +++ /dev/null @@ -1,37 +0,0 @@ -package parser - -import ( - "errors" - - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/config" - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/config/defaults" - ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - - "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" -) - -// ParseConfig loads configuration from known paths. -func ParseConfig(cfg *config.Config) error { - _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) - if err != nil { - return err - } - - defaults.EnsureDefaults(cfg) - - // load all env variables relevant to the config in the current context. - if err := envdecode.Decode(cfg); err != nil { - // no environment variable set for this config is an expected "error" - if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { - return err - } - } - - defaults.Sanitize(cfg) - - return Validate(cfg) -} - -func Validate(cfg *config.Config) error { - return nil -} diff --git a/extensions/webdav/pkg/dav/requests/thumbnail.go b/extensions/webdav/pkg/dav/requests/thumbnail.go deleted file mode 100644 index 134034cd3af..00000000000 --- a/extensions/webdav/pkg/dav/requests/thumbnail.go +++ /dev/null @@ -1,91 +0,0 @@ -package requests - -import ( - "fmt" - "net/http" - "net/url" - "path/filepath" - "strconv" - - "github.com/go-chi/chi/v5" - - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/constants" -) - -const ( - // DefaultWidth defines the default width of a thumbnail - DefaultWidth = 32 - // DefaultHeight defines the default height of a thumbnail - DefaultHeight = 32 -) - -// ThumbnailRequest combines all parameters provided when requesting a thumbnail -type ThumbnailRequest struct { - // The file path of the source file - Filepath string - // The file name of the source file including the extension - Filename string - // The file extension - Extension string - // The requested width of the thumbnail - Width int32 - // The requested height of the thumbnail - Height int32 - // In case of a public share the public link token. - PublicLinkToken string - // The Identifier from the requested URL - Identifier string -} - -// ParseThumbnailRequest extracts all required parameters from a http request. -func ParseThumbnailRequest(r *http.Request) (*ThumbnailRequest, error) { - ctx := r.Context() - - fp := ctx.Value(constants.ContextKeyPath).(string) - - id := "" - v := ctx.Value(constants.ContextKeyID) - if v != nil { - id = v.(string) - } - - q := r.URL.Query() - width, height, err := parseDimensions(q) - if err != nil { - return nil, err - } - - return &ThumbnailRequest{ - Filepath: fp, - Filename: filepath.Base(fp), - Extension: filepath.Ext(fp), - Width: int32(width), - Height: int32(height), - PublicLinkToken: chi.URLParam(r, "token"), - Identifier: id, - }, nil -} - -func parseDimensions(q url.Values) (int64, int64, error) { - width, err := parseDimension(q.Get("x"), "width", DefaultWidth) - if err != nil { - return 0, 0, err - } - height, err := parseDimension(q.Get("y"), "height", DefaultHeight) - if err != nil { - return 0, 0, err - } - return width, height, nil -} - -func parseDimension(d, name string, defaultValue int64) (int64, error) { - if d == "" { - return defaultValue, nil - } - result, err := strconv.ParseInt(d, 10, 32) - if err != nil || result < 1 { - // The error message doesn't fit but for OC10 API compatibility reasons we have to set this. - return 0, fmt.Errorf("Cannot set %s of 0 or smaller!", name) - } - return result, nil -} diff --git a/extensions/webdav/pkg/logging/logging.go b/extensions/webdav/pkg/logging/logging.go deleted file mode 100644 index 1d43635959c..00000000000 --- a/extensions/webdav/pkg/logging/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package logging - -import ( - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/config" - "github.com/owncloud/ocis/v2/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/webdav/pkg/server/debug/option.go b/extensions/webdav/pkg/server/debug/option.go deleted file mode 100644 index 8f070a68e11..00000000000 --- a/extensions/webdav/pkg/server/debug/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package debug - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/config" - "github.com/owncloud/ocis/v2/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/webdav/pkg/server/debug/server.go b/extensions/webdav/pkg/server/debug/server.go deleted file mode 100644 index b6905ec4596..00000000000 --- a/extensions/webdav/pkg/server/debug/server.go +++ /dev/null @@ -1,63 +0,0 @@ -package debug - -import ( - "io" - "net/http" - - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" - "github.com/owncloud/ocis/v2/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.GetString()), - 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/webdav/pkg/server/http/option.go b/extensions/webdav/pkg/server/http/option.go deleted file mode 100644 index ceff8482c1a..00000000000 --- a/extensions/webdav/pkg/server/http/option.go +++ /dev/null @@ -1,76 +0,0 @@ -package http - -import ( - "context" - - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/config" - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/metrics" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/urfave/cli/v2" -) - -// Option defines a single option function. -type Option func(o *Options) - -// Options defines the available options for this package. -type Options struct { - Namespace string - Logger log.Logger - Context context.Context - Config *config.Config - Metrics *metrics.Metrics - Flags []cli.Flag -} - -// 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 - } -} - -// Metrics provides a function to set the metrics option. -func Metrics(val *metrics.Metrics) Option { - return func(o *Options) { - o.Metrics = val - } -} - -// Flags provides a function to set the flags option. -func Flags(val []cli.Flag) Option { - return func(o *Options) { - o.Flags = append(o.Flags, val...) - } -} - -// Namespace provides a function to set the namespace option. -func Namespace(val string) Option { - return func(o *Options) { - o.Namespace = val - } -} diff --git a/extensions/webdav/pkg/server/http/server.go b/extensions/webdav/pkg/server/http/server.go deleted file mode 100644 index 498f9926cb4..00000000000 --- a/extensions/webdav/pkg/server/http/server.go +++ /dev/null @@ -1,66 +0,0 @@ -package http - -import ( - chimiddleware "github.com/go-chi/chi/v5/middleware" - svc "github.com/owncloud/ocis/v2/extensions/webdav/pkg/service/v0" - "github.com/owncloud/ocis/v2/ocis-pkg/cors" - "github.com/owncloud/ocis/v2/ocis-pkg/middleware" - "github.com/owncloud/ocis/v2/ocis-pkg/service/http" - "github.com/owncloud/ocis/v2/ocis-pkg/version" - "go-micro.dev/v4" -) - -// Server initializes the http service and server. -func Server(opts ...Option) (http.Service, error) { - options := newOptions(opts...) - - service := http.NewService( - http.Logger(options.Logger), - http.Namespace(options.Config.HTTP.Namespace), - http.Name(options.Config.Service.Name), - http.Version(version.GetString()), - http.Address(options.Config.HTTP.Addr), - http.Context(options.Context), - http.Flags(options.Flags...), - ) - - handle, err := svc.NewService( - svc.Logger(options.Logger), - svc.Config(options.Config), - svc.Middleware( - chimiddleware.RealIP, - chimiddleware.RequestID, - middleware.NoCache, - middleware.Cors( - cors.Logger(options.Logger), - cors.AllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins), - cors.AllowedMethods(options.Config.HTTP.CORS.AllowedMethods), - cors.AllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders), - cors.AllowCredentials(options.Config.HTTP.CORS.AllowCredentials), - ), - middleware.Secure, - middleware.Version( - options.Config.Service.Name, - version.GetString(), - ), - middleware.Logger( - options.Logger, - ), - ), - ) - if err != nil { - return http.Service{}, err - } - - { - handle = svc.NewInstrument(handle, options.Metrics) - handle = svc.NewLogging(handle, options.Logger) - handle = svc.NewTracing(handle) - } - - if err := micro.RegisterHandler(service.Server(), handle); err != nil { - return http.Service{}, err - } - - return service, nil -} diff --git a/extensions/webdav/pkg/service/v0/instrument.go b/extensions/webdav/pkg/service/v0/instrument.go deleted file mode 100644 index 34e318c5899..00000000000 --- a/extensions/webdav/pkg/service/v0/instrument.go +++ /dev/null @@ -1,30 +0,0 @@ -package svc - -import ( - "net/http" - - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/metrics" -) - -// NewInstrument returns a service that instruments metrics. -func NewInstrument(next Service, metrics *metrics.Metrics) Service { - return instrument{ - next: next, - metrics: metrics, - } -} - -type instrument struct { - next Service - metrics *metrics.Metrics -} - -// ServeHTTP implements the Service interface. -func (i instrument) ServeHTTP(w http.ResponseWriter, r *http.Request) { - i.next.ServeHTTP(w, r) -} - -// Thumbnail implements the Service interface. -func (i instrument) Thumbnail(w http.ResponseWriter, r *http.Request) { - i.next.Thumbnail(w, r) -} diff --git a/extensions/webdav/pkg/service/v0/option.go b/extensions/webdav/pkg/service/v0/option.go deleted file mode 100644 index 1bdab9a32f4..00000000000 --- a/extensions/webdav/pkg/service/v0/option.go +++ /dev/null @@ -1,50 +0,0 @@ -package svc - -import ( - "net/http" - - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/config" - "github.com/owncloud/ocis/v2/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 - Config *config.Config - Middleware []func(http.Handler) http.Handler -} - -// 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 - } -} - -// Config provides a function to set the config option. -func Config(val *config.Config) Option { - return func(o *Options) { - o.Config = val - } -} - -// Middleware provides a function to set the middleware option. -func Middleware(val ...func(http.Handler) http.Handler) Option { - return func(o *Options) { - o.Middleware = val - } -} diff --git a/extensions/webdav/pkg/service/v0/service.go b/extensions/webdav/pkg/service/v0/service.go deleted file mode 100644 index a914169d93e..00000000000 --- a/extensions/webdav/pkg/service/v0/service.go +++ /dev/null @@ -1,493 +0,0 @@ -package svc - -import ( - "context" - "encoding/xml" - "io" - "net/http" - "net/url" - "path" - "path/filepath" - "strings" - - gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" - userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" - "github.com/cs3org/reva/v2/pkg/storage/utils/templates" - "github.com/go-chi/chi/v5" - "github.com/go-chi/render" - merrors "go-micro.dev/v4/errors" - "google.golang.org/grpc/metadata" - - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/config" - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/constants" - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/dav/requests" - "github.com/owncloud/ocis/v2/ocis-pkg/log" - "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" - thumbnailsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/thumbnails/v0" - searchsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/search/v0" - thumbnailssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/thumbnails/v0" -) - -const ( - TokenHeader = "X-Access-Token" -) - -var ( - codesEnum = map[int]string{ - http.StatusBadRequest: "Sabre\\DAV\\Exception\\BadRequest", - http.StatusUnauthorized: "Sabre\\DAV\\Exception\\NotAuthenticated", - http.StatusNotFound: "Sabre\\DAV\\Exception\\NotFound", - http.StatusMethodNotAllowed: "Sabre\\DAV\\Exception\\MethodNotAllowed", - } -) - -// Service defines the extension handlers. -type Service interface { - ServeHTTP(http.ResponseWriter, *http.Request) - Thumbnail(http.ResponseWriter, *http.Request) -} - -// NewService returns a service implementation for Service. -func NewService(opts ...Option) (Service, error) { - options := newOptions(opts...) - conf := options.Config - - m := chi.NewMux() - // 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) - if err != nil { - return nil, err - } - - svc := Webdav{ - config: conf, - log: options.Logger, - mux: m, - searchClient: searchsvc.NewSearchProviderService("com.owncloud.api.search", grpc.DefaultClient), - thumbnailsClient: thumbnailssvc.NewThumbnailService("com.owncloud.api.thumbnails", grpc.DefaultClient), - revaClient: gwc, - } - - m.Route(options.Config.HTTP.Root, func(r chi.Router) { - - r.Group(func(r chi.Router) { - r.Use(svc.DavUserContext()) - - r.Get("/remote.php/dav/spaces/{id}", svc.SpacesThumbnail) - r.Get("/remote.php/dav/spaces/{id}/*", svc.SpacesThumbnail) - r.Get("/dav/spaces/{id}", svc.SpacesThumbnail) - r.Get("/dav/spaces/{id}/*", svc.SpacesThumbnail) - - r.Get("/remote.php/dav/files/{id}", svc.Thumbnail) - r.Get("/remote.php/dav/files/{id}/*", svc.Thumbnail) - r.Get("/dav/files/{id}", svc.Thumbnail) - r.Get("/dav/files/{id}/*", svc.Thumbnail) - }) - - r.Group(func(r chi.Router) { - r.Use(svc.DavPublicContext()) - - r.Head("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnailHead) - r.Head("/dav/public-files/{token}/*", svc.PublicThumbnailHead) - - r.Get("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnail) - r.Get("/dav/public-files/{token}/*", svc.PublicThumbnail) - }) - - r.Group(func(r chi.Router) { - r.Use(svc.WebDAVContext()) - r.Get("/remote.php/webdav/*", svc.Thumbnail) - r.Get("/webdav/*", svc.Thumbnail) - }) - - // 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 -} - -// Webdav implements the business logic for Service. -type Webdav struct { - config *config.Config - log log.Logger - mux *chi.Mux - searchClient searchsvc.SearchProviderService - thumbnailsClient thumbnailssvc.ThumbnailService - revaClient gatewayv1beta1.GatewayAPIClient -} - -// ServeHTTP implements the Service interface. -func (g Webdav) ServeHTTP(w http.ResponseWriter, r *http.Request) { - g.mux.ServeHTTP(w, r) -} - -func (g Webdav) DavUserContext() func(next http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - filePath := r.URL.Path - - id := chi.URLParam(r, "id") - id, err := url.QueryUnescape(id) - if err == nil && id != "" { - ctx = context.WithValue(ctx, constants.ContextKeyID, id) - } - - if id != "" { - filePath = strings.TrimPrefix(filePath, path.Join("/remote.php/dav/spaces", id)) - filePath = strings.TrimPrefix(filePath, path.Join("/dav/spaces", id)) - - filePath = strings.TrimPrefix(filePath, path.Join("/remote.php/dav/files", id)) - filePath = strings.TrimPrefix(filePath, path.Join("/dav/files", id)) - filePath = strings.TrimPrefix(filePath, "/") - } - - ctx = context.WithValue(ctx, constants.ContextKeyPath, filePath) - - next.ServeHTTP(w, r.WithContext(ctx)) - - }) - } -} - -func (g Webdav) DavPublicContext() func(next http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - filePath := r.URL.Path - - if token := chi.URLParam(r, "token"); token != "" { - filePath = strings.TrimPrefix(filePath, path.Join("/remote.php/dav/public-files", token)+"/") - filePath = strings.TrimPrefix(filePath, path.Join("/dav/public-files", token)+"/") - } - ctx = context.WithValue(ctx, constants.ContextKeyPath, filePath) - - next.ServeHTTP(w, r.WithContext(ctx)) - - }) - } -} -func (g Webdav) WebDAVContext() func(next http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - - filePath := r.URL.Path - filePath = strings.TrimPrefix(filePath, "/remote.php") - filePath = strings.TrimPrefix(filePath, "/webdav/") - - ctx := context.WithValue(r.Context(), constants.ContextKeyPath, filePath) - - next.ServeHTTP(w, r.WithContext(ctx)) - - }) - } -} - -// SpacesThumbnail is the endpoint for retrieving thumbnails inside of spaces. -func (g Webdav) SpacesThumbnail(w http.ResponseWriter, r *http.Request) { - tr, err := requests.ParseThumbnailRequest(r) - if err != nil { - g.log.Error().Err(err).Msg("could not create Request") - renderError(w, r, errBadRequest(err.Error())) - return - } - t := r.Header.Get(TokenHeader) - - fullPath := filepath.Join(tr.Identifier, tr.Filepath) - rsp, err := g.thumbnailsClient.GetThumbnail(r.Context(), &thumbnailssvc.GetThumbnailRequest{ - Filepath: strings.TrimLeft(tr.Filepath, "/"), - ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")), - Width: tr.Width, - Height: tr.Height, - Source: &thumbnailssvc.GetThumbnailRequest_Cs3Source{ - Cs3Source: &thumbnailsmsg.CS3Source{ - Path: fullPath, - Authorization: t, - }, - }, - }) - if err != nil { - e := merrors.Parse(err.Error()) - switch e.Code { - case http.StatusNotFound: - // StatusNotFound is expected for unsupported files - renderError(w, r, errNotFound(notFoundMsg(tr.Filename))) - return - case http.StatusBadRequest: - renderError(w, r, errBadRequest(err.Error())) - default: - renderError(w, r, errInternalError(err.Error())) - } - g.log.Error().Err(err).Msg("could not get thumbnail") - return - } - - g.sendThumbnailResponse(rsp, w, r) -} - -// Thumbnail implements the Service interface. -func (g Webdav) Thumbnail(w http.ResponseWriter, r *http.Request) { - tr, err := requests.ParseThumbnailRequest(r) - if err != nil { - g.log.Error().Err(err).Msg("could not create Request") - renderError(w, r, errBadRequest(err.Error())) - return - } - - t := r.Header.Get(TokenHeader) - - var user *userv1beta1.User - - if tr.Identifier == "" { - // look up user from token via WhoAmI - userRes, err := g.revaClient.WhoAmI(r.Context(), &gatewayv1beta1.WhoAmIRequest{ - Token: t, - }) - if err != nil || userRes.Status.Code != rpcv1beta1.Code_CODE_OK { - g.log.Error().Err(err).Msg("could not get user") - renderError(w, r, errInternalError("could not get user")) - return - } - user = userRes.GetUser() - } else { - // look up user from URL via GetUserByClaim - ctx := metadata.AppendToOutgoingContext(r.Context(), TokenHeader, t) - userRes, err := g.revaClient.GetUserByClaim(ctx, &userv1beta1.GetUserByClaimRequest{ - Claim: "username", - Value: tr.Identifier, - }) - if err != nil || userRes.Status.Code != rpcv1beta1.Code_CODE_OK { - g.log.Error().Err(err).Msg("could not get user") - renderError(w, r, errInternalError("could not get user")) - return - } - user = userRes.GetUser() - } - - fullPath := filepath.Join(templates.WithUser(user, g.config.WebdavNamespace), tr.Filepath) - rsp, err := g.thumbnailsClient.GetThumbnail(r.Context(), &thumbnailssvc.GetThumbnailRequest{ - Filepath: strings.TrimLeft(tr.Filepath, "/"), - ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")), - Width: tr.Width, - Height: tr.Height, - Source: &thumbnailssvc.GetThumbnailRequest_Cs3Source{ - Cs3Source: &thumbnailsmsg.CS3Source{ - Path: fullPath, - Authorization: t, - }, - }, - }) - if err != nil { - e := merrors.Parse(err.Error()) - switch e.Code { - case http.StatusNotFound: - // StatusNotFound is expected for unsupported files - renderError(w, r, errNotFound(notFoundMsg(tr.Filename))) - return - case http.StatusBadRequest: - renderError(w, r, errBadRequest(err.Error())) - default: - renderError(w, r, errInternalError(err.Error())) - } - g.log.Error().Err(err).Msg("could not get thumbnail") - return - } - - g.sendThumbnailResponse(rsp, w, r) -} - -func (g Webdav) PublicThumbnail(w http.ResponseWriter, r *http.Request) { - tr, err := requests.ParseThumbnailRequest(r) - if err != nil { - g.log.Error().Err(err).Msg("could not create Request") - renderError(w, r, errBadRequest(err.Error())) - return - } - - rsp, err := g.thumbnailsClient.GetThumbnail(r.Context(), &thumbnailssvc.GetThumbnailRequest{ - Filepath: strings.TrimLeft(tr.Filepath, "/"), - ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")), - Width: tr.Width, - Height: tr.Height, - Source: &thumbnailssvc.GetThumbnailRequest_WebdavSource{ - WebdavSource: &thumbnailsmsg.WebdavSource{ - Url: g.config.OcisPublicURL + r.URL.RequestURI(), - IsPublicLink: true, - PublicLinkToken: tr.PublicLinkToken, - }, - }, - }) - if err != nil { - e := merrors.Parse(err.Error()) - switch e.Code { - case http.StatusNotFound: - // StatusNotFound is expected for unsupported files - renderError(w, r, errNotFound(notFoundMsg(tr.Filename))) - return - case http.StatusBadRequest: - renderError(w, r, errBadRequest(err.Error())) - default: - renderError(w, r, errInternalError(err.Error())) - } - g.log.Error().Err(err).Msg("could not get thumbnail") - return - } - - g.sendThumbnailResponse(rsp, w, r) -} - -func (g Webdav) PublicThumbnailHead(w http.ResponseWriter, r *http.Request) { - tr, err := requests.ParseThumbnailRequest(r) - if err != nil { - g.log.Error().Err(err).Msg("could not create Request") - renderError(w, r, errBadRequest(err.Error())) - return - } - - _, err = g.thumbnailsClient.GetThumbnail(r.Context(), &thumbnailssvc.GetThumbnailRequest{ - Filepath: strings.TrimLeft(tr.Filepath, "/"), - ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")), - Width: tr.Width, - Height: tr.Height, - Source: &thumbnailssvc.GetThumbnailRequest_WebdavSource{ - WebdavSource: &thumbnailsmsg.WebdavSource{ - Url: g.config.OcisPublicURL + r.URL.RequestURI(), - IsPublicLink: true, - PublicLinkToken: tr.PublicLinkToken, - }, - }, - }) - if err != nil { - e := merrors.Parse(err.Error()) - switch e.Code { - case http.StatusNotFound: - // StatusNotFound is expected for unsupported files - renderError(w, r, errNotFound(notFoundMsg(tr.Filename))) - return - case http.StatusBadRequest: - renderError(w, r, errBadRequest(err.Error())) - default: - renderError(w, r, errInternalError(err.Error())) - } - g.log.Error().Err(err).Msg("could not get thumbnail") - return - } - - w.WriteHeader(http.StatusOK) -} - -func (g Webdav) sendThumbnailResponse(rsp *thumbnailssvc.GetThumbnailResponse, w http.ResponseWriter, r *http.Request) { - client := &http.Client{ - // Timeout: time.Second * 5, - } - - dlReq, err := http.NewRequest(http.MethodGet, rsp.DataEndpoint, http.NoBody) - if err != nil { - renderError(w, r, errInternalError(err.Error())) - g.log.Error().Err(err).Msg("could not download thumbnail") - return - } - dlReq.Header.Set("Transfer-Token", rsp.TransferToken) - - dlRsp, err := client.Do(dlReq) - if err != nil { - renderError(w, r, errInternalError(err.Error())) - g.log.Error().Err(err).Msg("could not download thumbnail") - return - } - defer dlRsp.Body.Close() - - if dlRsp.StatusCode != http.StatusOK { - g.log.Error(). - Str("transfer_token", rsp.TransferToken). - Str("data_endpoint", rsp.DataEndpoint). - Str("response_status", dlRsp.Status). - Msg("could not download thumbnail") - renderError(w, r, errInternalError("could not download thumbnail")) - return - } - - w.WriteHeader(http.StatusOK) - w.Header().Set("Content-Type", rsp.Mimetype) - _, err = io.Copy(w, dlRsp.Body) - if err != nil { - g.log.Error().Err(err).Msg("failed to write thumbnail to response writer") - } -} - -func extensionToThumbnailType(ext string) thumbnailsmsg.ThumbnailType { - switch strings.ToUpper(ext) { - case "GIF": - return thumbnailsmsg.ThumbnailType_GIF - case "PNG": - return thumbnailsmsg.ThumbnailType_PNG - default: - return thumbnailsmsg.ThumbnailType_JPG - } -} - -// http://www.webdav.org/specs/rfc4918.html#ELEMENT_error -type errResponse struct { - HTTPStatusCode int `json:"-" xml:"-"` - XMLName xml.Name `xml:"d:error"` - Xmlnsd string `xml:"xmlns:d,attr"` - Xmlnss string `xml:"xmlns:s,attr"` - Exception string `xml:"s:exception"` - Message string `xml:"s:message"` - InnerXML []byte `xml:",innerxml"` -} - -func newErrResponse(statusCode int, msg string) *errResponse { - rsp := &errResponse{ - HTTPStatusCode: statusCode, - Xmlnsd: "DAV", - Xmlnss: "http://sabredav.org/ns", - Exception: codesEnum[statusCode], - } - if msg != "" { - rsp.Message = msg - } - return rsp -} - -func errInternalError(msg string) *errResponse { - return newErrResponse(http.StatusInternalServerError, msg) -} - -func errBadRequest(msg string) *errResponse { - return newErrResponse(http.StatusBadRequest, msg) -} - -func errNotFound(msg string) *errResponse { - return newErrResponse(http.StatusNotFound, msg) -} - -func renderError(w http.ResponseWriter, r *http.Request, err *errResponse) { - render.Status(r, err.HTTPStatusCode) - render.XML(w, r, err) -} - -func notFoundMsg(name string) string { - return "File with name " + name + " could not be located" -} diff --git a/extensions/webdav/pkg/tracing/tracing.go b/extensions/webdav/pkg/tracing/tracing.go deleted file mode 100644 index 6e7d39a3bba..00000000000 --- a/extensions/webdav/pkg/tracing/tracing.go +++ /dev/null @@ -1,23 +0,0 @@ -package tracing - -import ( - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/config" - pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" - "go.opentelemetry.io/otel/trace" -) - -var ( - // TraceProvider is the global trace provider for the proxy service. - TraceProvider = trace.NewNoopTracerProvider() -) - -func Configure(cfg *config.Config) error { - var err error - if cfg.Tracing.Enabled { - if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { - return err - } - } - - return nil -} diff --git a/ocis-pkg/config/config.go b/ocis-pkg/config/config.go index 4028a689ec8..c5c2f03588b 100644 --- a/ocis-pkg/config/config.go +++ b/ocis-pkg/config/config.go @@ -3,36 +3,36 @@ package config import ( "github.com/owncloud/ocis/v2/ocis-pkg/shared" - appProvider "github.com/owncloud/ocis/v2/extensions/app-provider/pkg/config" - appRegistry "github.com/owncloud/ocis/v2/extensions/app-registry/pkg/config" - audit "github.com/owncloud/ocis/v2/extensions/audit/pkg/config" - authbasic "github.com/owncloud/ocis/v2/extensions/auth-basic/pkg/config" - authbearer "github.com/owncloud/ocis/v2/extensions/auth-bearer/pkg/config" - authmachine "github.com/owncloud/ocis/v2/extensions/auth-machine/pkg/config" - frontend "github.com/owncloud/ocis/v2/extensions/frontend/pkg/config" - gateway "github.com/owncloud/ocis/v2/extensions/gateway/pkg/config" - graphExplorer "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/config" - graph "github.com/owncloud/ocis/v2/extensions/graph/pkg/config" - groups "github.com/owncloud/ocis/v2/extensions/groups/pkg/config" - idm "github.com/owncloud/ocis/v2/extensions/idm/pkg/config" - idp "github.com/owncloud/ocis/v2/extensions/idp/pkg/config" - nats "github.com/owncloud/ocis/v2/extensions/nats/pkg/config" - notifications "github.com/owncloud/ocis/v2/extensions/notifications/pkg/config" - ocdav "github.com/owncloud/ocis/v2/extensions/ocdav/pkg/config" - ocs "github.com/owncloud/ocis/v2/extensions/ocs/pkg/config" - proxy "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config" - search "github.com/owncloud/ocis/v2/extensions/search/pkg/config" - settings "github.com/owncloud/ocis/v2/extensions/settings/pkg/config" - sharing "github.com/owncloud/ocis/v2/extensions/sharing/pkg/config" - storagepublic "github.com/owncloud/ocis/v2/extensions/storage-publiclink/pkg/config" - storageshares "github.com/owncloud/ocis/v2/extensions/storage-shares/pkg/config" - storagesystem "github.com/owncloud/ocis/v2/extensions/storage-system/pkg/config" - storageusers "github.com/owncloud/ocis/v2/extensions/storage-users/pkg/config" - store "github.com/owncloud/ocis/v2/extensions/store/pkg/config" - thumbnails "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/config" - users "github.com/owncloud/ocis/v2/extensions/users/pkg/config" - web "github.com/owncloud/ocis/v2/extensions/web/pkg/config" - webdav "github.com/owncloud/ocis/v2/extensions/webdav/pkg/config" + appProvider "github.com/owncloud/ocis/v2/services/app-provider/pkg/config" + appRegistry "github.com/owncloud/ocis/v2/services/app-registry/pkg/config" + audit "github.com/owncloud/ocis/v2/services/audit/pkg/config" + authbasic "github.com/owncloud/ocis/v2/services/auth-basic/pkg/config" + authbearer "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/config" + authmachine "github.com/owncloud/ocis/v2/services/auth-machine/pkg/config" + frontend "github.com/owncloud/ocis/v2/services/frontend/pkg/config" + gateway "github.com/owncloud/ocis/v2/services/gateway/pkg/config" + graphExplorer "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/config" + graph "github.com/owncloud/ocis/v2/services/graph/pkg/config" + groups "github.com/owncloud/ocis/v2/services/groups/pkg/config" + idm "github.com/owncloud/ocis/v2/services/idm/pkg/config" + idp "github.com/owncloud/ocis/v2/services/idp/pkg/config" + nats "github.com/owncloud/ocis/v2/services/nats/pkg/config" + notifications "github.com/owncloud/ocis/v2/services/notifications/pkg/config" + ocdav "github.com/owncloud/ocis/v2/services/ocdav/pkg/config" + ocs "github.com/owncloud/ocis/v2/services/ocs/pkg/config" + proxy "github.com/owncloud/ocis/v2/services/proxy/pkg/config" + search "github.com/owncloud/ocis/v2/services/search/pkg/config" + settings "github.com/owncloud/ocis/v2/services/settings/pkg/config" + sharing "github.com/owncloud/ocis/v2/services/sharing/pkg/config" + storagepublic "github.com/owncloud/ocis/v2/services/storage-publiclink/pkg/config" + storageshares "github.com/owncloud/ocis/v2/services/storage-shares/pkg/config" + storagesystem "github.com/owncloud/ocis/v2/services/storage-system/pkg/config" + storageusers "github.com/owncloud/ocis/v2/services/storage-users/pkg/config" + store "github.com/owncloud/ocis/v2/services/store/pkg/config" + thumbnails "github.com/owncloud/ocis/v2/services/thumbnails/pkg/config" + users "github.com/owncloud/ocis/v2/services/users/pkg/config" + web "github.com/owncloud/ocis/v2/services/web/pkg/config" + webdav "github.com/owncloud/ocis/v2/services/webdav/pkg/config" ) const ( @@ -49,7 +49,7 @@ type Mode int type Runtime struct { Port string `yaml:"port" env:"OCIS_RUNTIME_PORT"` Host string `yaml:"host" env:"OCIS_RUNTIME_HOST"` - Extensions string `yaml:"extensions" env:"OCIS_RUN_EXTENSIONS"` + Extensions string `yaml:"services" env:"OCIS_RUN_EXTENSIONS,OCIS_RUN_SERVICES"` } // Config combines all available configuration parts. diff --git a/ocis-pkg/config/defaultconfig.go b/ocis-pkg/config/defaultconfig.go index b3fa9d1af50..b0546c7af00 100644 --- a/ocis-pkg/config/defaultconfig.go +++ b/ocis-pkg/config/defaultconfig.go @@ -1,36 +1,36 @@ package config import ( - appProvider "github.com/owncloud/ocis/v2/extensions/app-provider/pkg/config/defaults" - appRegistry "github.com/owncloud/ocis/v2/extensions/app-registry/pkg/config/defaults" - audit "github.com/owncloud/ocis/v2/extensions/audit/pkg/config/defaults" - authbasic "github.com/owncloud/ocis/v2/extensions/auth-basic/pkg/config/defaults" - authbearer "github.com/owncloud/ocis/v2/extensions/auth-bearer/pkg/config/defaults" - authmachine "github.com/owncloud/ocis/v2/extensions/auth-machine/pkg/config/defaults" - frontend "github.com/owncloud/ocis/v2/extensions/frontend/pkg/config/defaults" - gateway "github.com/owncloud/ocis/v2/extensions/gateway/pkg/config/defaults" - graphExplorer "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/config/defaults" - graph "github.com/owncloud/ocis/v2/extensions/graph/pkg/config/defaults" - groups "github.com/owncloud/ocis/v2/extensions/groups/pkg/config/defaults" - idm "github.com/owncloud/ocis/v2/extensions/idm/pkg/config/defaults" - idp "github.com/owncloud/ocis/v2/extensions/idp/pkg/config/defaults" - nats "github.com/owncloud/ocis/v2/extensions/nats/pkg/config/defaults" - notifications "github.com/owncloud/ocis/v2/extensions/notifications/pkg/config/defaults" - ocdav "github.com/owncloud/ocis/v2/extensions/ocdav/pkg/config/defaults" - ocs "github.com/owncloud/ocis/v2/extensions/ocs/pkg/config/defaults" - proxy "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config/defaults" - search "github.com/owncloud/ocis/v2/extensions/search/pkg/config/defaults" - settings "github.com/owncloud/ocis/v2/extensions/settings/pkg/config/defaults" - sharing "github.com/owncloud/ocis/v2/extensions/sharing/pkg/config/defaults" - storagepublic "github.com/owncloud/ocis/v2/extensions/storage-publiclink/pkg/config/defaults" - storageshares "github.com/owncloud/ocis/v2/extensions/storage-shares/pkg/config/defaults" - storageSystem "github.com/owncloud/ocis/v2/extensions/storage-system/pkg/config/defaults" - storageusers "github.com/owncloud/ocis/v2/extensions/storage-users/pkg/config/defaults" - store "github.com/owncloud/ocis/v2/extensions/store/pkg/config/defaults" - thumbnails "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/config/defaults" - users "github.com/owncloud/ocis/v2/extensions/users/pkg/config/defaults" - web "github.com/owncloud/ocis/v2/extensions/web/pkg/config/defaults" - webdav "github.com/owncloud/ocis/v2/extensions/webdav/pkg/config/defaults" + appProvider "github.com/owncloud/ocis/v2/services/app-provider/pkg/config/defaults" + appRegistry "github.com/owncloud/ocis/v2/services/app-registry/pkg/config/defaults" + audit "github.com/owncloud/ocis/v2/services/audit/pkg/config/defaults" + authbasic "github.com/owncloud/ocis/v2/services/auth-basic/pkg/config/defaults" + authbearer "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/config/defaults" + authmachine "github.com/owncloud/ocis/v2/services/auth-machine/pkg/config/defaults" + frontend "github.com/owncloud/ocis/v2/services/frontend/pkg/config/defaults" + gateway "github.com/owncloud/ocis/v2/services/gateway/pkg/config/defaults" + graphExplorer "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/config/defaults" + graph "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" + groups "github.com/owncloud/ocis/v2/services/groups/pkg/config/defaults" + idm "github.com/owncloud/ocis/v2/services/idm/pkg/config/defaults" + idp "github.com/owncloud/ocis/v2/services/idp/pkg/config/defaults" + nats "github.com/owncloud/ocis/v2/services/nats/pkg/config/defaults" + notifications "github.com/owncloud/ocis/v2/services/notifications/pkg/config/defaults" + ocdav "github.com/owncloud/ocis/v2/services/ocdav/pkg/config/defaults" + ocs "github.com/owncloud/ocis/v2/services/ocs/pkg/config/defaults" + proxy "github.com/owncloud/ocis/v2/services/proxy/pkg/config/defaults" + search "github.com/owncloud/ocis/v2/services/search/pkg/config/defaults" + settings "github.com/owncloud/ocis/v2/services/settings/pkg/config/defaults" + sharing "github.com/owncloud/ocis/v2/services/sharing/pkg/config/defaults" + storagepublic "github.com/owncloud/ocis/v2/services/storage-publiclink/pkg/config/defaults" + storageshares "github.com/owncloud/ocis/v2/services/storage-shares/pkg/config/defaults" + storageSystem "github.com/owncloud/ocis/v2/services/storage-system/pkg/config/defaults" + storageusers "github.com/owncloud/ocis/v2/services/storage-users/pkg/config/defaults" + store "github.com/owncloud/ocis/v2/services/store/pkg/config/defaults" + thumbnails "github.com/owncloud/ocis/v2/services/thumbnails/pkg/config/defaults" + users "github.com/owncloud/ocis/v2/services/users/pkg/config/defaults" + web "github.com/owncloud/ocis/v2/services/web/pkg/config/defaults" + webdav "github.com/owncloud/ocis/v2/services/webdav/pkg/config/defaults" ) func DefaultConfig() *Config { diff --git a/ocis/pkg/command/app-provider.go b/ocis/pkg/command/app-provider.go index 0ebd0272451..89120bd57f3 100644 --- a/ocis/pkg/command/app-provider.go +++ b/ocis/pkg/command/app-provider.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/app-provider/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/app-provider/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func AppProviderCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.AppProvider.Service.Name, Usage: helper.SubcommandDescription(cfg.AppProvider.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/app-registry.go b/ocis/pkg/command/app-registry.go index a859f035286..b16df293a80 100644 --- a/ocis/pkg/command/app-registry.go +++ b/ocis/pkg/command/app-registry.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/app-registry/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/app-registry/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func AppRegistryCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.AppRegistry.Service.Name, Usage: helper.SubcommandDescription(cfg.AppRegistry.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/audit.go b/ocis/pkg/command/audit.go index 3978adf5152..58b4d4e0a67 100644 --- a/ocis/pkg/command/audit.go +++ b/ocis/pkg/command/audit.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/audit/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/audit/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func AuditCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.Audit.Service.Name, Usage: helper.SubcommandDescription(cfg.Audit.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/auth-basic.go b/ocis/pkg/command/auth-basic.go index 1a61993d36b..3a850faa67a 100644 --- a/ocis/pkg/command/auth-basic.go +++ b/ocis/pkg/command/auth-basic.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/auth-basic/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/auth-basic/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func AuthBasicCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.AuthBasic.Service.Name, Usage: helper.SubcommandDescription(cfg.AuthBasic.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/auth-bearer.go b/ocis/pkg/command/auth-bearer.go index d30be5f6d5e..be1e3605039 100644 --- a/ocis/pkg/command/auth-bearer.go +++ b/ocis/pkg/command/auth-bearer.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/auth-bearer/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func AuthBearerCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.AuthBearer.Service.Name, Usage: helper.SubcommandDescription(cfg.AuthBearer.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/auth-machine.go b/ocis/pkg/command/auth-machine.go index 485e7ddd026..336823efab2 100644 --- a/ocis/pkg/command/auth-machine.go +++ b/ocis/pkg/command/auth-machine.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/auth-machine/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/auth-machine/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func AuthMachineCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.AuthMachine.Service.Name, Usage: helper.SubcommandDescription(cfg.AuthMachine.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/frontend.go b/ocis/pkg/command/frontend.go index cd31c90bb23..741e96cdce7 100644 --- a/ocis/pkg/command/frontend.go +++ b/ocis/pkg/command/frontend.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/frontend/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/frontend/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func FrontendCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.Frontend.Service.Name, Usage: helper.SubcommandDescription(cfg.Frontend.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/gateway.go b/ocis/pkg/command/gateway.go index 2e7e8b08dbf..b771d3c9e9d 100644 --- a/ocis/pkg/command/gateway.go +++ b/ocis/pkg/command/gateway.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/gateway/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/gateway/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func GatewayCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.Gateway.Service.Name, Usage: helper.SubcommandDescription(cfg.Gateway.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/graph-explorer.go b/ocis/pkg/command/graph-explorer.go index 0f7a8f2cf74..4c54deec1ea 100644 --- a/ocis/pkg/command/graph-explorer.go +++ b/ocis/pkg/command/graph-explorer.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func GraphExplorerCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.GraphExplorer.Service.Name, Usage: helper.SubcommandDescription(cfg.GraphExplorer.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/graph.go b/ocis/pkg/command/graph.go index fd604075ce2..ce0acc33e6b 100644 --- a/ocis/pkg/command/graph.go +++ b/ocis/pkg/command/graph.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/graph/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func GraphCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.Graph.Service.Name, Usage: helper.SubcommandDescription(cfg.Graph.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/groups.go b/ocis/pkg/command/groups.go index efce3edcfcd..50394348946 100644 --- a/ocis/pkg/command/groups.go +++ b/ocis/pkg/command/groups.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/groups/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/groups/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func GroupsCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.Groups.Service.Name, Usage: helper.SubcommandDescription(cfg.Groups.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/idm.go b/ocis/pkg/command/idm.go index 70d116f41ce..21862ade96a 100644 --- a/ocis/pkg/command/idm.go +++ b/ocis/pkg/command/idm.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/idm/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/idm/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func IDMCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.IDM.Service.Name, Usage: helper.SubcommandDescription(cfg.IDM.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/idp.go b/ocis/pkg/command/idp.go index 71923b108b9..6b798bf4380 100644 --- a/ocis/pkg/command/idp.go +++ b/ocis/pkg/command/idp.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/idp/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/idp/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func IDPCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.IDP.Service.Name, Usage: helper.SubcommandDescription(cfg.IDP.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/list.go b/ocis/pkg/command/list.go index 2078775ad40..7e85591b54c 100644 --- a/ocis/pkg/command/list.go +++ b/ocis/pkg/command/list.go @@ -15,7 +15,7 @@ import ( func ListCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "list", - Usage: "list oCIS extensions running in the runtime (supervised mode)", + Usage: "list oCIS services running in the runtime (supervised mode)", Category: "runtime", Flags: []cli.Flag{ &cli.StringFlag{ diff --git a/ocis/pkg/command/migrate.go b/ocis/pkg/command/migrate.go index 11bc4866872..3f18b96ece1 100644 --- a/ocis/pkg/command/migrate.go +++ b/ocis/pkg/command/migrate.go @@ -11,12 +11,12 @@ import ( publicregistry "github.com/cs3org/reva/v2/pkg/publicshare/manager/registry" "github.com/cs3org/reva/v2/pkg/share" "github.com/cs3org/reva/v2/pkg/share/manager/registry" - sharing "github.com/owncloud/ocis/v2/extensions/sharing/pkg/config" - sharingparser "github.com/owncloud/ocis/v2/extensions/sharing/pkg/config/parser" + "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" - "github.com/owncloud/ocis/v2/ocis/pkg/register" + sharing "github.com/owncloud/ocis/v2/services/sharing/pkg/config" + sharingparser "github.com/owncloud/ocis/v2/services/sharing/pkg/config/parser" "github.com/rs/zerolog" "github.com/urfave/cli/v2" ) diff --git a/ocis/pkg/command/nats.go b/ocis/pkg/command/nats.go index 31575f5e01a..e8c60061ed0 100644 --- a/ocis/pkg/command/nats.go +++ b/ocis/pkg/command/nats.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/nats/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/nats/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func NatsCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.Nats.Service.Name, Usage: helper.SubcommandDescription(cfg.Nats.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/notifications.go b/ocis/pkg/command/notifications.go index a5580bde554..0599dc86e88 100644 --- a/ocis/pkg/command/notifications.go +++ b/ocis/pkg/command/notifications.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/notifications/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/notifications/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func NotificationsCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.Notifications.Service.Name, Usage: helper.SubcommandDescription(cfg.Notifications.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/ocdav.go b/ocis/pkg/command/ocdav.go index 57f5e73140c..cc04d648847 100644 --- a/ocis/pkg/command/ocdav.go +++ b/ocis/pkg/command/ocdav.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/ocdav/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/ocdav/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func OCDavCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.OCDav.Service.Name, Usage: helper.SubcommandDescription(cfg.OCDav.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/ocs.go b/ocis/pkg/command/ocs.go index 8b5065a4fcd..abeb8310bd0 100644 --- a/ocis/pkg/command/ocs.go +++ b/ocis/pkg/command/ocs.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/ocs/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func OCSCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.OCS.Service.Name, Usage: helper.SubcommandDescription(cfg.OCS.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/proxy.go b/ocis/pkg/command/proxy.go index 51eb65b2a0e..127957bc693 100644 --- a/ocis/pkg/command/proxy.go +++ b/ocis/pkg/command/proxy.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/proxy/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func ProxyCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.Proxy.Service.Name, Usage: helper.SubcommandDescription(cfg.Proxy.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/search.go b/ocis/pkg/command/search.go index f19e6a607f6..c649fbce414 100644 --- a/ocis/pkg/command/search.go +++ b/ocis/pkg/command/search.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/search/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/search/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func SearchCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.Search.Service.Name, Usage: helper.SubcommandDescription(cfg.Search.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/server.go b/ocis/pkg/command/server.go index 2ca6399006f..c935aba9f14 100644 --- a/ocis/pkg/command/server.go +++ b/ocis/pkg/command/server.go @@ -14,7 +14,7 @@ import ( func Server(cfg *config.Config) *cli.Command { return &cli.Command{ Name: "server", - Usage: "start a fullstack server (runtime and all extensions in supervised mode)", + Usage: "start a fullstack server (runtime and all services in supervised mode)", Category: "fullstack", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, false); err != nil { diff --git a/ocis/pkg/command/settings.go b/ocis/pkg/command/settings.go index 4c6257f5d4b..ec95cf3a17a 100644 --- a/ocis/pkg/command/settings.go +++ b/ocis/pkg/command/settings.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/settings/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func SettingsCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.Settings.Service.Name, Usage: helper.SubcommandDescription(cfg.Settings.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/sharing.go b/ocis/pkg/command/sharing.go index a3feca244a8..9145f532c52 100644 --- a/ocis/pkg/command/sharing.go +++ b/ocis/pkg/command/sharing.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/sharing/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/sharing/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func SharingCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.Sharing.Service.Name, Usage: helper.SubcommandDescription(cfg.Sharing.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/storage-publiclink.go b/ocis/pkg/command/storage-publiclink.go index a120d71e663..04780870c5c 100644 --- a/ocis/pkg/command/storage-publiclink.go +++ b/ocis/pkg/command/storage-publiclink.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/storage-publiclink/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/storage-publiclink/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func StoragePublicLinkCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.StoragePublicLink.Service.Name, Usage: helper.SubcommandDescription(cfg.StoragePublicLink.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/storage-shares.go b/ocis/pkg/command/storage-shares.go index 8c066096b21..e499e008f66 100644 --- a/ocis/pkg/command/storage-shares.go +++ b/ocis/pkg/command/storage-shares.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/storage-shares/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/storage-shares/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func StorageSharesCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.StorageShares.Service.Name, Usage: helper.SubcommandDescription(cfg.StorageShares.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/storage-system.go b/ocis/pkg/command/storage-system.go index 72d61cf0f34..e90af469073 100644 --- a/ocis/pkg/command/storage-system.go +++ b/ocis/pkg/command/storage-system.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/storage-system/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/storage-system/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func StorageSystemCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.StorageSystem.Service.Name, Usage: helper.SubcommandDescription(cfg.StorageSystem.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/storage-users.go b/ocis/pkg/command/storage-users.go index 44a1486129c..f1598332bb7 100644 --- a/ocis/pkg/command/storage-users.go +++ b/ocis/pkg/command/storage-users.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/storage-users/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/storage-users/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func StorageUsersCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.StorageUsers.Service.Name, Usage: helper.SubcommandDescription(cfg.StorageUsers.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/store.go b/ocis/pkg/command/store.go index addde0baf86..617af037cd8 100644 --- a/ocis/pkg/command/store.go +++ b/ocis/pkg/command/store.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/store/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/store/pkg/command" "github.com/urfave/cli/v2" ) @@ -17,7 +17,7 @@ func StoreCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.Store.Service.Name, Usage: helper.SubcommandDescription(cfg.Store.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/thumbnails.go b/ocis/pkg/command/thumbnails.go index 82ec98f28ae..da60a12a189 100644 --- a/ocis/pkg/command/thumbnails.go +++ b/ocis/pkg/command/thumbnails.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func ThumbnailsCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.Thumbnails.Service.Name, Usage: helper.SubcommandDescription(cfg.Thumbnails.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/users.go b/ocis/pkg/command/users.go index f1cce21b6c8..992a0492858 100644 --- a/ocis/pkg/command/users.go +++ b/ocis/pkg/command/users.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/users/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/users/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func UsersCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.Users.Service.Name, Usage: helper.SubcommandDescription(cfg.Users.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/web.go b/ocis/pkg/command/web.go index 1255b71c7ba..dd826d6b998 100644 --- a/ocis/pkg/command/web.go +++ b/ocis/pkg/command/web.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/web/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/web/pkg/command" "github.com/urfave/cli/v2" ) @@ -16,7 +16,7 @@ func WebCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.Web.Service.Name, Usage: helper.SubcommandDescription(cfg.Web.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/command/webdav.go b/ocis/pkg/command/webdav.go index d747f567b5f..20be56b5b0e 100644 --- a/ocis/pkg/command/webdav.go +++ b/ocis/pkg/command/webdav.go @@ -3,11 +3,11 @@ package command import ( "fmt" - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/command" "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/webdav/pkg/command" "github.com/urfave/cli/v2" ) @@ -17,7 +17,7 @@ func WebDAVCommand(cfg *config.Config) *cli.Command { return &cli.Command{ Name: cfg.WebDAV.Service.Name, Usage: helper.SubcommandDescription(cfg.WebDAV.Service.Name), - Category: "extensions", + Category: "services", Before: func(c *cli.Context) error { if err := parser.ParseConfig(cfg, true); err != nil { fmt.Printf("%v", err) diff --git a/ocis/pkg/runtime/cmd/list.go b/ocis/pkg/runtime/cmd/list.go index 64e2a9f8bfb..b4d54dd299a 100644 --- a/ocis/pkg/runtime/cmd/list.go +++ b/ocis/pkg/runtime/cmd/list.go @@ -15,7 +15,7 @@ func List(cfg *config.Config) *cobra.Command { return &cobra.Command{ Use: "list", Aliases: []string{"r"}, - Short: "List running extensions", + Short: "List running services", Run: func(cmd *cobra.Command, args []string) { client, err := rpc.DialHTTP("tcp", net.JoinHostPort(cfg.Hostname, cfg.Port)) if err != nil { diff --git a/ocis/pkg/runtime/service/service.go b/ocis/pkg/runtime/service/service.go index 42a93230529..dc5e76bfdbd 100644 --- a/ocis/pkg/runtime/service/service.go +++ b/ocis/pkg/runtime/service/service.go @@ -19,37 +19,37 @@ import ( "github.com/mohae/deepcopy" "github.com/olekukonko/tablewriter" - appProvider "github.com/owncloud/ocis/v2/extensions/app-provider/pkg/command" - appRegistry "github.com/owncloud/ocis/v2/extensions/app-registry/pkg/command" - authbasic "github.com/owncloud/ocis/v2/extensions/auth-basic/pkg/command" - authbearer "github.com/owncloud/ocis/v2/extensions/auth-bearer/pkg/command" - authmachine "github.com/owncloud/ocis/v2/extensions/auth-machine/pkg/command" - frontend "github.com/owncloud/ocis/v2/extensions/frontend/pkg/command" - gateway "github.com/owncloud/ocis/v2/extensions/gateway/pkg/command" - graphExplorer "github.com/owncloud/ocis/v2/extensions/graph-explorer/pkg/command" - graph "github.com/owncloud/ocis/v2/extensions/graph/pkg/command" - groups "github.com/owncloud/ocis/v2/extensions/groups/pkg/command" - idm "github.com/owncloud/ocis/v2/extensions/idm/pkg/command" - idp "github.com/owncloud/ocis/v2/extensions/idp/pkg/command" - nats "github.com/owncloud/ocis/v2/extensions/nats/pkg/command" - notifications "github.com/owncloud/ocis/v2/extensions/notifications/pkg/command" - ocdav "github.com/owncloud/ocis/v2/extensions/ocdav/pkg/command" - ocs "github.com/owncloud/ocis/v2/extensions/ocs/pkg/command" - proxy "github.com/owncloud/ocis/v2/extensions/proxy/pkg/command" - search "github.com/owncloud/ocis/v2/extensions/search/pkg/command" - settings "github.com/owncloud/ocis/v2/extensions/settings/pkg/command" - sharing "github.com/owncloud/ocis/v2/extensions/sharing/pkg/command" - storagepublic "github.com/owncloud/ocis/v2/extensions/storage-publiclink/pkg/command" - storageshares "github.com/owncloud/ocis/v2/extensions/storage-shares/pkg/command" - storageSystem "github.com/owncloud/ocis/v2/extensions/storage-system/pkg/command" - storageusers "github.com/owncloud/ocis/v2/extensions/storage-users/pkg/command" - store "github.com/owncloud/ocis/v2/extensions/store/pkg/command" - thumbnails "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/command" - users "github.com/owncloud/ocis/v2/extensions/users/pkg/command" - web "github.com/owncloud/ocis/v2/extensions/web/pkg/command" - webdav "github.com/owncloud/ocis/v2/extensions/webdav/pkg/command" ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/log" + appProvider "github.com/owncloud/ocis/v2/services/app-provider/pkg/command" + appRegistry "github.com/owncloud/ocis/v2/services/app-registry/pkg/command" + authbasic "github.com/owncloud/ocis/v2/services/auth-basic/pkg/command" + authbearer "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/command" + authmachine "github.com/owncloud/ocis/v2/services/auth-machine/pkg/command" + frontend "github.com/owncloud/ocis/v2/services/frontend/pkg/command" + gateway "github.com/owncloud/ocis/v2/services/gateway/pkg/command" + graphExplorer "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/command" + graph "github.com/owncloud/ocis/v2/services/graph/pkg/command" + groups "github.com/owncloud/ocis/v2/services/groups/pkg/command" + idm "github.com/owncloud/ocis/v2/services/idm/pkg/command" + idp "github.com/owncloud/ocis/v2/services/idp/pkg/command" + nats "github.com/owncloud/ocis/v2/services/nats/pkg/command" + notifications "github.com/owncloud/ocis/v2/services/notifications/pkg/command" + ocdav "github.com/owncloud/ocis/v2/services/ocdav/pkg/command" + ocs "github.com/owncloud/ocis/v2/services/ocs/pkg/command" + proxy "github.com/owncloud/ocis/v2/services/proxy/pkg/command" + search "github.com/owncloud/ocis/v2/services/search/pkg/command" + settings "github.com/owncloud/ocis/v2/services/settings/pkg/command" + sharing "github.com/owncloud/ocis/v2/services/sharing/pkg/command" + storagepublic "github.com/owncloud/ocis/v2/services/storage-publiclink/pkg/command" + storageshares "github.com/owncloud/ocis/v2/services/storage-shares/pkg/command" + storageSystem "github.com/owncloud/ocis/v2/services/storage-system/pkg/command" + storageusers "github.com/owncloud/ocis/v2/services/storage-users/pkg/command" + store "github.com/owncloud/ocis/v2/services/store/pkg/command" + thumbnails "github.com/owncloud/ocis/v2/services/thumbnails/pkg/command" + users "github.com/owncloud/ocis/v2/services/users/pkg/command" + web "github.com/owncloud/ocis/v2/services/web/pkg/command" + webdav "github.com/owncloud/ocis/v2/services/webdav/pkg/command" "github.com/rs/zerolog" "github.com/thejerf/suture/v4" "go-micro.dev/v4/logger" diff --git a/protogen/gen/ocis/services/search/v0/search.pb.go b/protogen/gen/ocis/services/search/v0/search.pb.go index 29fb75e166d..69aec7639d0 100644 --- a/protogen/gen/ocis/services/search/v0/search.pb.go +++ b/protogen/gen/ocis/services/search/v0/search.pb.go @@ -456,11 +456,11 @@ var file_ocis_services_search_v0_search_proto_rawDesc = []byte{ 0x78, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x22, 0x1b, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x30, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x3a, 0x01, - 0x2a, 0x42, 0xde, 0x02, 0x5a, 0x3c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2a, 0x42, 0xdc, 0x02, 0x5a, 0x3c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2f, - 0x76, 0x30, 0x92, 0x41, 0x9c, 0x02, 0x12, 0xb4, 0x01, 0x0a, 0x1e, 0x6f, 0x77, 0x6e, 0x43, 0x6c, + 0x76, 0x30, 0x92, 0x41, 0x9a, 0x02, 0x12, 0xb4, 0x01, 0x0a, 0x1e, 0x6f, 0x77, 0x6e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x49, 0x6e, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x65, 0x20, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x20, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x22, 0x47, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x47, 0x6d, 0x62, 0x48, 0x12, 0x20, 0x68, 0x74, 0x74, 0x70, @@ -474,11 +474,11 @@ var file_ocis_services_search_v0_search_proto_rawDesc = []byte{ 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x32, 0x05, 0x31, 0x2e, 0x30, 0x2e, 0x30, 0x2a, 0x02, 0x01, 0x02, 0x32, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x3b, 0x0a, 0x10, 0x44, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, - 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x12, 0x27, 0x68, 0x74, 0x74, 0x70, 0x73, + 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x39, 0x0a, 0x10, 0x44, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, + 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x12, 0x25, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x64, 0x65, 0x76, 0x2f, - 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, - 0x68, 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2f, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/protogen/gen/ocis/services/search/v0/search.swagger.json b/protogen/gen/ocis/services/search/v0/search.swagger.json index 55b025f1216..a25a91c758b 100644 --- a/protogen/gen/ocis/services/search/v0/search.swagger.json +++ b/protogen/gen/ocis/services/search/v0/search.swagger.json @@ -315,6 +315,6 @@ }, "externalDocs": { "description": "Developer Manual", - "url": "https://owncloud.dev/extensions/search/" + "url": "https://owncloud.dev/services/search/" } } diff --git a/protogen/gen/ocis/services/settings/v0/settings.pb.go b/protogen/gen/ocis/services/settings/v0/settings.pb.go index b14b56e87a7..d7bfa5ea77d 100644 --- a/protogen/gen/ocis/services/settings/v0/settings.pb.go +++ b/protogen/gen/ocis/services/settings/v0/settings.pb.go @@ -1549,12 +1549,12 @@ var file_ocis_services_settings_v0_settings_proto_rawDesc = []byte{ 0x79, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x31, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2b, 0x22, 0x26, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x30, 0x2f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x73, 0x2d, 0x67, 0x65, 0x74, 0x2d, 0x62, 0x79, 0x2d, 0x69, 0x64, 0x3a, 0x01, 0x2a, 0x42, 0xe5, + 0x73, 0x2d, 0x67, 0x65, 0x74, 0x2d, 0x62, 0x79, 0x2d, 0x69, 0x64, 0x3a, 0x01, 0x2a, 0x42, 0xe3, 0x02, 0x5a, 0x3f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x2f, - 0x76, 0x30, 0x92, 0x41, 0xa0, 0x02, 0x12, 0xb6, 0x01, 0x0a, 0x20, 0x6f, 0x77, 0x6e, 0x43, 0x6c, + 0x76, 0x30, 0x92, 0x41, 0x9e, 0x02, 0x12, 0xb6, 0x01, 0x0a, 0x20, 0x6f, 0x77, 0x6e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x49, 0x6e, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x65, 0x20, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x20, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x47, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x47, 0x6d, 0x62, 0x48, 0x12, 0x20, 0x68, 0x74, @@ -1568,11 +1568,11 @@ var file_ocis_services_settings_v0_settings_proto_rawDesc = []byte{ 0x2f, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x32, 0x05, 0x31, 0x2e, 0x30, 0x2e, 0x30, 0x2a, 0x02, 0x01, 0x02, 0x32, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x3d, 0x0a, 0x10, 0x44, 0x65, 0x76, 0x65, 0x6c, - 0x6f, 0x70, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x12, 0x29, 0x68, 0x74, 0x74, + 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x3b, 0x0a, 0x10, 0x44, 0x65, 0x76, 0x65, 0x6c, + 0x6f, 0x70, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x12, 0x27, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x64, 0x65, - 0x76, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x73, 0x65, 0x74, - 0x74, 0x69, 0x6e, 0x67, 0x73, 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x76, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x73, 0x65, 0x74, 0x74, 0x69, + 0x6e, 0x67, 0x73, 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/protogen/gen/ocis/services/settings/v0/settings.swagger.json b/protogen/gen/ocis/services/settings/v0/settings.swagger.json index 4d2db4d52cf..6298622ba28 100644 --- a/protogen/gen/ocis/services/settings/v0/settings.swagger.json +++ b/protogen/gen/ocis/services/settings/v0/settings.swagger.json @@ -1109,6 +1109,6 @@ }, "externalDocs": { "description": "Developer Manual", - "url": "https://owncloud.dev/extensions/settings/" + "url": "https://owncloud.dev/services/settings/" } } diff --git a/protogen/gen/ocis/services/store/v0/store.pb.go b/protogen/gen/ocis/services/store/v0/store.pb.go index 47e658138fa..279de4afb95 100644 --- a/protogen/gen/ocis/services/store/v0/store.pb.go +++ b/protogen/gen/ocis/services/store/v0/store.pb.go @@ -674,11 +674,11 @@ var file_ocis_services_store_v0_store_proto_rawDesc = []byte{ 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x30, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0xdb, 0x02, 0x5a, 0x3b, 0x67, 0x69, 0x74, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0xd9, 0x02, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, - 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x30, 0x92, 0x41, 0x9a, 0x02, 0x12, 0xb3, 0x01, 0x0a, + 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x30, 0x92, 0x41, 0x98, 0x02, 0x12, 0xb3, 0x01, 0x0a, 0x1d, 0x6f, 0x77, 0x6e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x49, 0x6e, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x65, 0x20, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x22, 0x47, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x47, 0x6d, 0x62, 0x48, 0x12, @@ -692,11 +692,11 @@ var file_ocis_services_store_v0_store_proto_rawDesc = []byte{ 0x74, 0x65, 0x72, 0x2f, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x32, 0x05, 0x31, 0x2e, 0x30, 0x2e, 0x30, 0x2a, 0x02, 0x01, 0x02, 0x32, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x3a, 0x0a, 0x10, 0x44, 0x65, - 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x12, 0x26, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x38, 0x0a, 0x10, 0x44, 0x65, + 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x12, 0x24, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, - 0x2e, 0x64, 0x65, 0x76, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, - 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x2e, 0x64, 0x65, 0x76, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x73, 0x74, + 0x6f, 0x72, 0x65, 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/protogen/gen/ocis/services/store/v0/store.swagger.json b/protogen/gen/ocis/services/store/v0/store.swagger.json index 2beaed54c48..21a5a67ab63 100644 --- a/protogen/gen/ocis/services/store/v0/store.swagger.json +++ b/protogen/gen/ocis/services/store/v0/store.swagger.json @@ -237,6 +237,6 @@ }, "externalDocs": { "description": "Developer Manual", - "url": "https://owncloud.dev/extensions/store/" + "url": "https://owncloud.dev/services/store/" } } diff --git a/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go b/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go index d892e3f0451..9d0293e6fb5 100644 --- a/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go +++ b/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go @@ -257,12 +257,12 @@ var file_ocis_services_thumbnails_v0_thumbnails_proto_rawDesc = []byte{ 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x76, 0x30, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, - 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0xeb, 0x02, + 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0xe9, 0x02, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, - 0x2f, 0x76, 0x30, 0x92, 0x41, 0xa4, 0x02, 0x12, 0xb8, 0x01, 0x0a, 0x22, 0x6f, 0x77, 0x6e, 0x43, + 0x2f, 0x76, 0x30, 0x92, 0x41, 0xa2, 0x02, 0x12, 0xb8, 0x01, 0x0a, 0x22, 0x6f, 0x77, 0x6e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x49, 0x6e, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x65, 0x20, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x20, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x47, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x47, 0x6d, 0x62, 0x48, 0x12, @@ -276,12 +276,12 @@ var file_ocis_services_thumbnails_v0_thumbnails_proto_rawDesc = []byte{ 0x74, 0x65, 0x72, 0x2f, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x32, 0x05, 0x31, 0x2e, 0x30, 0x2e, 0x30, 0x2a, 0x02, 0x01, 0x02, 0x32, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x3f, 0x0a, 0x10, 0x44, 0x65, - 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x12, 0x2b, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x3d, 0x0a, 0x10, 0x44, 0x65, + 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x12, 0x29, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, - 0x2e, 0x64, 0x65, 0x76, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, - 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x2e, 0x64, 0x65, 0x76, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x74, 0x68, + 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( diff --git a/protogen/gen/ocis/services/thumbnails/v0/thumbnails.swagger.json b/protogen/gen/ocis/services/thumbnails/v0/thumbnails.swagger.json index 7e4731e46d7..1ce43d1bdc2 100644 --- a/protogen/gen/ocis/services/thumbnails/v0/thumbnails.swagger.json +++ b/protogen/gen/ocis/services/thumbnails/v0/thumbnails.swagger.json @@ -124,6 +124,6 @@ }, "externalDocs": { "description": "Developer Manual", - "url": "https://owncloud.dev/extensions/thumbnails/" + "url": "https://owncloud.dev/services/thumbnails/" } } diff --git a/protogen/proto/ocis/services/search/v0/search.proto b/protogen/proto/ocis/services/search/v0/search.proto index 7d78dae35a4..5d071bf0174 100644 --- a/protogen/proto/ocis/services/search/v0/search.proto +++ b/protogen/proto/ocis/services/search/v0/search.proto @@ -30,7 +30,7 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { produces: "application/json"; external_docs: { description: "Developer Manual"; - url: "https://owncloud.dev/extensions/search/"; + url: "https://owncloud.dev/services/search/"; }; }; diff --git a/protogen/proto/ocis/services/settings/v0/settings.proto b/protogen/proto/ocis/services/settings/v0/settings.proto index 7aa342a1eb0..a9a6e4cc083 100644 --- a/protogen/proto/ocis/services/settings/v0/settings.proto +++ b/protogen/proto/ocis/services/settings/v0/settings.proto @@ -29,7 +29,7 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { produces: "application/json"; external_docs: { description: "Developer Manual"; - url: "https://owncloud.dev/extensions/settings/"; + url: "https://owncloud.dev/services/settings/"; }; }; diff --git a/protogen/proto/ocis/services/store/v0/store.proto b/protogen/proto/ocis/services/store/v0/store.proto index 01f9d1839c8..03a8e6393ad 100644 --- a/protogen/proto/ocis/services/store/v0/store.proto +++ b/protogen/proto/ocis/services/store/v0/store.proto @@ -27,7 +27,7 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { produces: "application/json"; external_docs: { description: "Developer Manual"; - url: "https://owncloud.dev/extensions/store/"; + url: "https://owncloud.dev/services/store/"; }; }; diff --git a/protogen/proto/ocis/services/thumbnails/v0/thumbnails.proto b/protogen/proto/ocis/services/thumbnails/v0/thumbnails.proto index 148175586d2..03c1dba60c0 100644 --- a/protogen/proto/ocis/services/thumbnails/v0/thumbnails.proto +++ b/protogen/proto/ocis/services/thumbnails/v0/thumbnails.proto @@ -27,7 +27,7 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { produces: "application/json"; external_docs: { description: "Developer Manual"; - url: "https://owncloud.dev/extensions/thumbnails/"; + url: "https://owncloud.dev/services/thumbnails/"; }; }; diff --git a/extensions/app-provider/Makefile b/services/app-provider/Makefile similarity index 100% rename from extensions/app-provider/Makefile rename to services/app-provider/Makefile diff --git a/services/app-provider/cmd/app-provider/main.go b/services/app-provider/cmd/app-provider/main.go new file mode 100644 index 00000000000..c4afd562c2d --- /dev/null +++ b/services/app-provider/cmd/app-provider/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/app-provider/pkg/command" + "github.com/owncloud/ocis/v2/services/app-provider/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/services/app-provider/pkg/command/health.go b/services/app-provider/pkg/command/health.go new file mode 100644 index 00000000000..9344b8a9acb --- /dev/null +++ b/services/app-provider/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/app-provider/pkg/config" + "github.com/owncloud/ocis/v2/services/app-provider/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/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/services/app-provider/pkg/command/root.go b/services/app-provider/pkg/command/root.go new file mode 100644 index 00000000000..3db69a21ada --- /dev/null +++ b/services/app-provider/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/app-provider/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-provider 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 app-provider command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new app-provider.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/services/app-provider/pkg/command/server.go b/services/app-provider/pkg/command/server.go new file mode 100644 index 00000000000..f6bdf87dc3e --- /dev/null +++ b/services/app-provider/pkg/command/server.go @@ -0,0 +1,108 @@ +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/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/sync" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/app-provider/pkg/config" + "github.com/owncloud/ocis/v2/services/app-provider/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/app-provider/pkg/logging" + "github.com/owncloud/ocis/v2/services/app-provider/pkg/revaconfig" + "github.com/owncloud/ocis/v2/services/app-provider/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/app-provider/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) + os.Exit(1) + } + 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.GetString(), + 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/services/app-provider/pkg/command/version.go b/services/app-provider/pkg/command/version.go new file mode 100644 index 00000000000..c191fbcfffd --- /dev/null +++ b/services/app-provider/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/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.GetString()) + 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/services/app-provider/pkg/config/config.go similarity index 100% rename from extensions/app-provider/pkg/config/config.go rename to services/app-provider/pkg/config/config.go diff --git a/services/app-provider/pkg/config/defaults/defaultconfig.go b/services/app-provider/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..810f1e54d0e --- /dev/null +++ b/services/app-provider/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,92 @@ +package defaults + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/services/app-provider/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:9165", + Token: "", + Pprof: false, + Zpages: false, + }, + GRPC: config.GRPCConfig{ + Addr: "127.0.0.1:9164", + Namespace: "com.owncloud.api", + Protocol: "tcp", + }, + Service: config.Service{ + Name: "app-provider", + }, + Reva: &config.Reva{ + Address: "127.0.0.1:9142", + }, + Driver: "", + Drivers: config.Drivers{ + WOPI: config.WOPIDriver{}, + }, + } +} + +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 +} + +func Validate(cfg *config.Config) error { + if cfg.TokenManager.JWTSecret == "" { + return shared.MissingJWTTokenError(cfg.Service.Name) + } + + return nil +} diff --git a/services/app-provider/pkg/config/parser/parse.go b/services/app-provider/pkg/config/parser/parse.go new file mode 100644 index 00000000000..290a970b3e3 --- /dev/null +++ b/services/app-provider/pkg/config/parser/parse.go @@ -0,0 +1,42 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/services/app-provider/pkg/config" + "github.com/owncloud/ocis/v2/services/app-provider/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + if cfg.TokenManager.JWTSecret == "" { + return shared.MissingJWTTokenError(cfg.Service.Name) + } + + return nil +} diff --git a/extensions/app-provider/pkg/config/reva.go b/services/app-provider/pkg/config/reva.go similarity index 100% rename from extensions/app-provider/pkg/config/reva.go rename to services/app-provider/pkg/config/reva.go diff --git a/services/app-provider/pkg/logging/logging.go b/services/app-provider/pkg/logging/logging.go new file mode 100644 index 00000000000..d288439ea87 --- /dev/null +++ b/services/app-provider/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/app-provider/pkg/config" +) + +// 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/services/app-provider/pkg/revaconfig/config.go b/services/app-provider/pkg/revaconfig/config.go new file mode 100644 index 00000000000..4ccc75952e0 --- /dev/null +++ b/services/app-provider/pkg/revaconfig/config.go @@ -0,0 +1,47 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/v2/services/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/services/app-provider/pkg/server/debug/option.go b/services/app-provider/pkg/server/debug/option.go new file mode 100644 index 00000000000..8fa6342dc94 --- /dev/null +++ b/services/app-provider/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/app-provider/pkg/config" +) + +// 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/services/app-provider/pkg/server/debug/server.go b/services/app-provider/pkg/server/debug/server.go new file mode 100644 index 00000000000..69ac3bd16f9 --- /dev/null +++ b/services/app-provider/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/app-provider/pkg/config" +) + +// 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.GetString()), + 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/services/app-provider/pkg/tracing/tracing.go b/services/app-provider/pkg/tracing/tracing.go new file mode 100644 index 00000000000..a04b6685abf --- /dev/null +++ b/services/app-provider/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/app-provider/pkg/config" + "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/services/app-registry/Makefile similarity index 100% rename from extensions/app-registry/Makefile rename to services/app-registry/Makefile diff --git a/services/app-registry/cmd/app-registry/main.go b/services/app-registry/cmd/app-registry/main.go new file mode 100644 index 00000000000..6a9d91f7d68 --- /dev/null +++ b/services/app-registry/cmd/app-registry/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/app-registry/pkg/command" + "github.com/owncloud/ocis/v2/services/app-registry/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/services/app-registry/pkg/command/health.go b/services/app-registry/pkg/command/health.go new file mode 100644 index 00000000000..d1227a0544c --- /dev/null +++ b/services/app-registry/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/app-registry/pkg/config" + "github.com/owncloud/ocis/v2/services/app-registry/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/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/services/app-registry/pkg/command/root.go b/services/app-registry/pkg/command/root.go new file mode 100644 index 00000000000..5f8362165ae --- /dev/null +++ b/services/app-registry/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/app-registry/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/services/app-registry/pkg/command/server.go b/services/app-registry/pkg/command/server.go new file mode 100644 index 00000000000..063a5986b80 --- /dev/null +++ b/services/app-registry/pkg/command/server.go @@ -0,0 +1,103 @@ +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/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/app-registry/pkg/config" + "github.com/owncloud/ocis/v2/services/app-registry/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/app-registry/pkg/logging" + "github.com/owncloud/ocis/v2/services/app-registry/pkg/revaconfig" + "github.com/owncloud/ocis/v2/services/app-registry/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/app-registry/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) + os.Exit(1) + } + 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.GetString(), + 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/services/app-registry/pkg/command/version.go b/services/app-registry/pkg/command/version.go new file mode 100644 index 00000000000..380a3086267 --- /dev/null +++ b/services/app-registry/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/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.GetString()) + 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/services/app-registry/pkg/config/config.go similarity index 100% rename from extensions/app-registry/pkg/config/config.go rename to services/app-registry/pkg/config/config.go diff --git a/services/app-registry/pkg/config/defaults/defaultconfig.go b/services/app-registry/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..8ac93b5267f --- /dev/null +++ b/services/app-registry/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,155 @@ +package defaults + +import ( + "github.com/owncloud/ocis/v2/services/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/services/app-registry/pkg/config/parser/parse.go b/services/app-registry/pkg/config/parser/parse.go new file mode 100644 index 00000000000..6b0d7e0fb59 --- /dev/null +++ b/services/app-registry/pkg/config/parser/parse.go @@ -0,0 +1,42 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/services/app-registry/pkg/config" + "github.com/owncloud/ocis/v2/services/app-registry/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + if cfg.TokenManager.JWTSecret == "" { + return shared.MissingJWTTokenError(cfg.Service.Name) + } + + return nil +} diff --git a/extensions/app-registry/pkg/config/reva.go b/services/app-registry/pkg/config/reva.go similarity index 100% rename from extensions/app-registry/pkg/config/reva.go rename to services/app-registry/pkg/config/reva.go diff --git a/services/app-registry/pkg/logging/logging.go b/services/app-registry/pkg/logging/logging.go new file mode 100644 index 00000000000..bf5737a49e5 --- /dev/null +++ b/services/app-registry/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/app-registry/pkg/config" +) + +// 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/services/app-registry/pkg/revaconfig/config.go b/services/app-registry/pkg/revaconfig/config.go new file mode 100644 index 00000000000..3f1b27caee4 --- /dev/null +++ b/services/app-registry/pkg/revaconfig/config.go @@ -0,0 +1,48 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + + "github.com/mitchellh/mapstructure" + "github.com/owncloud/ocis/v2/services/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/services/app-registry/pkg/server/debug/option.go b/services/app-registry/pkg/server/debug/option.go new file mode 100644 index 00000000000..ea63c5116eb --- /dev/null +++ b/services/app-registry/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/app-registry/pkg/config" +) + +// 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/services/app-registry/pkg/server/debug/server.go b/services/app-registry/pkg/server/debug/server.go new file mode 100644 index 00000000000..1b172de6e57 --- /dev/null +++ b/services/app-registry/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/app-registry/pkg/config" +) + +// 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.GetString()), + 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/services/app-registry/pkg/tracing/tracing.go b/services/app-registry/pkg/tracing/tracing.go new file mode 100644 index 00000000000..a230f3b8fba --- /dev/null +++ b/services/app-registry/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/app-registry/pkg/config" + "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/audit/Makefile b/services/audit/Makefile similarity index 100% rename from extensions/audit/Makefile rename to services/audit/Makefile diff --git a/services/audit/cmd/audit/main.go b/services/audit/cmd/audit/main.go new file mode 100644 index 00000000000..f365e874ec3 --- /dev/null +++ b/services/audit/cmd/audit/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/audit/pkg/command" + "github.com/owncloud/ocis/v2/services/audit/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/services/audit/pkg/command/health.go b/services/audit/pkg/command/health.go new file mode 100644 index 00000000000..faf43b8e08c --- /dev/null +++ b/services/audit/pkg/command/health.go @@ -0,0 +1,18 @@ +package command + +import ( + "github.com/owncloud/ocis/v2/services/audit/pkg/config" + "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", + Action: func(c *cli.Context) error { + // Not implemented + return nil + }, + } +} diff --git a/services/audit/pkg/command/root.go b/services/audit/pkg/command/root.go new file mode 100644 index 00000000000..ec1ef4d72b9 --- /dev/null +++ b/services/audit/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/audit/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 audit command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "audit", + Usage: "starts audit service", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the audit command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new audit.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.Audit.Commons = cfg.Commons + return SutureService{ + cfg: cfg.Audit, + } +} + +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/services/audit/pkg/command/server.go b/services/audit/pkg/command/server.go new file mode 100644 index 00000000000..fd904a0095d --- /dev/null +++ b/services/audit/pkg/command/server.go @@ -0,0 +1,60 @@ +package command + +import ( + "context" + "fmt" + "os" + + "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/events/server" + "github.com/go-micro/plugins/v4/events/natsjs" + "github.com/owncloud/ocis/v2/services/audit/pkg/config" + "github.com/owncloud/ocis/v2/services/audit/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/audit/pkg/logging" + svc "github.com/owncloud/ocis/v2/services/audit/pkg/service" + "github.com/owncloud/ocis/v2/services/audit/pkg/types" + "github.com/urfave/cli/v2" +) + +// Server is the entrypoint 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) + os.Exit(1) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + ctx := cfg.Context + if ctx == nil { + ctx = context.Background() + } + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + evtsCfg := cfg.Events + client, err := server.NewNatsStream( + natsjs.Address(evtsCfg.Endpoint), + natsjs.ClusterID(evtsCfg.Cluster), + ) + if err != nil { + return err + } + evts, err := events.Consume(client, evtsCfg.ConsumerGroup, types.RegisteredEvents()...) + if err != nil { + return err + } + + svc.AuditLoggerFromConfig(ctx, cfg.Auditlog, evts, logger) + return nil + }, + } +} diff --git a/services/audit/pkg/command/version.go b/services/audit/pkg/command/version.go new file mode 100644 index 00000000000..c3d0eb0b5a8 --- /dev/null +++ b/services/audit/pkg/command/version.go @@ -0,0 +1,19 @@ +package command + +import ( + "github.com/owncloud/ocis/v2/services/audit/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 { + // not implemented + return nil + }, + } +} diff --git a/extensions/audit/pkg/config/config.go b/services/audit/pkg/config/config.go similarity index 100% rename from extensions/audit/pkg/config/config.go rename to services/audit/pkg/config/config.go diff --git a/extensions/audit/pkg/config/debug.go b/services/audit/pkg/config/debug.go similarity index 100% rename from extensions/audit/pkg/config/debug.go rename to services/audit/pkg/config/debug.go diff --git a/services/audit/pkg/config/defaults/defaultconfig.go b/services/audit/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..f9c06f903b2 --- /dev/null +++ b/services/audit/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,50 @@ +package defaults + +import ( + "github.com/owncloud/ocis/v2/services/audit/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:9234", + }, + Service: config.Service{ + Name: "audit", + }, + Events: config.Events{ + Endpoint: "127.0.0.1:9233", + Cluster: "ocis-cluster", + ConsumerGroup: "audit", + }, + Auditlog: config.Auditlog{ + LogToConsole: true, + Format: "json", + }, + } +} + +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{} + } +} + +func Sanitize(cfg *config.Config) { + // sanitize config +} diff --git a/extensions/audit/pkg/config/log.go b/services/audit/pkg/config/log.go similarity index 100% rename from extensions/audit/pkg/config/log.go rename to services/audit/pkg/config/log.go diff --git a/services/audit/pkg/config/parser/parse.go b/services/audit/pkg/config/parser/parse.go new file mode 100644 index 00000000000..43adabd426f --- /dev/null +++ b/services/audit/pkg/config/parser/parse.go @@ -0,0 +1,37 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/audit/pkg/config" + "github.com/owncloud/ocis/v2/services/audit/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + return nil +} diff --git a/extensions/audit/pkg/config/service.go b/services/audit/pkg/config/service.go similarity index 100% rename from extensions/audit/pkg/config/service.go rename to services/audit/pkg/config/service.go diff --git a/services/audit/pkg/logging/logging.go b/services/audit/pkg/logging/logging.go new file mode 100644 index 00000000000..4acd0282b93 --- /dev/null +++ b/services/audit/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/audit/pkg/config" +) + +// 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/services/audit/pkg/service/service.go b/services/audit/pkg/service/service.go new file mode 100644 index 00000000000..961c4196348 --- /dev/null +++ b/services/audit/pkg/service/service.go @@ -0,0 +1,170 @@ +package svc + +import ( + "context" + "encoding/json" + "fmt" + "os" + + "github.com/cs3org/reva/v2/pkg/events" + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/audit/pkg/config" + "github.com/owncloud/ocis/v2/services/audit/pkg/types" +) + +// Log is used to log to different outputs +type Log func([]byte) + +// Marshaller is used to marshal events +type Marshaller func(interface{}) ([]byte, error) + +// AuditLoggerFromConfig will start a new AuditLogger generated from the config +func AuditLoggerFromConfig(ctx context.Context, cfg config.Auditlog, ch <-chan interface{}, log log.Logger) { + var logs []Log + + if cfg.LogToConsole { + logs = append(logs, WriteToStdout()) + } + + if cfg.LogToFile { + logs = append(logs, WriteToFile(cfg.FilePath, log)) + } + + StartAuditLogger(ctx, ch, log, Marshal(cfg.Format, log), logs...) + +} + +// StartAuditLogger will block. run in seperate go routine +func StartAuditLogger(ctx context.Context, ch <-chan interface{}, log log.Logger, marshaller Marshaller, logto ...Log) { + for { + select { + case <-ctx.Done(): + return + case i := <-ch: + var auditEvent interface{} + switch ev := i.(type) { + case events.ShareCreated: + auditEvent = types.ShareCreated(ev) + case events.LinkCreated: + auditEvent = types.LinkCreated(ev) + case events.ShareUpdated: + auditEvent = types.ShareUpdated(ev) + case events.LinkUpdated: + auditEvent = types.LinkUpdated(ev) + case events.ShareRemoved: + auditEvent = types.ShareRemoved(ev) + case events.LinkRemoved: + auditEvent = types.LinkRemoved(ev) + case events.ReceivedShareUpdated: + auditEvent = types.ReceivedShareUpdated(ev) + case events.LinkAccessed: + auditEvent = types.LinkAccessed(ev) + case events.LinkAccessFailed: + auditEvent = types.LinkAccessFailed(ev) + case events.ContainerCreated: + auditEvent = types.ContainerCreated(ev) + case events.FileUploaded: + auditEvent = types.FileUploaded(ev) + case events.FileDownloaded: + auditEvent = types.FileDownloaded(ev) + case events.ItemMoved: + auditEvent = types.ItemMoved(ev) + case events.ItemTrashed: + auditEvent = types.ItemTrashed(ev) + case events.ItemPurged: + auditEvent = types.ItemPurged(ev) + case events.ItemRestored: + auditEvent = types.ItemRestored(ev) + case events.FileVersionRestored: + auditEvent = types.FileVersionRestored(ev) + case events.SpaceCreated: + auditEvent = types.SpaceCreated(ev) + case events.SpaceRenamed: + auditEvent = types.SpaceRenamed(ev) + case events.SpaceDisabled: + auditEvent = types.SpaceDisabled(ev) + case events.SpaceEnabled: + auditEvent = types.SpaceEnabled(ev) + case events.SpaceDeleted: + auditEvent = types.SpaceDeleted(ev) + case events.UserCreated: + auditEvent = types.UserCreated(ev) + case events.UserDeleted: + auditEvent = types.UserDeleted(ev) + case events.UserFeatureChanged: + auditEvent = types.UserFeatureChanged(ev) + case events.GroupCreated: + auditEvent = types.GroupCreated(ev) + case events.GroupDeleted: + auditEvent = types.GroupDeleted(ev) + case events.GroupMemberAdded: + auditEvent = types.GroupMemberAdded(ev) + case events.GroupMemberRemoved: + auditEvent = types.GroupMemberRemoved(ev) + default: + log.Error().Interface("event", ev).Msg(fmt.Sprintf("can't handle event of type '%T'", ev)) + continue + + } + + b, err := marshaller(auditEvent) + if err != nil { + log.Error().Err(err).Msg("error marshaling the event") + continue + } + + for _, l := range logto { + l(b) + } + } + } + +} + +// WriteToFile returns a Log function writing to a file +func WriteToFile(path string, log log.Logger) Log { + return func(content []byte) { + file, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + log.Error().Err(err).Msgf("error opening file '%s'", path) + return + } + defer file.Close() + if _, err := fmt.Fprintln(file, string(content)); err != nil { + log.Error().Err(err).Msgf("error writing to file '%s'", path) + } + } +} + +// WriteToStdout return a Log function writing to Stdout +func WriteToStdout() Log { + return func(content []byte) { + fmt.Println(string(content)) + } +} + +// Marshal returns a Marshaller from the `format` string +func Marshal(format string, log log.Logger) Marshaller { + switch format { + default: + log.Error().Msgf("unknown format '%s'", format) + return nil + case "json": + return json.Marshal + case "minimal": + return func(ev interface{}) ([]byte, error) { + b, err := json.Marshal(ev) + if err != nil { + return nil, err + } + + m := make(map[string]interface{}) + if err := json.Unmarshal(b, &m); err != nil { + return nil, err + } + + format := fmt.Sprintf("%s)\n %s", m["Action"], m["Message"]) + return []byte(format), nil + } + } +} diff --git a/extensions/audit/pkg/service/service_test.go b/services/audit/pkg/service/service_test.go similarity index 99% rename from extensions/audit/pkg/service/service_test.go rename to services/audit/pkg/service/service_test.go index ae64f43a2c3..072c4dc7c09 100644 --- a/extensions/audit/pkg/service/service_test.go +++ b/services/audit/pkg/service/service_test.go @@ -6,8 +6,8 @@ import ( "testing" "github.com/cs3org/reva/v2/pkg/events" - "github.com/owncloud/ocis/v2/extensions/audit/pkg/types" "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/audit/pkg/types" "github.com/test-go/testify/require" group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" diff --git a/extensions/audit/pkg/types/constants.go b/services/audit/pkg/types/constants.go similarity index 100% rename from extensions/audit/pkg/types/constants.go rename to services/audit/pkg/types/constants.go diff --git a/extensions/audit/pkg/types/conversion.go b/services/audit/pkg/types/conversion.go similarity index 100% rename from extensions/audit/pkg/types/conversion.go rename to services/audit/pkg/types/conversion.go diff --git a/extensions/audit/pkg/types/events.go b/services/audit/pkg/types/events.go similarity index 100% rename from extensions/audit/pkg/types/events.go rename to services/audit/pkg/types/events.go diff --git a/extensions/audit/pkg/types/types.go b/services/audit/pkg/types/types.go similarity index 100% rename from extensions/audit/pkg/types/types.go rename to services/audit/pkg/types/types.go diff --git a/extensions/auth-basic/Makefile b/services/auth-basic/Makefile similarity index 100% rename from extensions/auth-basic/Makefile rename to services/auth-basic/Makefile diff --git a/services/auth-basic/cmd/auth-basic/main.go b/services/auth-basic/cmd/auth-basic/main.go new file mode 100644 index 00000000000..ec5af41edf3 --- /dev/null +++ b/services/auth-basic/cmd/auth-basic/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/auth-basic/pkg/command" + "github.com/owncloud/ocis/v2/services/auth-basic/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/services/auth-basic/pkg/command/health.go b/services/auth-basic/pkg/command/health.go new file mode 100644 index 00000000000..48f07427d8c --- /dev/null +++ b/services/auth-basic/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/auth-basic/pkg/config" + "github.com/owncloud/ocis/v2/services/auth-basic/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/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/services/auth-basic/pkg/command/root.go b/services/auth-basic/pkg/command/root.go new file mode 100644 index 00000000000..d62b0d9e378 --- /dev/null +++ b/services/auth-basic/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/auth-basic/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/services/auth-basic/pkg/command/server.go b/services/auth-basic/pkg/command/server.go new file mode 100644 index 00000000000..cb5bbf6b6c3 --- /dev/null +++ b/services/auth-basic/pkg/command/server.go @@ -0,0 +1,121 @@ +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/v2/ocis-pkg/ldap" + "github.com/owncloud/ocis/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/sync" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/auth-basic/pkg/config" + "github.com/owncloud/ocis/v2/services/auth-basic/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/auth-basic/pkg/logging" + "github.com/owncloud/ocis/v2/services/auth-basic/pkg/revaconfig" + "github.com/owncloud/ocis/v2/services/auth-basic/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/auth-basic/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) + os.Exit(1) + } + 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.GetString(), + 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/services/auth-basic/pkg/command/version.go b/services/auth-basic/pkg/command/version.go new file mode 100644 index 00000000000..9e2291bf412 --- /dev/null +++ b/services/auth-basic/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/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.GetString()) + 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/services/auth-basic/pkg/config/config.go similarity index 100% rename from extensions/auth-basic/pkg/config/config.go rename to services/auth-basic/pkg/config/config.go diff --git a/services/auth-basic/pkg/config/defaults/defaultconfig.go b/services/auth-basic/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..dd558ef0795 --- /dev/null +++ b/services/auth-basic/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,126 @@ +package defaults + +import ( + "path/filepath" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" + "github.com/owncloud/ocis/v2/services/auth-basic/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:9147", + Token: "", + Pprof: false, + Zpages: false, + }, + GRPC: config.GRPCConfig{ + Addr: "127.0.0.1:9146", + Namespace: "com.owncloud.api", + Protocol: "tcp", + }, + Service: config.Service{ + Name: "auth-basic", + }, + Reva: &config.Reva{ + Address: "127.0.0.1:9142", + }, + AuthProvider: "ldap", + AuthProviders: config.AuthProviders{ + LDAP: config.LDAPProvider{ + URI: "ldaps://localhost:9235", + CACert: filepath.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: "https://localhost:9200", + UserSchema: config.LDAPUserSchema{ + ID: "ownclouduuid", + Mail: "mail", + DisplayName: "displayname", + Username: "uid", + }, + GroupSchema: config.LDAPGroupSchema{ + ID: "ownclouduuid", + Mail: "mail", + DisplayName: "cn", + Groupname: "cn", + Member: "member", + }, + }, + JSON: config.JSONProvider{}, + OwnCloudSQL: config.OwnCloudSQLProvider{ + DBUsername: "owncloud", + DBHost: "mysql", + DBPort: 3306, + DBName: "owncloud", + IDP: "https://localhost:9200", + Nobody: 90, + JoinUsername: false, + JoinOwnCloudUUID: false, + }, + }, + } +} + +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/services/auth-basic/pkg/config/parser/parse.go b/services/auth-basic/pkg/config/parser/parse.go new file mode 100644 index 00000000000..bf000769c1f --- /dev/null +++ b/services/auth-basic/pkg/config/parser/parse.go @@ -0,0 +1,46 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/services/auth-basic/pkg/config" + "github.com/owncloud/ocis/v2/services/auth-basic/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + if cfg.TokenManager.JWTSecret == "" { + return shared.MissingJWTTokenError(cfg.Service.Name) + } + + if cfg.AuthProviders.LDAP.BindPassword == "" && cfg.AuthProvider == "ldap" { + return shared.MissingLDAPBindPassword(cfg.Service.Name) + } + + return nil +} diff --git a/extensions/auth-basic/pkg/config/reva.go b/services/auth-basic/pkg/config/reva.go similarity index 100% rename from extensions/auth-basic/pkg/config/reva.go rename to services/auth-basic/pkg/config/reva.go diff --git a/services/auth-basic/pkg/logging/logging.go b/services/auth-basic/pkg/logging/logging.go new file mode 100644 index 00000000000..004f3523fd9 --- /dev/null +++ b/services/auth-basic/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/auth-basic/pkg/config" +) + +// 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/services/auth-basic/pkg/revaconfig/config.go b/services/auth-basic/pkg/revaconfig/config.go new file mode 100644 index 00000000000..32833a1aa4f --- /dev/null +++ b/services/auth-basic/pkg/revaconfig/config.go @@ -0,0 +1,83 @@ +package revaconfig + +import "github.com/owncloud/ocis/v2/services/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/services/auth-basic/pkg/server/debug/option.go b/services/auth-basic/pkg/server/debug/option.go new file mode 100644 index 00000000000..e9a4f982964 --- /dev/null +++ b/services/auth-basic/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/auth-basic/pkg/config" +) + +// 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/services/auth-basic/pkg/server/debug/server.go b/services/auth-basic/pkg/server/debug/server.go new file mode 100644 index 00000000000..62dafeab8bd --- /dev/null +++ b/services/auth-basic/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/auth-basic/pkg/config" +) + +// 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.GetString()), + 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/services/auth-basic/pkg/tracing/tracing.go b/services/auth-basic/pkg/tracing/tracing.go new file mode 100644 index 00000000000..d264fe2f7d7 --- /dev/null +++ b/services/auth-basic/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/auth-basic/pkg/config" + "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/services/auth-bearer/Makefile similarity index 100% rename from extensions/auth-bearer/Makefile rename to services/auth-bearer/Makefile diff --git a/services/auth-bearer/cmd/auth-bearer/main.go b/services/auth-bearer/cmd/auth-bearer/main.go new file mode 100644 index 00000000000..8617701cfde --- /dev/null +++ b/services/auth-bearer/cmd/auth-bearer/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/command" + "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/services/auth-bearer/pkg/command/health.go b/services/auth-bearer/pkg/command/health.go new file mode 100644 index 00000000000..040e695b26a --- /dev/null +++ b/services/auth-bearer/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/config" + "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/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/services/auth-bearer/pkg/command/root.go b/services/auth-bearer/pkg/command/root.go new file mode 100644 index 00000000000..694500d4d63 --- /dev/null +++ b/services/auth-bearer/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/auth-bearer/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/services/auth-bearer/pkg/command/server.go b/services/auth-bearer/pkg/command/server.go new file mode 100644 index 00000000000..7a871f12c06 --- /dev/null +++ b/services/auth-bearer/pkg/command/server.go @@ -0,0 +1,108 @@ +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/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/sync" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/config" + "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/logging" + "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/revaconfig" + "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/auth-bearer/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) + os.Exit(1) + } + 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.GetString(), + 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/services/auth-bearer/pkg/command/version.go b/services/auth-bearer/pkg/command/version.go new file mode 100644 index 00000000000..030171e1692 --- /dev/null +++ b/services/auth-bearer/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/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.GetString()) + 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/services/auth-bearer/pkg/config/config.go similarity index 100% rename from extensions/auth-bearer/pkg/config/config.go rename to services/auth-bearer/pkg/config/config.go diff --git a/services/auth-bearer/pkg/config/defaults/defaultconfig.go b/services/auth-bearer/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..73e1cc0721b --- /dev/null +++ b/services/auth-bearer/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,84 @@ +package defaults + +import ( + "github.com/owncloud/ocis/v2/services/auth-bearer/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:9149", + Token: "", + Pprof: false, + Zpages: false, + }, + GRPC: config.GRPCConfig{ + Addr: "127.0.0.1:9148", + Namespace: "com.owncloud.api", + Protocol: "tcp", + }, + Service: config.Service{ + Name: "auth-bearer", + }, + Reva: &config.Reva{ + Address: "127.0.0.1:9142", + }, + 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.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/services/auth-bearer/pkg/config/parser/parse.go b/services/auth-bearer/pkg/config/parser/parse.go new file mode 100644 index 00000000000..01890e2e998 --- /dev/null +++ b/services/auth-bearer/pkg/config/parser/parse.go @@ -0,0 +1,42 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/config" + "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + if cfg.TokenManager.JWTSecret == "" { + return shared.MissingJWTTokenError(cfg.Service.Name) + } + + return nil +} diff --git a/extensions/auth-bearer/pkg/config/reva.go b/services/auth-bearer/pkg/config/reva.go similarity index 100% rename from extensions/auth-bearer/pkg/config/reva.go rename to services/auth-bearer/pkg/config/reva.go diff --git a/services/auth-bearer/pkg/logging/logging.go b/services/auth-bearer/pkg/logging/logging.go new file mode 100644 index 00000000000..8b55220f5b6 --- /dev/null +++ b/services/auth-bearer/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/config" +) + +// 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/services/auth-bearer/pkg/revaconfig/config.go b/services/auth-bearer/pkg/revaconfig/config.go new file mode 100644 index 00000000000..cf51a02dcc8 --- /dev/null +++ b/services/auth-bearer/pkg/revaconfig/config.go @@ -0,0 +1,38 @@ +package revaconfig + +import "github.com/owncloud/ocis/v2/services/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/services/auth-bearer/pkg/server/debug/option.go b/services/auth-bearer/pkg/server/debug/option.go new file mode 100644 index 00000000000..2fde7978b68 --- /dev/null +++ b/services/auth-bearer/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/config" +) + +// 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/services/auth-bearer/pkg/server/debug/server.go b/services/auth-bearer/pkg/server/debug/server.go new file mode 100644 index 00000000000..a44400a0414 --- /dev/null +++ b/services/auth-bearer/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/config" +) + +// 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.GetString()), + 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/services/auth-bearer/pkg/tracing/tracing.go b/services/auth-bearer/pkg/tracing/tracing.go new file mode 100644 index 00000000000..125a743d216 --- /dev/null +++ b/services/auth-bearer/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/config" + "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/services/auth-machine/Makefile similarity index 100% rename from extensions/auth-machine/Makefile rename to services/auth-machine/Makefile diff --git a/services/auth-machine/cmd/auth-machine/main.go b/services/auth-machine/cmd/auth-machine/main.go new file mode 100644 index 00000000000..937b0b20cdf --- /dev/null +++ b/services/auth-machine/cmd/auth-machine/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/auth-machine/pkg/command" + "github.com/owncloud/ocis/v2/services/auth-machine/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/services/auth-machine/pkg/command/health.go b/services/auth-machine/pkg/command/health.go new file mode 100644 index 00000000000..cc26ea7f29e --- /dev/null +++ b/services/auth-machine/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/auth-machine/pkg/config" + "github.com/owncloud/ocis/v2/services/auth-machine/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/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/services/auth-machine/pkg/command/root.go b/services/auth-machine/pkg/command/root.go new file mode 100644 index 00000000000..29d9cdc8e34 --- /dev/null +++ b/services/auth-machine/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/auth-machine/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/services/auth-machine/pkg/command/server.go b/services/auth-machine/pkg/command/server.go new file mode 100644 index 00000000000..1bb1610f47a --- /dev/null +++ b/services/auth-machine/pkg/command/server.go @@ -0,0 +1,108 @@ +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/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/sync" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/auth-machine/pkg/config" + "github.com/owncloud/ocis/v2/services/auth-machine/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/auth-machine/pkg/logging" + "github.com/owncloud/ocis/v2/services/auth-machine/pkg/revaconfig" + "github.com/owncloud/ocis/v2/services/auth-machine/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/auth-machine/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) + os.Exit(1) + } + 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.GetString(), + 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/services/auth-machine/pkg/command/version.go b/services/auth-machine/pkg/command/version.go new file mode 100644 index 00000000000..1db2354e441 --- /dev/null +++ b/services/auth-machine/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/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.GetString()) + 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/services/auth-machine/pkg/config/config.go similarity index 100% rename from extensions/auth-machine/pkg/config/config.go rename to services/auth-machine/pkg/config/config.go diff --git a/services/auth-machine/pkg/config/defaults/defaultconfig.go b/services/auth-machine/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..fc1dee5fc5a --- /dev/null +++ b/services/auth-machine/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,83 @@ +package defaults + +import ( + "github.com/owncloud/ocis/v2/services/auth-machine/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:9167", + Token: "", + Pprof: false, + Zpages: false, + }, + GRPC: config.GRPCConfig{ + Addr: "127.0.0.1:9166", + Namespace: "com.owncloud.api", + Protocol: "tcp", + }, + Service: config.Service{ + Name: "auth-machine", + }, + Reva: &config.Reva{ + Address: "127.0.0.1:9142", + }, + } +} + +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{} + } + + if cfg.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" { + cfg.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey + } +} + +func Sanitize(cfg *config.Config) { + // nothing to sanitize here atm +} diff --git a/services/auth-machine/pkg/config/parser/parse.go b/services/auth-machine/pkg/config/parser/parse.go new file mode 100644 index 00000000000..1b9c1606a92 --- /dev/null +++ b/services/auth-machine/pkg/config/parser/parse.go @@ -0,0 +1,45 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/services/auth-machine/pkg/config" + "github.com/owncloud/ocis/v2/services/auth-machine/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + if cfg.TokenManager.JWTSecret == "" { + return shared.MissingJWTTokenError(cfg.Service.Name) + } + + if cfg.MachineAuthAPIKey == "" { + return shared.MissingMachineAuthApiKeyError(cfg.Service.Name) + } + return nil +} diff --git a/extensions/auth-machine/pkg/config/reva.go b/services/auth-machine/pkg/config/reva.go similarity index 100% rename from extensions/auth-machine/pkg/config/reva.go rename to services/auth-machine/pkg/config/reva.go diff --git a/services/auth-machine/pkg/logging/logging.go b/services/auth-machine/pkg/logging/logging.go new file mode 100644 index 00000000000..f46568ba129 --- /dev/null +++ b/services/auth-machine/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/auth-machine/pkg/config" +) + +// 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/services/auth-machine/pkg/revaconfig/config.go b/services/auth-machine/pkg/revaconfig/config.go new file mode 100644 index 00000000000..637488da00e --- /dev/null +++ b/services/auth-machine/pkg/revaconfig/config.go @@ -0,0 +1,37 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/v2/services/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/services/auth-machine/pkg/server/debug/option.go b/services/auth-machine/pkg/server/debug/option.go new file mode 100644 index 00000000000..2208627e311 --- /dev/null +++ b/services/auth-machine/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/auth-machine/pkg/config" +) + +// 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/services/auth-machine/pkg/server/debug/server.go b/services/auth-machine/pkg/server/debug/server.go new file mode 100644 index 00000000000..d2f3b1fc415 --- /dev/null +++ b/services/auth-machine/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/auth-machine/pkg/config" +) + +// 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.GetString()), + 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/services/auth-machine/pkg/tracing/tracing.go b/services/auth-machine/pkg/tracing/tracing.go new file mode 100644 index 00000000000..cd6cbc75a25 --- /dev/null +++ b/services/auth-machine/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/auth-machine/pkg/config" + "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/services/frontend/Makefile similarity index 100% rename from extensions/frontend/Makefile rename to services/frontend/Makefile diff --git a/services/frontend/cmd/frontend/main.go b/services/frontend/cmd/frontend/main.go new file mode 100644 index 00000000000..cbdc5dac510 --- /dev/null +++ b/services/frontend/cmd/frontend/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/frontend/pkg/command" + "github.com/owncloud/ocis/v2/services/frontend/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/services/frontend/pkg/command/health.go b/services/frontend/pkg/command/health.go new file mode 100644 index 00000000000..15858682615 --- /dev/null +++ b/services/frontend/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/frontend/pkg/config" + "github.com/owncloud/ocis/v2/services/frontend/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/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/services/frontend/pkg/command/root.go b/services/frontend/pkg/command/root.go new file mode 100644 index 00000000000..313a134969b --- /dev/null +++ b/services/frontend/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/frontend/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/services/frontend/pkg/command/server.go b/services/frontend/pkg/command/server.go new file mode 100644 index 00000000000..f52e97ba8a3 --- /dev/null +++ b/services/frontend/pkg/command/server.go @@ -0,0 +1,108 @@ +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/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/sync" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/frontend/pkg/config" + "github.com/owncloud/ocis/v2/services/frontend/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/frontend/pkg/logging" + "github.com/owncloud/ocis/v2/services/frontend/pkg/revaconfig" + "github.com/owncloud/ocis/v2/services/frontend/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/frontend/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) + os.Exit(1) + } + 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.GetString(), + 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/services/frontend/pkg/command/version.go b/services/frontend/pkg/command/version.go new file mode 100644 index 00000000000..77ec32121a8 --- /dev/null +++ b/services/frontend/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/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.GetString()) + 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/services/frontend/pkg/config/config.go similarity index 100% rename from extensions/frontend/pkg/config/config.go rename to services/frontend/pkg/config/config.go diff --git a/services/frontend/pkg/config/defaults/defaultconfig.go b/services/frontend/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..b520d75c71f --- /dev/null +++ b/services/frontend/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,125 @@ +package defaults + +import ( + "github.com/owncloud/ocis/v2/services/frontend/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:9141", + Token: "", + Pprof: false, + Zpages: false, + }, + HTTP: config.HTTPConfig{ + Addr: "127.0.0.1:9140", + Namespace: "com.owncloud.web", + Protocol: "tcp", + Prefix: "", + }, + Service: config.Service{ + Name: "frontend", + }, + Reva: &config.Reva{ + Address: "127.0.0.1:9142", + }, + PublicURL: "https://localhost:9200", + EnableFavorites: false, + EnableProjectSpaces: true, + EnableShareJail: true, + UploadMaxChunkSize: 1e+8, + UploadHTTPMethodOverride: "", + DefaultUploadProtocol: "tus", + EnableResharing: false, + Checksums: config.Checksums{ + SupportedTypes: []string{"sha1", "md5", "adler32"}, + PreferredUploadType: "", + }, + AppHandler: config.AppHandler{ + Prefix: "app", + }, + Archiver: config.Archiver{ + Insecure: false, + Prefix: "archiver", + MaxNumFiles: 10000, + MaxSize: 1073741824, + }, + DataGateway: config.DataGateway{ + Prefix: "data", + }, + OCS: config.OCS{ + Prefix: "ocs", + SharePrefix: "/Shares", + HomeNamespace: "/users/{{.Id.OpaqueId}}", + AdditionalInfoAttribute: "{{.Mail}}", + ResourceInfoCacheTTL: 0, + }, + Middleware: config.Middleware{ + Auth: config.Auth{ + CredentialsByUserAgent: map[string]string{}, + }, + }, + } +} + +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{} + } + + if cfg.TransferSecret == "" && cfg.Commons != nil && cfg.Commons.TransferSecret != "" { + cfg.TransferSecret = cfg.Commons.TransferSecret + } + + if cfg.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" { + cfg.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey + } + +} + +func Sanitize(cfg *config.Config) { + // nothing to sanitize here atm +} diff --git a/services/frontend/pkg/config/parser/parse.go b/services/frontend/pkg/config/parser/parse.go new file mode 100644 index 00000000000..026ae126c99 --- /dev/null +++ b/services/frontend/pkg/config/parser/parse.go @@ -0,0 +1,50 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/services/frontend/pkg/config" + "github.com/owncloud/ocis/v2/services/frontend/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +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) + } + + if cfg.MachineAuthAPIKey == "" { + return shared.MissingMachineAuthApiKeyError(cfg.Service.Name) + } + + return nil +} diff --git a/extensions/frontend/pkg/config/reva.go b/services/frontend/pkg/config/reva.go similarity index 100% rename from extensions/frontend/pkg/config/reva.go rename to services/frontend/pkg/config/reva.go diff --git a/services/frontend/pkg/logging/logging.go b/services/frontend/pkg/logging/logging.go new file mode 100644 index 00000000000..a1b6d6d4408 --- /dev/null +++ b/services/frontend/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/frontend/pkg/config" +) + +// 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/services/frontend/pkg/revaconfig/config.go b/services/frontend/pkg/revaconfig/config.go new file mode 100644 index 00000000000..31fcf951280 --- /dev/null +++ b/services/frontend/pkg/revaconfig/config.go @@ -0,0 +1,230 @@ +package revaconfig + +import ( + "path" + "strconv" + + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/frontend/pkg/config" +) + +// 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), + }, + } + + 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": cfg.Service.Name, + }, + "shared": map[string]interface{}{ + "jwt_secret": cfg.TokenManager.JWTSecret, + "gatewaysvc": cfg.Reva.Address, // Todo or address? + "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, + }, + "http": map[string]interface{}{ + "network": cfg.HTTP.Protocol, + "address": cfg.HTTP.Addr, + "middlewares": map[string]interface{}{ + "cors": map[string]interface{}{ + "allow_credentials": true, + }, + "auth": map[string]interface{}{ + "credentials_by_user_agent": cfg.Middleware.Auth.CredentialsByUserAgent, + "credential_chain": []string{"bearer"}, + }, + }, + // TODO build services dynamically + "services": map[string]interface{}{ + // this reva service called "appprovider" comes from + // `internal/http/services/appprovider` and is a translation + // layer from the grpc app registry to http, used by eg. ownCloud Web + // It should not be confused with `internal/grpc/services/appprovider` + // which is currently only has only the driver for the CS3org WOPI server + "appprovider": map[string]interface{}{ + "prefix": cfg.AppHandler.Prefix, + "transfer_shared_secret": cfg.TransferSecret, + "timeout": 86400, + "insecure": cfg.AppHandler.Insecure, + }, + "archiver": map[string]interface{}{ + "prefix": cfg.Archiver.Prefix, + "timeout": 86400, + "insecure": cfg.Archiver.Insecure, + "max_num_files": cfg.Archiver.MaxNumFiles, + "max_size": cfg.Archiver.MaxSize, + }, + "datagateway": map[string]interface{}{ + "prefix": cfg.DataGateway.Prefix, + "transfer_shared_secret": cfg.TransferSecret, + "timeout": 86400, + "insecure": true, + }, + "ocs": map[string]interface{}{ + "storage_registry_svc": cfg.Reva.Address, + "share_prefix": cfg.OCS.SharePrefix, + "home_namespace": cfg.OCS.HomeNamespace, + "resource_info_cache_ttl": cfg.OCS.ResourceInfoCacheTTL, + "prefix": cfg.OCS.Prefix, + "additional_info_attribute": cfg.OCS.AdditionalInfoAttribute, + "machine_auth_apikey": cfg.MachineAuthAPIKey, + "cache_warmup_driver": cfg.OCS.CacheWarmupDriver, + "cache_warmup_drivers": map[string]interface{}{ + "cbox": map[string]interface{}{ + "db_username": cfg.OCS.CacheWarmupDrivers.CBOX.DBUsername, + "db_password": cfg.OCS.CacheWarmupDrivers.CBOX.DBPassword, + "db_host": cfg.OCS.CacheWarmupDrivers.CBOX.DBHost, + "db_port": cfg.OCS.CacheWarmupDrivers.CBOX.DBPort, + "db_name": cfg.OCS.CacheWarmupDrivers.CBOX.DBName, + "namespace": cfg.OCS.CacheWarmupDrivers.CBOX.Namespace, + "gatewaysvc": cfg.Reva.Address, + }, + }, + "config": map[string]interface{}{ + "version": "1.7", + "website": "ownCloud", + "host": cfg.PublicURL, + "contact": "", + "ssl": "false", + }, + "default_upload_protocol": cfg.DefaultUploadProtocol, + "capabilities": map[string]interface{}{ + "capabilities": map[string]interface{}{ + "core": map[string]interface{}{ + "poll_interval": 60, + "webdav_root": "remote.php/webdav", + "status": map[string]interface{}{ + "installed": true, + "maintenance": false, + "needsDbUpgrade": false, + "version": version.Legacy, + "versionstring": version.LegacyString, + "edition": "Community", + "productname": "Infinite Scale", + "product": "Infinite Scale", + "productversion": version.GetString(), + "hostname": "", + }, + "support_url_signing": true, + }, + "checksums": map[string]interface{}{ + "supported_types": cfg.Checksums.SupportedTypes, + "preferred_upload_type": cfg.Checksums.PreferredUploadType, + }, + "files": filesCfg, + "dav": map[string]interface{}{ + "reports": []string{"search-files"}, + }, + "files_sharing": map[string]interface{}{ + "api_enabled": true, + "resharing": cfg.EnableResharing, + "group_sharing": true, + "auto_accept_share": true, + "share_with_group_members_only": true, + "share_with_membership_groups_only": true, + "default_permissions": 22, + "search_min_length": 3, + "public": map[string]interface{}{ + "enabled": true, + "send_mail": true, + "defaultPublicLinkShareName": "Public link", + "social_share": true, + "upload": true, + "multiple": true, + "supports_upload_only": true, + "password": map[string]interface{}{ + "enforced": false, + "enforced_for": map[string]interface{}{ + "read_only": false, + "read_write": false, + "upload_only": false, + }, + }, + "expire_date": map[string]interface{}{ + "enabled": true, + }, + "can_edit": true, + }, + "user": map[string]interface{}{ + "send_mail": true, + "profile_picture": false, + "settings": []map[string]interface{}{ + { + "enabled": true, + "version": "1.0.0", + }, + }, + }, + "user_enumeration": map[string]interface{}{ + "enabled": true, + "group_members_only": true, + }, + "federation": map[string]interface{}{ + "outgoing": true, + "incoming": true, + }, + }, + "spaces": map[string]interface{}{ + "version": "0.0.1", + "enabled": cfg.EnableProjectSpaces || cfg.EnableShareJail, + "projects": cfg.EnableProjectSpaces, + "share_jail": cfg.EnableShareJail, + }, + }, + "version": map[string]interface{}{ + "product": "Infinite Scale", + "edition": "Community", + "major": version.ParsedLegacy().Major(), + "minor": version.ParsedLegacy().Minor(), + "micro": version.ParsedLegacy().Patch(), + "string": version.LegacyString, + "productversion": version.GetString(), + }, + }, + }, + }, + }, + } +} diff --git a/services/frontend/pkg/server/debug/option.go b/services/frontend/pkg/server/debug/option.go new file mode 100644 index 00000000000..913fe9a70b7 --- /dev/null +++ b/services/frontend/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/frontend/pkg/config" +) + +// 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/services/frontend/pkg/server/debug/server.go b/services/frontend/pkg/server/debug/server.go new file mode 100644 index 00000000000..84dfc1c71fb --- /dev/null +++ b/services/frontend/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/frontend/pkg/config" +) + +// 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.GetString()), + 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/services/frontend/pkg/tracing/tracing.go b/services/frontend/pkg/tracing/tracing.go new file mode 100644 index 00000000000..84ced0ccbb7 --- /dev/null +++ b/services/frontend/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/frontend/pkg/config" + "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/services/gateway/Makefile similarity index 100% rename from extensions/gateway/Makefile rename to services/gateway/Makefile diff --git a/services/gateway/cmd/gateway/main.go b/services/gateway/cmd/gateway/main.go new file mode 100644 index 00000000000..d22e804011a --- /dev/null +++ b/services/gateway/cmd/gateway/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/gateway/pkg/command" + "github.com/owncloud/ocis/v2/services/gateway/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/services/gateway/pkg/command/health.go b/services/gateway/pkg/command/health.go new file mode 100644 index 00000000000..1dd8229b695 --- /dev/null +++ b/services/gateway/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/gateway/pkg/config" + "github.com/owncloud/ocis/v2/services/gateway/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/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/services/gateway/pkg/command/root.go b/services/gateway/pkg/command/root.go new file mode 100644 index 00000000000..41cddd994c8 --- /dev/null +++ b/services/gateway/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/gateway/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/services/gateway/pkg/command/server.go b/services/gateway/pkg/command/server.go new file mode 100644 index 00000000000..3287c92307a --- /dev/null +++ b/services/gateway/pkg/command/server.go @@ -0,0 +1,103 @@ +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/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/gateway/pkg/config" + "github.com/owncloud/ocis/v2/services/gateway/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/gateway/pkg/logging" + "github.com/owncloud/ocis/v2/services/gateway/pkg/revaconfig" + "github.com/owncloud/ocis/v2/services/gateway/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/gateway/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) + os.Exit(1) + } + 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.GetString(), + 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/services/gateway/pkg/command/version.go b/services/gateway/pkg/command/version.go new file mode 100644 index 00000000000..6e4fcfc0786 --- /dev/null +++ b/services/gateway/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/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.GetString()) + 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/services/gateway/pkg/config/config.go similarity index 100% rename from extensions/gateway/pkg/config/config.go rename to services/gateway/pkg/config/config.go diff --git a/services/gateway/pkg/config/defaults/defaultconfig.go b/services/gateway/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..34d36e16ed4 --- /dev/null +++ b/services/gateway/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,109 @@ +package defaults + +import ( + "github.com/owncloud/ocis/v2/services/gateway/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:9143", + Token: "", + Pprof: false, + Zpages: false, + }, + GRPC: config.GRPCConfig{ + Addr: "127.0.0.1:9142", + Namespace: "com.owncloud.api", + Protocol: "tcp", + }, + Service: config.Service{ + Name: "gateway", + }, + Reva: &config.Reva{ + Address: "127.0.0.1:9142", + }, + + CommitShareToStorageGrant: true, + CommitShareToStorageRef: true, + ShareFolder: "Shares", + DisableHomeCreationOnLogin: true, + TransferExpires: 24 * 60 * 60, + EtagCacheTTL: 0, + + 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", + StorageSharesEndpoint: "localhost:9154", + StorageUsersEndpoint: "localhost:9157", + UsersEndpoint: "localhost:9144", + + StorageRegistry: config.StorageRegistry{ + Driver: "spaces", + JSON: "", + }, + } +} + +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{} + } + + if cfg.TransferSecret == "" && cfg.Commons != nil && cfg.Commons.TransferSecret != "" { + cfg.TransferSecret = cfg.Commons.TransferSecret + } +} + +func Sanitize(cfg *config.Config) { + // nothing to sanitize here atm +} diff --git a/services/gateway/pkg/config/parser/parse.go b/services/gateway/pkg/config/parser/parse.go new file mode 100644 index 00000000000..aea69ba8cb0 --- /dev/null +++ b/services/gateway/pkg/config/parser/parse.go @@ -0,0 +1,46 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/services/gateway/pkg/config" + "github.com/owncloud/ocis/v2/services/gateway/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +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/gateway/pkg/config/reva.go b/services/gateway/pkg/config/reva.go similarity index 100% rename from extensions/gateway/pkg/config/reva.go rename to services/gateway/pkg/config/reva.go diff --git a/services/gateway/pkg/logging/logging.go b/services/gateway/pkg/logging/logging.go new file mode 100644 index 00000000000..20e57dfb50a --- /dev/null +++ b/services/gateway/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/gateway/pkg/config" +) + +// 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/services/gateway/pkg/revaconfig/config.go b/services/gateway/pkg/revaconfig/config.go new file mode 100644 index 00000000000..3d9850a9c02 --- /dev/null +++ b/services/gateway/pkg/revaconfig/config.go @@ -0,0 +1,160 @@ +package revaconfig + +import ( + "encoding/json" + "io/ioutil" + "strings" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + + "github.com/cs3org/reva/v2/pkg/utils" + "github.com/owncloud/ocis/v2/services/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, + "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/services/gateway/pkg/server/debug/option.go b/services/gateway/pkg/server/debug/option.go new file mode 100644 index 00000000000..4ff6334a9b3 --- /dev/null +++ b/services/gateway/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/gateway/pkg/config" +) + +// 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/services/gateway/pkg/server/debug/server.go b/services/gateway/pkg/server/debug/server.go new file mode 100644 index 00000000000..4f2e20c60f9 --- /dev/null +++ b/services/gateway/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/gateway/pkg/config" +) + +// 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.GetString()), + 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/services/gateway/pkg/tracing/tracing.go b/services/gateway/pkg/tracing/tracing.go new file mode 100644 index 00000000000..82af5831678 --- /dev/null +++ b/services/gateway/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/gateway/pkg/config" + "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/graph-explorer/.dockerignore b/services/graph-explorer/.dockerignore similarity index 100% rename from extensions/graph-explorer/.dockerignore rename to services/graph-explorer/.dockerignore diff --git a/extensions/graph-explorer/Makefile b/services/graph-explorer/Makefile similarity index 100% rename from extensions/graph-explorer/Makefile rename to services/graph-explorer/Makefile diff --git a/extensions/graph-explorer/assets/.keep b/services/graph-explorer/assets/.keep similarity index 100% rename from extensions/graph-explorer/assets/.keep rename to services/graph-explorer/assets/.keep diff --git a/services/graph-explorer/cmd/graph-explorer/main.go b/services/graph-explorer/cmd/graph-explorer/main.go new file mode 100644 index 00000000000..092cd87412f --- /dev/null +++ b/services/graph-explorer/cmd/graph-explorer/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/command" + "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/graph-explorer/docker/Dockerfile.linux.amd64 b/services/graph-explorer/docker/Dockerfile.linux.amd64 similarity index 100% rename from extensions/graph-explorer/docker/Dockerfile.linux.amd64 rename to services/graph-explorer/docker/Dockerfile.linux.amd64 diff --git a/extensions/graph-explorer/docker/Dockerfile.linux.arm b/services/graph-explorer/docker/Dockerfile.linux.arm similarity index 100% rename from extensions/graph-explorer/docker/Dockerfile.linux.arm rename to services/graph-explorer/docker/Dockerfile.linux.arm diff --git a/extensions/graph-explorer/docker/Dockerfile.linux.arm64 b/services/graph-explorer/docker/Dockerfile.linux.arm64 similarity index 100% rename from extensions/graph-explorer/docker/Dockerfile.linux.arm64 rename to services/graph-explorer/docker/Dockerfile.linux.arm64 diff --git a/extensions/graph-explorer/docker/manifest.tmpl b/services/graph-explorer/docker/manifest.tmpl similarity index 100% rename from extensions/graph-explorer/docker/manifest.tmpl rename to services/graph-explorer/docker/manifest.tmpl diff --git a/extensions/graph-explorer/graph_explorer.go b/services/graph-explorer/graph_explorer.go similarity index 100% rename from extensions/graph-explorer/graph_explorer.go rename to services/graph-explorer/graph_explorer.go diff --git a/services/graph-explorer/pkg/assets/option.go b/services/graph-explorer/pkg/assets/option.go new file mode 100644 index 00000000000..b02703243a6 --- /dev/null +++ b/services/graph-explorer/pkg/assets/option.go @@ -0,0 +1,50 @@ +package assets + +import ( + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/assetsfs" + "github.com/owncloud/ocis/v2/ocis-pkg/log" + graphexplorer "github.com/owncloud/ocis/v2/services/graph-explorer" + "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/config" +) + +// New returns a new http filesystem to serve assets. +func New(opts ...Option) http.FileSystem { + options := newOptions(opts...) + return assetsfs.New(graphexplorer.Assets, "", options.Logger) +} + +// 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 + 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 + } +} + +// 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/services/graph-explorer/pkg/command/health.go b/services/graph-explorer/pkg/command/health.go new file mode 100644 index 00000000000..b83a33c820b --- /dev/null +++ b/services/graph-explorer/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/config" + "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/graph-explorer/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/services/graph-explorer/pkg/command/root.go b/services/graph-explorer/pkg/command/root.go new file mode 100644 index 00000000000..ad9e1be393e --- /dev/null +++ b/services/graph-explorer/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/graph-explorer/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 graph-explorer command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "graph-explorer", + Usage: "Serve Graph-Explorer 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 graph-explorer command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new graph-explorer.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.GraphExplorer.Commons = cfg.Commons + return SutureService{ + cfg: cfg.GraphExplorer, + } +} + +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/services/graph-explorer/pkg/command/server.go b/services/graph-explorer/pkg/command/server.go new file mode 100644 index 00000000000..db484c4db8c --- /dev/null +++ b/services/graph-explorer/pkg/command/server.go @@ -0,0 +1,108 @@ +package command + +import ( + "context" + "fmt" + "os" + + "github.com/oklog/run" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/config" + "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/logging" + "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/metrics" + "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/server/http" + "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/tracing" + "github.com/urfave/cli/v2" +) + +// Server is the entrypoint 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(ctx *cli.Context) error { + err := parser.ParseConfig(cfg) + if err != nil { + fmt.Printf("%v", err) + os.Exit(1) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg) + if err != nil { + return err + } + var ( + gr = run.Group{} + ctx, cancel = func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() + mtrcs = metrics.New() + ) + + defer cancel() + + mtrcs.BuildInfo.WithLabelValues(version.GetString()).Set(1) + + { + server, err := http.Server( + http.Logger(logger), + http.Context(ctx), + http.Namespace(cfg.HTTP.Namespace), + http.Config(cfg), + http.Metrics(mtrcs), + ) + + if err != nil { + logger.Info().Err(err).Str("transport", "http").Msg("Failed to initialize server") + return err + } + + gr.Add(func() error { + err := server.Run() + if err != nil { + logger.Error(). + Err(err). + Str("transport", "http"). + Msg("Failed to start server") + } + return err + }, func(_ error) { + logger.Info(). + Str("transport", "http"). + Msg("Shutting down server") + + cancel() + }) + } + + { + server, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("transport", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(server.ListenAndServe, func(_ error) { + _ = server.Shutdown(ctx) + cancel() + }) + } + + return gr.Run() + }, + } +} diff --git a/services/graph-explorer/pkg/command/version.go b/services/graph-explorer/pkg/command/version.go new file mode 100644 index 00000000000..49b923e9a56 --- /dev/null +++ b/services/graph-explorer/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/graph-explorer/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.GetString()) + 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/graph-explorer/pkg/config/config.go b/services/graph-explorer/pkg/config/config.go similarity index 100% rename from extensions/graph-explorer/pkg/config/config.go rename to services/graph-explorer/pkg/config/config.go diff --git a/extensions/graph-explorer/pkg/config/debug.go b/services/graph-explorer/pkg/config/debug.go similarity index 100% rename from extensions/graph-explorer/pkg/config/debug.go rename to services/graph-explorer/pkg/config/debug.go diff --git a/services/graph-explorer/pkg/config/defaults/defaultconfig.go b/services/graph-explorer/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..f342e7037fa --- /dev/null +++ b/services/graph-explorer/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,71 @@ +package defaults + +import ( + "strings" + + "github.com/owncloud/ocis/v2/services/graph-explorer/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:9136", + Token: "", + Pprof: false, + Zpages: false, + }, + HTTP: config.HTTP{ + Addr: "127.0.0.1:9135", + Root: "/graph-explorer", + Namespace: "com.owncloud.web", + }, + Service: config.Service{ + Name: "graph-explorer", + }, + GraphExplorer: config.GraphExplorer{ + ClientID: "ocis-explorer.js", + Issuer: "https://localhost:9200", + GraphURLBase: "https://localhost:9200", + GraphURLPath: "/graph", + }, + } +} + +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{} + } +} + +func Sanitize(cfg *config.Config) { + // sanitize config + if cfg.HTTP.Root == "/" { + cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/graph-explorer") + } +} diff --git a/extensions/graph-explorer/pkg/config/http.go b/services/graph-explorer/pkg/config/http.go similarity index 100% rename from extensions/graph-explorer/pkg/config/http.go rename to services/graph-explorer/pkg/config/http.go diff --git a/extensions/graph-explorer/pkg/config/log.go b/services/graph-explorer/pkg/config/log.go similarity index 100% rename from extensions/graph-explorer/pkg/config/log.go rename to services/graph-explorer/pkg/config/log.go diff --git a/services/graph-explorer/pkg/config/parser/parse.go b/services/graph-explorer/pkg/config/parser/parse.go new file mode 100644 index 00000000000..a77ced49d54 --- /dev/null +++ b/services/graph-explorer/pkg/config/parser/parse.go @@ -0,0 +1,38 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/config" + "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + // sanitize config + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + return nil +} diff --git a/extensions/graph-explorer/pkg/config/service.go b/services/graph-explorer/pkg/config/service.go similarity index 100% rename from extensions/graph-explorer/pkg/config/service.go rename to services/graph-explorer/pkg/config/service.go diff --git a/extensions/graph-explorer/pkg/config/tracing.go b/services/graph-explorer/pkg/config/tracing.go similarity index 100% rename from extensions/graph-explorer/pkg/config/tracing.go rename to services/graph-explorer/pkg/config/tracing.go diff --git a/services/graph-explorer/pkg/logging/logging.go b/services/graph-explorer/pkg/logging/logging.go new file mode 100644 index 00000000000..7fd5a58f45c --- /dev/null +++ b/services/graph-explorer/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/config" +) + +// 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/graph-explorer/pkg/metrics/metrics.go b/services/graph-explorer/pkg/metrics/metrics.go similarity index 100% rename from extensions/graph-explorer/pkg/metrics/metrics.go rename to services/graph-explorer/pkg/metrics/metrics.go diff --git a/services/graph-explorer/pkg/server/debug/option.go b/services/graph-explorer/pkg/server/debug/option.go new file mode 100644 index 00000000000..ac86e54fd53 --- /dev/null +++ b/services/graph-explorer/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/config" +) + +// 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/services/graph-explorer/pkg/server/debug/server.go b/services/graph-explorer/pkg/server/debug/server.go new file mode 100644 index 00000000000..424d621f394 --- /dev/null +++ b/services/graph-explorer/pkg/server/debug/server.go @@ -0,0 +1,59 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/config" +) + +// 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.GetString()), + 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)), + ), 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/services/graph-explorer/pkg/server/http/option.go b/services/graph-explorer/pkg/server/http/option.go new file mode 100644 index 00000000000..b1748528207 --- /dev/null +++ b/services/graph-explorer/pkg/server/http/option.go @@ -0,0 +1,76 @@ +package http + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/config" + "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/metrics" + "github.com/urfave/cli/v2" +) + +// 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 + Metrics *metrics.Metrics + Flags []cli.Flag + Namespace string +} + +// 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 + } +} + +// Metrics provides a function to set the metrics option. +func Metrics(val *metrics.Metrics) Option { + return func(o *Options) { + o.Metrics = val + } +} + +// Flags provides a function to set the flags option. +func Flags(val []cli.Flag) Option { + return func(o *Options) { + o.Flags = append(o.Flags, val...) + } +} + +// Namespace provides a function to set the Namespace option. +func Namespace(val string) Option { + return func(o *Options) { + o.Namespace = val + } +} diff --git a/services/graph-explorer/pkg/server/http/server.go b/services/graph-explorer/pkg/server/http/server.go new file mode 100644 index 00000000000..bb35c2cd134 --- /dev/null +++ b/services/graph-explorer/pkg/server/http/server.go @@ -0,0 +1,55 @@ +package http + +import ( + chimiddleware "github.com/go-chi/chi/v5/middleware" + "github.com/owncloud/ocis/v2/ocis-pkg/middleware" + "github.com/owncloud/ocis/v2/ocis-pkg/service/http" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + svc "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/service/v0" + "go-micro.dev/v4" +) + +// Server initializes the http service and server. +func Server(opts ...Option) (http.Service, error) { + options := newOptions(opts...) + + service := http.NewService( + http.Logger(options.Logger), + http.Namespace(options.Namespace), + http.Name("graph-explorer"), + http.Version(version.GetString()), + http.Address(options.Config.HTTP.Addr), + http.Context(options.Context), + http.Flags(options.Flags...), + ) + + handle := svc.NewService( + svc.Logger(options.Logger), + svc.Config(options.Config), + svc.Middleware( + chimiddleware.RealIP, + chimiddleware.RequestID, + middleware.NoCache, + middleware.Secure, + middleware.Version( + "graph-explorer", + version.GetString(), + ), + middleware.Logger( + options.Logger, + ), + ), + ) + + { + handle = svc.NewInstrument(handle, options.Metrics) + handle = svc.NewLogging(handle, options.Logger) + handle = svc.NewTracing(handle) + } + + if err := micro.RegisterHandler(service.Server(), handle); err != nil { + return http.Service{}, err + } + + return service, nil +} diff --git a/services/graph-explorer/pkg/service/v0/instrument.go b/services/graph-explorer/pkg/service/v0/instrument.go new file mode 100644 index 00000000000..58dfd3d01fb --- /dev/null +++ b/services/graph-explorer/pkg/service/v0/instrument.go @@ -0,0 +1,30 @@ +package svc + +import ( + "net/http" + + "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/metrics" +) + +// NewInstrument returns a service that instruments metrics. +func NewInstrument(next Service, metrics *metrics.Metrics) Service { + return instrument{ + next: next, + metrics: metrics, + } +} + +type instrument struct { + next Service + metrics *metrics.Metrics +} + +// ServeHTTP implements the Service interface. +func (i instrument) ServeHTTP(w http.ResponseWriter, r *http.Request) { + i.next.ServeHTTP(w, r) +} + +// ConfigJs implements the Service interface. +func (i instrument) ConfigJs(w http.ResponseWriter, r *http.Request) { + i.next.ConfigJs(w, r) +} diff --git a/extensions/graph-explorer/pkg/service/v0/logging.go b/services/graph-explorer/pkg/service/v0/logging.go similarity index 100% rename from extensions/graph-explorer/pkg/service/v0/logging.go rename to services/graph-explorer/pkg/service/v0/logging.go diff --git a/services/graph-explorer/pkg/service/v0/option.go b/services/graph-explorer/pkg/service/v0/option.go new file mode 100644 index 00000000000..f3a2f690f9c --- /dev/null +++ b/services/graph-explorer/pkg/service/v0/option.go @@ -0,0 +1,50 @@ +package svc + +import ( + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/config" +) + +// 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 + Config *config.Config + Middleware []func(http.Handler) http.Handler +} + +// 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 + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} + +// Middleware provides a function to set the middleware option. +func Middleware(val ...func(http.Handler) http.Handler) Option { + return func(o *Options) { + o.Middleware = val + } +} diff --git a/services/graph-explorer/pkg/service/v0/service.go b/services/graph-explorer/pkg/service/v0/service.go new file mode 100644 index 00000000000..055f82388a2 --- /dev/null +++ b/services/graph-explorer/pkg/service/v0/service.go @@ -0,0 +1,110 @@ +package svc + +import ( + "fmt" + "io" + "net/http" + "strings" + + "github.com/go-chi/chi/v5" + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/assets" + "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/config" +) + +// Service defines the extension handlers. +type Service interface { + ServeHTTP(http.ResponseWriter, *http.Request) + ConfigJs(http.ResponseWriter, *http.Request) +} + +// NewService returns a service implementation for Service. +func NewService(opts ...Option) Service { + options := newOptions(opts...) + + m := chi.NewMux() + m.Use(options.Middleware...) + + svc := GraphExplorer{ + logger: options.Logger, + config: options.Config, + mux: m, + } + + m.Route(options.Config.HTTP.Root, func(r chi.Router) { + r.Get("/config.js", svc.ConfigJs) + r.Mount("/", svc.Static()) + }) + + return svc +} + +// GraphExplorer defines implements the business logic for Service. +type GraphExplorer struct { + logger log.Logger + config *config.Config + mux *chi.Mux +} + +// ServeHTTP implements the Service interface. +func (p GraphExplorer) ServeHTTP(w http.ResponseWriter, r *http.Request) { + p.mux.ServeHTTP(w, r) +} + +// ConfigJs implements the Service interface. +func (p GraphExplorer) ConfigJs(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/javascript") + w.WriteHeader(http.StatusOK) + if _, err := io.WriteString(w, fmt.Sprintf("window.ClientId = \"%v\";", p.config.GraphExplorer.ClientID)); err != nil { + p.logger.Error().Err(err).Msg("Could not write to response writer") + } + if _, err := io.WriteString(w, fmt.Sprintf("window.Iss = \"%v\";", p.config.GraphExplorer.Issuer)); err != nil { + p.logger.Error().Err(err).Msg("Could not write to response writer") + } + if _, err := io.WriteString(w, fmt.Sprintf("window.GraphUrl = \"%v\";", p.config.GraphExplorer.GraphURLBase+p.config.GraphExplorer.GraphURLPath)); err != nil { + p.logger.Error().Err(err).Msg("Could not write to response writer") + } +} + +// Static simply serves all static files. +func (p GraphExplorer) Static() http.HandlerFunc { + rootWithSlash := p.config.HTTP.Root + + if !strings.HasSuffix(rootWithSlash, "/") { + rootWithSlash = rootWithSlash + "/" + } + + static := http.StripPrefix( + rootWithSlash, + http.FileServer( + assets.New( + assets.Logger(p.logger), + assets.Config(p.config), + ), + ), + ) + + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if rootWithSlash != "/" && r.URL.Path == p.config.HTTP.Root { + http.Redirect( + w, + r, + rootWithSlash, + http.StatusMovedPermanently, + ) + + return + } + + if r.URL.Path != rootWithSlash && strings.HasSuffix(r.URL.Path, "/") { + http.NotFound( + w, + r, + ) + + return + } + + static.ServeHTTP(w, r) + }) +} diff --git a/extensions/graph-explorer/pkg/service/v0/tracing.go b/services/graph-explorer/pkg/service/v0/tracing.go similarity index 100% rename from extensions/graph-explorer/pkg/service/v0/tracing.go rename to services/graph-explorer/pkg/service/v0/tracing.go diff --git a/services/graph-explorer/pkg/tracing/tracing.go b/services/graph-explorer/pkg/tracing/tracing.go new file mode 100644 index 00000000000..2371083fbca --- /dev/null +++ b/services/graph-explorer/pkg/tracing/tracing.go @@ -0,0 +1,23 @@ +package tracing + +import ( + pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/graph-explorer/pkg/config" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the proxy service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config) error { + var err error + if cfg.Tracing.Enabled { + if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { + return err + } + } + + return nil +} diff --git a/extensions/graph-explorer/reflex.conf b/services/graph-explorer/reflex.conf similarity index 100% rename from extensions/graph-explorer/reflex.conf rename to services/graph-explorer/reflex.conf diff --git a/extensions/graph/.dockerignore b/services/graph/.dockerignore similarity index 100% rename from extensions/graph/.dockerignore rename to services/graph/.dockerignore diff --git a/extensions/graph/Makefile b/services/graph/Makefile similarity index 100% rename from extensions/graph/Makefile rename to services/graph/Makefile diff --git a/services/graph/cmd/graph/main.go b/services/graph/cmd/graph/main.go new file mode 100644 index 00000000000..7844daa6af9 --- /dev/null +++ b/services/graph/cmd/graph/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/graph/pkg/command" + "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/graph/docker/Dockerfile.linux.amd64 b/services/graph/docker/Dockerfile.linux.amd64 similarity index 100% rename from extensions/graph/docker/Dockerfile.linux.amd64 rename to services/graph/docker/Dockerfile.linux.amd64 diff --git a/extensions/graph/docker/Dockerfile.linux.arm b/services/graph/docker/Dockerfile.linux.arm similarity index 100% rename from extensions/graph/docker/Dockerfile.linux.arm rename to services/graph/docker/Dockerfile.linux.arm diff --git a/extensions/graph/docker/Dockerfile.linux.arm64 b/services/graph/docker/Dockerfile.linux.arm64 similarity index 100% rename from extensions/graph/docker/Dockerfile.linux.arm64 rename to services/graph/docker/Dockerfile.linux.arm64 diff --git a/extensions/graph/docker/manifest.tmpl b/services/graph/docker/manifest.tmpl similarity index 100% rename from extensions/graph/docker/manifest.tmpl rename to services/graph/docker/manifest.tmpl diff --git a/extensions/graph/mocks/gateway_client.go b/services/graph/mocks/gateway_client.go similarity index 100% rename from extensions/graph/mocks/gateway_client.go rename to services/graph/mocks/gateway_client.go diff --git a/extensions/graph/mocks/http_client.go b/services/graph/mocks/http_client.go similarity index 100% rename from extensions/graph/mocks/http_client.go rename to services/graph/mocks/http_client.go diff --git a/extensions/graph/mocks/ldapclient.go b/services/graph/mocks/ldapclient.go similarity index 100% rename from extensions/graph/mocks/ldapclient.go rename to services/graph/mocks/ldapclient.go diff --git a/extensions/graph/mocks/publisher.go b/services/graph/mocks/publisher.go similarity index 100% rename from extensions/graph/mocks/publisher.go rename to services/graph/mocks/publisher.go diff --git a/services/graph/pkg/command/health.go b/services/graph/pkg/command/health.go new file mode 100644 index 00000000000..e1cbbbd641c --- /dev/null +++ b/services/graph/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/graph/pkg/config" + "github.com/owncloud/ocis/v2/services/graph/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/graph/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/services/graph/pkg/command/root.go b/services/graph/pkg/command/root.go new file mode 100644 index 00000000000..0e563e60e09 --- /dev/null +++ b/services/graph/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + "github.com/thejerf/suture/v4" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/graph/pkg/config" + "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-graph command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "graph", + Usage: "Serve Graph 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 graph command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new graph.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.Graph.Commons = cfg.Commons + return SutureService{ + cfg: cfg.Graph, + } +} + +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/services/graph/pkg/command/server.go b/services/graph/pkg/command/server.go new file mode 100644 index 00000000000..e0a1f0e7bb4 --- /dev/null +++ b/services/graph/pkg/command/server.go @@ -0,0 +1,99 @@ +package command + +import ( + "context" + "fmt" + "os" + + "github.com/oklog/run" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/graph/pkg/config" + "github.com/owncloud/ocis/v2/services/graph/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/graph/pkg/logging" + "github.com/owncloud/ocis/v2/services/graph/pkg/metrics" + "github.com/owncloud/ocis/v2/services/graph/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/graph/pkg/server/http" + "github.com/owncloud/ocis/v2/services/graph/pkg/tracing" + "github.com/urfave/cli/v2" +) + +// Server is the entrypoint 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) + os.Exit(1) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg) + if err != nil { + return err + } + + gr := run.Group{} + ctx, cancel := func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() + mtrcs := metrics.New() + + defer cancel() + + mtrcs.BuildInfo.WithLabelValues(version.GetString()).Set(1) + + { + server, err := http.Server( + http.Logger(logger), + http.Context(ctx), + http.Config(cfg), + http.Metrics(mtrcs), + ) + + if err != nil { + logger.Info().Err(err).Str("transport", "http").Msg("Failed to initialize server") + return err + } + + gr.Add(func() error { + return server.Run() + }, func(_ error) { + logger.Info(). + Str("transport", "http"). + Msg("Shutting down server") + + cancel() + }) + } + + { + server, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("transport", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(server.ListenAndServe, func(_ error) { + _ = server.Shutdown(ctx) + cancel() + }) + } + + return gr.Run() + }, + } +} diff --git a/services/graph/pkg/command/version.go b/services/graph/pkg/command/version.go new file mode 100644 index 00000000000..ebf93b1436e --- /dev/null +++ b/services/graph/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/graph/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.GetString()) + 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/graph/pkg/config/config.go b/services/graph/pkg/config/config.go similarity index 100% rename from extensions/graph/pkg/config/config.go rename to services/graph/pkg/config/config.go diff --git a/extensions/graph/pkg/config/debug.go b/services/graph/pkg/config/debug.go similarity index 100% rename from extensions/graph/pkg/config/debug.go rename to services/graph/pkg/config/debug.go diff --git a/services/graph/pkg/config/defaults/defaultconfig.go b/services/graph/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..508654e31ce --- /dev/null +++ b/services/graph/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,113 @@ +package defaults + +import ( + "path" + "strings" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" + "github.com/owncloud/ocis/v2/services/graph/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:9124", + Token: "", + }, + HTTP: config.HTTP{ + Addr: "127.0.0.1:9120", + Namespace: "com.owncloud.graph", + Root: "/graph", + }, + Service: config.Service{ + Name: "graph", + }, + Reva: &config.Reva{ + Address: "127.0.0.1:9142", + }, + Spaces: config.Spaces{ + WebDavBase: "https://localhost:9200", + WebDavPath: "/dav/spaces/", + DefaultQuota: "1000000000", + Insecure: false, + }, + Identity: config.Identity{ + Backend: "ldap", + LDAP: config.LDAP{ + URI: "ldaps://localhost:9235", + Insecure: false, + CACert: path.Join(defaults.BaseDataPath(), "idm", "ldap.crt"), + BindDN: "uid=libregraph,ou=sysusers,o=libregraph-idm", + UseServerUUID: false, + WriteEnabled: true, + UserBaseDN: "ou=users,o=libregraph-idm", + UserSearchScope: "sub", + UserFilter: "", + UserObjectClass: "inetOrgPerson", + UserEmailAttribute: "mail", + UserDisplayNameAttribute: "displayName", + UserNameAttribute: "uid", + // FIXME: switch this to some more widely available attribute by default + // ideally this needs to be constant for the lifetime of a users + UserIDAttribute: "owncloudUUID", + GroupBaseDN: "ou=groups,o=libregraph-idm", + GroupSearchScope: "sub", + GroupFilter: "", + GroupObjectClass: "groupOfNames", + GroupNameAttribute: "cn", + GroupIDAttribute: "owncloudUUID", + }, + }, + Events: config.Events{ + Endpoint: "127.0.0.1:9233", + Cluster: "ocis-cluster", + }, + } +} + +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.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) { + // sanitize config + if cfg.HTTP.Root != "/" { + cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") + } +} diff --git a/extensions/graph/pkg/config/http.go b/services/graph/pkg/config/http.go similarity index 100% rename from extensions/graph/pkg/config/http.go rename to services/graph/pkg/config/http.go diff --git a/extensions/graph/pkg/config/log.go b/services/graph/pkg/config/log.go similarity index 100% rename from extensions/graph/pkg/config/log.go rename to services/graph/pkg/config/log.go diff --git a/services/graph/pkg/config/parser/parse.go b/services/graph/pkg/config/parser/parse.go new file mode 100644 index 00000000000..d0fa4780b82 --- /dev/null +++ b/services/graph/pkg/config/parser/parse.go @@ -0,0 +1,46 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/services/graph/pkg/config" + "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + if cfg.TokenManager.JWTSecret == "" { + return shared.MissingJWTTokenError(cfg.Service.Name) + } + + if cfg.Identity.Backend == "ldap" && cfg.Identity.LDAP.BindPassword == "" { + return shared.MissingLDAPBindPassword(cfg.Service.Name) + } + + return nil +} diff --git a/extensions/graph/pkg/config/reva.go b/services/graph/pkg/config/reva.go similarity index 100% rename from extensions/graph/pkg/config/reva.go rename to services/graph/pkg/config/reva.go diff --git a/extensions/graph/pkg/config/service.go b/services/graph/pkg/config/service.go similarity index 100% rename from extensions/graph/pkg/config/service.go rename to services/graph/pkg/config/service.go diff --git a/extensions/graph/pkg/config/tracing.go b/services/graph/pkg/config/tracing.go similarity index 100% rename from extensions/graph/pkg/config/tracing.go rename to services/graph/pkg/config/tracing.go diff --git a/extensions/graph/pkg/identity/backend.go b/services/graph/pkg/identity/backend.go similarity index 100% rename from extensions/graph/pkg/identity/backend.go rename to services/graph/pkg/identity/backend.go diff --git a/services/graph/pkg/identity/cs3.go b/services/graph/pkg/identity/cs3.go new file mode 100644 index 00000000000..039edbfb869 --- /dev/null +++ b/services/graph/pkg/identity/cs3.go @@ -0,0 +1,209 @@ +package identity + +import ( + "context" + "net/url" + + cs3group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" + cs3user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" + libregraph "github.com/owncloud/libre-graph-api-go" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/graph/pkg/config" + "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" +) + +var ( + errNotImplemented = errorcode.New(errorcode.NotSupported, "not implemented") +) + +type CS3 struct { + Config *config.Reva + Logger *log.Logger +} + +// CreateUser implements the Backend Interface. It's currently not supported for the CS3 backend +func (i *CS3) CreateUser(ctx context.Context, user libregraph.User) (*libregraph.User, error) { + return nil, errNotImplemented +} + +// DeleteUser implements the Backend Interface. It's currently not supported for the CS3 backend +func (i *CS3) DeleteUser(ctx context.Context, nameOrID string) error { + return errNotImplemented +} + +// UpdateUser implements the Backend Interface. It's currently not suported for the CS3 backend +func (i *CS3) UpdateUser(ctx context.Context, nameOrID string, user libregraph.User) (*libregraph.User, error) { + return nil, errNotImplemented +} + +func (i *CS3) GetUser(ctx context.Context, userID string, queryParam url.Values) (*libregraph.User, error) { + client, err := pool.GetGatewayServiceClient(i.Config.Address) + if err != nil { + i.Logger.Error().Err(err).Msg("could not get client") + return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) + } + + res, err := client.GetUserByClaim(ctx, &cs3user.GetUserByClaimRequest{ + Claim: "userid", // FIXME add consts to reva + Value: userID, + }) + + switch { + case err != nil: + i.Logger.Error().Err(err).Str("userid", userID).Msg("error sending get user by claim id grpc request") + return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) + case res.Status.Code != cs3rpc.Code_CODE_OK: + if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND { + return nil, errorcode.New(errorcode.ItemNotFound, res.Status.Message) + } + i.Logger.Error().Err(err).Str("userid", userID).Msg("error sending get user by claim id grpc request") + return nil, errorcode.New(errorcode.GeneralException, res.Status.Message) + } + return CreateUserModelFromCS3(res.User), nil +} + +func (i *CS3) GetUsers(ctx context.Context, queryParam url.Values) ([]*libregraph.User, error) { + client, err := pool.GetGatewayServiceClient(i.Config.Address) + if err != nil { + i.Logger.Error().Err(err).Msg("could not get client") + return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) + } + + search := queryParam.Get("search") + if search == "" { + search = queryParam.Get("$search") + } + + res, err := client.FindUsers(ctx, &cs3user.FindUsersRequest{ + // FIXME presence match is currently not implemented, an empty search currently leads to + // Unwilling To Perform": Search Error: error parsing filter: (&(objectclass=posixAccount)(|(cn=*)(displayname=*)(mail=*))), error: Present filter match for cn not implemented + Filter: search, + }) + switch { + case err != nil: + i.Logger.Error().Err(err).Str("search", search).Msg("error sending find users grpc request") + return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) + case res.Status.Code != cs3rpc.Code_CODE_OK: + if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND { + return nil, errorcode.New(errorcode.ItemNotFound, res.Status.Message) + } + i.Logger.Error().Err(err).Str("search", search).Msg("error sending find users grpc request") + return nil, errorcode.New(errorcode.GeneralException, res.Status.Message) + } + + users := make([]*libregraph.User, 0, len(res.Users)) + + for _, user := range res.Users { + users = append(users, CreateUserModelFromCS3(user)) + } + + return users, nil +} + +func (i *CS3) GetGroups(ctx context.Context, queryParam url.Values) ([]*libregraph.Group, error) { + client, err := pool.GetGatewayServiceClient(i.Config.Address) + if err != nil { + i.Logger.Error().Err(err).Msg("could not get client") + return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) + } + + search := queryParam.Get("search") + if search == "" { + search = queryParam.Get("$search") + } + + res, err := client.FindGroups(ctx, &cs3group.FindGroupsRequest{ + // FIXME presence match is currently not implemented, an empty search currently leads to + // Unwilling To Perform": Search Error: error parsing filter: (&(objectclass=posixAccount)(|(cn=*)(displayname=*)(mail=*))), error: Present filter match for cn not implemented + Filter: search, + }) + + switch { + case err != nil: + i.Logger.Error().Err(err).Str("search", search).Msg("error sending find groups grpc request") + return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) + case res.Status.Code != cs3rpc.Code_CODE_OK: + if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND { + return nil, errorcode.New(errorcode.ItemNotFound, res.Status.Message) + } + i.Logger.Error().Err(err).Str("search", search).Msg("error sending find groups grpc request") + return nil, errorcode.New(errorcode.GeneralException, res.Status.Message) + } + + groups := make([]*libregraph.Group, 0, len(res.Groups)) + + for _, group := range res.Groups { + groups = append(groups, createGroupModelFromCS3(group)) + } + + return groups, nil +} + +// CreateGroup implements the Backend Interface. It's currently not supported for the CS3 backend +func (i *CS3) CreateGroup(ctx context.Context, group libregraph.Group) (*libregraph.Group, error) { + return nil, errorcode.New(errorcode.NotSupported, "not implemented") +} + +func (i *CS3) GetGroup(ctx context.Context, groupID string, queryParam url.Values) (*libregraph.Group, error) { + client, err := pool.GetGatewayServiceClient(i.Config.Address) + if err != nil { + i.Logger.Error().Err(err).Msg("could not get client") + return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) + } + + res, err := client.GetGroupByClaim(ctx, &cs3group.GetGroupByClaimRequest{ + Claim: "groupid", // FIXME add consts to reva + Value: groupID, + }) + + switch { + case err != nil: + i.Logger.Error().Err(err).Str("groupid", groupID).Msg("error sending get group by claim id grpc request") + return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error()) + case res.Status.Code != cs3rpc.Code_CODE_OK: + if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND { + return nil, errorcode.New(errorcode.ItemNotFound, res.Status.Message) + } + i.Logger.Error().Err(err).Str("groupid", groupID).Msg("error sending get group by claim id grpc request") + return nil, errorcode.New(errorcode.GeneralException, res.Status.Message) + } + + return createGroupModelFromCS3(res.Group), nil +} + +// DeleteGroup implements the Backend Interface. It's currently not supported for the CS3 backend +func (i *CS3) DeleteGroup(ctx context.Context, id string) error { + return errorcode.New(errorcode.NotSupported, "not implemented") +} + +// GetGroupMembers implements the Backend Interface. It's currently not supported for the CS3 backend +func (i *CS3) GetGroupMembers(ctx context.Context, groupID string) ([]*libregraph.User, error) { + return nil, errorcode.New(errorcode.NotSupported, "not implemented") +} + +// AddMembersToGroup implements the Backend Interface. It's currently not supported for the CS3 backend +func (i *CS3) AddMembersToGroup(ctx context.Context, groupID string, memberID []string) error { + return errorcode.New(errorcode.NotSupported, "not implemented") +} + +// RemoveMemberFromGroup implements the Backend Interface. It's currently not supported for the CS3 backend +func (i *CS3) RemoveMemberFromGroup(ctx context.Context, groupID string, memberID string) error { + return errorcode.New(errorcode.NotSupported, "not implemented") +} + +func createGroupModelFromCS3(g *cs3group.Group) *libregraph.Group { + if g.Id == nil { + g.Id = &cs3group.GroupId{} + } + return &libregraph.Group{ + Id: &g.Id.OpaqueId, + OnPremisesDomainName: &g.Id.Idp, + OnPremisesSamAccountName: &g.GroupName, + DisplayName: &g.DisplayName, + Mail: &g.Mail, + // TODO when to fetch and expand memberof, usernames or ids? + } +} diff --git a/extensions/graph/pkg/identity/ldap.go b/services/graph/pkg/identity/ldap.go similarity index 99% rename from extensions/graph/pkg/identity/ldap.go rename to services/graph/pkg/identity/ldap.go index f7726179bc8..959d62e741a 100644 --- a/extensions/graph/pkg/identity/ldap.go +++ b/services/graph/pkg/identity/ldap.go @@ -11,9 +11,9 @@ import ( "github.com/gofrs/uuid" ldapdn "github.com/libregraph/idm/pkg/ldapdn" libregraph "github.com/owncloud/libre-graph-api-go" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/config" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/service/v0/errorcode" "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/graph/pkg/config" + "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" "golang.org/x/exp/slices" ) diff --git a/extensions/graph/pkg/identity/ldap/reconnect.go b/services/graph/pkg/identity/ldap/reconnect.go similarity index 100% rename from extensions/graph/pkg/identity/ldap/reconnect.go rename to services/graph/pkg/identity/ldap/reconnect.go diff --git a/extensions/graph/pkg/identity/ldap_test.go b/services/graph/pkg/identity/ldap_test.go similarity index 99% rename from extensions/graph/pkg/identity/ldap_test.go rename to services/graph/pkg/identity/ldap_test.go index aac54eca444..87d0b171627 100644 --- a/extensions/graph/pkg/identity/ldap_test.go +++ b/services/graph/pkg/identity/ldap_test.go @@ -10,8 +10,8 @@ import ( "time" "github.com/go-ldap/ldap/v3" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/graph/pkg/config" ) // ldapMock implements the ldap.Client interfac diff --git a/services/graph/pkg/logging/logging.go b/services/graph/pkg/logging/logging.go new file mode 100644 index 00000000000..d48a2d31581 --- /dev/null +++ b/services/graph/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/graph/pkg/config" +) + +// 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/graph/pkg/metrics/metrics.go b/services/graph/pkg/metrics/metrics.go similarity index 100% rename from extensions/graph/pkg/metrics/metrics.go rename to services/graph/pkg/metrics/metrics.go diff --git a/extensions/graph/pkg/middleware/auth.go b/services/graph/pkg/middleware/auth.go similarity index 97% rename from extensions/graph/pkg/middleware/auth.go rename to services/graph/pkg/middleware/auth.go index e123ba8dbb2..fc6c487f72f 100644 --- a/extensions/graph/pkg/middleware/auth.go +++ b/services/graph/pkg/middleware/auth.go @@ -6,9 +6,9 @@ import ( "github.com/cs3org/reva/v2/pkg/auth/scope" revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/token/manager/jwt" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/service/v0/errorcode" "github.com/owncloud/ocis/v2/ocis-pkg/account" opkgm "github.com/owncloud/ocis/v2/ocis-pkg/middleware" + "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" gmmetadata "go-micro.dev/v4/metadata" "google.golang.org/grpc/metadata" ) diff --git a/services/graph/pkg/middleware/requireadmin.go b/services/graph/pkg/middleware/requireadmin.go new file mode 100644 index 00000000000..15d679041c5 --- /dev/null +++ b/services/graph/pkg/middleware/requireadmin.go @@ -0,0 +1,53 @@ +package middleware + +import ( + "net/http" + + revactx "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/roles" + "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + settings "github.com/owncloud/ocis/v2/services/settings/pkg/service/v0" +) + +// RequireAdmin middleware is used to require the user in context to be an admin / have account management permissions +func RequireAdmin(rm *roles.Manager, logger log.Logger) func(next http.Handler) http.Handler { + + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + u, ok := revactx.ContextGetUser(r.Context()) + if !ok { + errorcode.AccessDenied.Render(w, r, http.StatusUnauthorized, "Unauthorized") + return + } + if u.Id == nil || u.Id.OpaqueId == "" { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "user is missing an id") + return + } + // get roles from context + roleIDs, ok := roles.ReadRoleIDsFromContext(r.Context()) + if !ok { + logger.Debug().Str("userid", u.Id.OpaqueId).Msg("No roles in context, contacting settings service") + var err error + roleIDs, err = rm.FindRoleIDsForUser(r.Context(), u.Id.OpaqueId) + if err != nil { + logger.Err(err).Str("userid", u.Id.OpaqueId).Msg("failed to get roles for user") + errorcode.AccessDenied.Render(w, r, http.StatusUnauthorized, "Unauthorized") + return + } + if len(roleIDs) == 0 { + errorcode.AccessDenied.Render(w, r, http.StatusUnauthorized, "Unauthorized") + return + } + } + + // check if permission is present in roles of the authenticated account + if rm.FindPermissionByID(r.Context(), roleIDs, settings.AccountManagementPermissionID) != nil { + next.ServeHTTP(w, r) + return + } + + errorcode.AccessDenied.Render(w, r, http.StatusUnauthorized, "Unauthorized") + }) + } +} diff --git a/services/graph/pkg/server/debug/option.go b/services/graph/pkg/server/debug/option.go new file mode 100644 index 00000000000..a4b779eb2b9 --- /dev/null +++ b/services/graph/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/graph/pkg/config" +) + +// 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/services/graph/pkg/server/debug/server.go b/services/graph/pkg/server/debug/server.go new file mode 100644 index 00000000000..4b6ad6cecbb --- /dev/null +++ b/services/graph/pkg/server/debug/server.go @@ -0,0 +1,59 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/graph/pkg/config" +) + +// 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.GetString()), + 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)), + ), 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/services/graph/pkg/server/http/option.go b/services/graph/pkg/server/http/option.go new file mode 100644 index 00000000000..641c8438a72 --- /dev/null +++ b/services/graph/pkg/server/http/option.go @@ -0,0 +1,76 @@ +package http + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/graph/pkg/config" + "github.com/owncloud/ocis/v2/services/graph/pkg/metrics" + "github.com/urfave/cli/v2" +) + +// 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 + Metrics *metrics.Metrics + Flags []cli.Flag + Namespace string +} + +// 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 + } +} + +// Metrics provides a function to set the metrics option. +func Metrics(val *metrics.Metrics) Option { + return func(o *Options) { + o.Metrics = val + } +} + +// Flags provides a function to set the flags option. +func Flags(val []cli.Flag) Option { + return func(o *Options) { + o.Flags = append(o.Flags, val...) + } +} + +// Namespace provides a function to set the Namespace option. +func Namespace(val string) Option { + return func(o *Options) { + o.Namespace = val + } +} diff --git a/services/graph/pkg/server/http/server.go b/services/graph/pkg/server/http/server.go new file mode 100644 index 00000000000..5ffb9ae08b9 --- /dev/null +++ b/services/graph/pkg/server/http/server.go @@ -0,0 +1,77 @@ +package http + +import ( + "github.com/cs3org/reva/v2/pkg/events/server" + chimiddleware "github.com/go-chi/chi/v5/middleware" + "github.com/go-micro/plugins/v4/events/natsjs" + "github.com/owncloud/ocis/v2/ocis-pkg/account" + "github.com/owncloud/ocis/v2/ocis-pkg/middleware" + "github.com/owncloud/ocis/v2/ocis-pkg/service/http" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + graphMiddleware "github.com/owncloud/ocis/v2/services/graph/pkg/middleware" + svc "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" + "github.com/pkg/errors" + "go-micro.dev/v4" +) + +// Server initializes the http service and server. +func Server(opts ...Option) (http.Service, error) { + options := newOptions(opts...) + + service := http.NewService( + http.Logger(options.Logger), + http.Namespace(options.Config.HTTP.Namespace), + http.Name("graph"), + http.Version(version.GetString()), + http.Address(options.Config.HTTP.Addr), + http.Context(options.Context), + http.Flags(options.Flags...), + ) + + publisher, err := server.NewNatsStream( + natsjs.Address(options.Config.Events.Endpoint), + natsjs.ClusterID(options.Config.Events.Cluster), + ) + if err != nil { + options.Logger.Error(). + Err(err). + Msg("Error initializing events publisher") + return http.Service{}, errors.Wrap(err, "could not initialize events publisher") + } + + handle := svc.NewService( + svc.Logger(options.Logger), + svc.Config(options.Config), + svc.Middleware( + chimiddleware.RequestID, + middleware.Version( + "graph", + version.GetString(), + ), + middleware.Logger( + options.Logger, + ), + graphMiddleware.Auth( + account.Logger(options.Logger), + account.JWTSecret(options.Config.TokenManager.JWTSecret), + ), + ), + svc.EventsPublisher(publisher), + ) + + if handle == nil { + return http.Service{}, errors.New("could not initialize graph service") + } + + { + handle = svc.NewInstrument(handle, options.Metrics) + handle = svc.NewLogging(handle, options.Logger) + handle = svc.NewTracing(handle) + } + + if err := micro.RegisterHandler(service.Server(), handle); err != nil { + return http.Service{}, err + } + + return service, nil +} diff --git a/extensions/graph/pkg/service/v0/driveitems.go b/services/graph/pkg/service/v0/driveitems.go similarity index 99% rename from extensions/graph/pkg/service/v0/driveitems.go rename to services/graph/pkg/service/v0/driveitems.go index 68c40d58729..0307bdf006c 100644 --- a/extensions/graph/pkg/service/v0/driveitems.go +++ b/services/graph/pkg/service/v0/driveitems.go @@ -15,7 +15,7 @@ import ( "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/go-chi/render" libregraph "github.com/owncloud/libre-graph-api-go" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" ) // GetRootDriveChildren implements the Service interface. diff --git a/extensions/graph/pkg/service/v0/drives.go b/services/graph/pkg/service/v0/drives.go similarity index 99% rename from extensions/graph/pkg/service/v0/drives.go rename to services/graph/pkg/service/v0/drives.go index 1a0d9db1fe3..956b5b6d705 100644 --- a/extensions/graph/pkg/service/v0/drives.go +++ b/services/graph/pkg/service/v0/drives.go @@ -24,11 +24,11 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/render" libregraph "github.com/owncloud/libre-graph-api-go" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/service/v0/errorcode" - settingsServiceExt "github.com/owncloud/ocis/v2/extensions/settings/pkg/service/v0" "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" v0 "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" + "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + settingsServiceExt "github.com/owncloud/ocis/v2/services/settings/pkg/service/v0" merrors "go-micro.dev/v4/errors" ) diff --git a/extensions/graph/pkg/service/v0/drives_test.go b/services/graph/pkg/service/v0/drives_test.go similarity index 100% rename from extensions/graph/pkg/service/v0/drives_test.go rename to services/graph/pkg/service/v0/drives_test.go diff --git a/extensions/graph/pkg/service/v0/errorcode/errorcode.go b/services/graph/pkg/service/v0/errorcode/errorcode.go similarity index 100% rename from extensions/graph/pkg/service/v0/errorcode/errorcode.go rename to services/graph/pkg/service/v0/errorcode/errorcode.go diff --git a/extensions/graph/pkg/service/v0/graph.go b/services/graph/pkg/service/v0/graph.go similarity index 97% rename from extensions/graph/pkg/service/v0/graph.go rename to services/graph/pkg/service/v0/graph.go index 1b0ab42462c..9117b56b1c1 100644 --- a/extensions/graph/pkg/service/v0/graph.go +++ b/services/graph/pkg/service/v0/graph.go @@ -9,10 +9,10 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/v2/pkg/events" "github.com/go-chi/chi/v5" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/config" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/identity" "github.com/owncloud/ocis/v2/ocis-pkg/log" settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" + "github.com/owncloud/ocis/v2/services/graph/pkg/config" + "github.com/owncloud/ocis/v2/services/graph/pkg/identity" mevents "go-micro.dev/v4/events" "google.golang.org/grpc" ) diff --git a/extensions/graph/pkg/service/v0/graph_suite_test.go b/services/graph/pkg/service/v0/graph_suite_test.go similarity index 100% rename from extensions/graph/pkg/service/v0/graph_suite_test.go rename to services/graph/pkg/service/v0/graph_suite_test.go diff --git a/extensions/graph/pkg/service/v0/graph_test.go b/services/graph/pkg/service/v0/graph_test.go similarity index 97% rename from extensions/graph/pkg/service/v0/graph_test.go rename to services/graph/pkg/service/v0/graph_test.go index 89ce1fb0241..a119713c38f 100644 --- a/extensions/graph/pkg/service/v0/graph_test.go +++ b/services/graph/pkg/service/v0/graph_test.go @@ -17,11 +17,11 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" libregraph "github.com/owncloud/libre-graph-api-go" - "github.com/owncloud/ocis/v2/extensions/graph/mocks" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/config" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/config/defaults" - service "github.com/owncloud/ocis/v2/extensions/graph/pkg/service/v0" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/mocks" + "github.com/owncloud/ocis/v2/services/graph/pkg/config" + "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" + service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" + "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" "github.com/stretchr/testify/mock" ) diff --git a/services/graph/pkg/service/v0/groups.go b/services/graph/pkg/service/v0/groups.go new file mode 100644 index 00000000000..8afdf65a014 --- /dev/null +++ b/services/graph/pkg/service/v0/groups.go @@ -0,0 +1,374 @@ +package svc + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "sort" + "strings" + + "github.com/CiscoM31/godata" + libregraph "github.com/owncloud/libre-graph-api-go" + "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + + ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/v2/pkg/events" + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" +) + +const memberRefsLimit = 20 + +// GetGroups implements the Service interface. +func (g Graph) GetGroups(w http.ResponseWriter, r *http.Request) { + sanitizedPath := strings.TrimPrefix(r.URL.Path, "/graph/v1.0/") + odataReq, err := godata.ParseRequest(r.Context(), sanitizedPath, r.URL.Query()) + if err != nil { + g.logger.Err(err).Interface("query", r.URL.Query()).Msg("query error") + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) + return + } + + groups, err := g.identityBackend.GetGroups(r.Context(), r.URL.Query()) + + if err != nil { + var errcode errorcode.Error + if errors.As(err, &errcode) { + errcode.Render(w, r) + } else { + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) + } + } + + groups, err = sortGroups(odataReq, groups) + if err != nil { + var errcode errorcode.Error + if errors.As(err, &errcode) { + errcode.Render(w, r) + } else { + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) + } + return + } + render.Status(r, http.StatusOK) + render.JSON(w, r, &listResponse{Value: groups}) +} + +// PostGroup implements the Service interface. +func (g Graph) PostGroup(w http.ResponseWriter, r *http.Request) { + grp := libregraph.NewGroup() + err := json.NewDecoder(r.Body).Decode(grp) + if err != nil { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) + return + } + + if _, ok := grp.GetDisplayNameOk(); !ok { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "Missing Required Attribute") + return + } + + // Disallow user-supplied IDs. It's supposed to be readonly. We're either + // generating them in the backend ourselves or rely on the Backend's + // storage (e.g. LDAP) to provide a unique ID. + if _, ok := grp.GetIdOk(); ok { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "group id is a read-only attribute") + return + } + + if grp, err = g.identityBackend.CreateGroup(r.Context(), *grp); err != nil { + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) + return + } + + if grp != nil && grp.Id != nil { + currentUser := ctxpkg.ContextMustGetUser(r.Context()) + g.publishEvent(events.GroupCreated{Executant: currentUser.Id, GroupID: *grp.Id}) + } + render.Status(r, http.StatusOK) + render.JSON(w, r, grp) +} + +// PatchGroup implements the Service interface. +func (g Graph) PatchGroup(w http.ResponseWriter, r *http.Request) { + g.logger.Debug().Msg("Calling PatchGroup") + groupID := chi.URLParam(r, "groupID") + groupID, err := url.PathUnescape(groupID) + if err != nil { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping group id failed") + return + } + + if groupID == "" { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing group id") + return + } + changes := libregraph.NewGroup() + err = json.NewDecoder(r.Body).Decode(changes) + if err != nil { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) + return + } + + if memberRefs, ok := changes.GetMembersodataBindOk(); ok { + // The spec defines a limit of 20 members maxium per Request + if len(memberRefs) > memberRefsLimit { + errorcode.NotAllowed.Render(w, r, http.StatusInternalServerError, + fmt.Sprintf("Request is limited to %d members", memberRefsLimit)) + return + } + memberIDs := make([]string, 0, len(memberRefs)) + for _, memberRef := range memberRefs { + memberType, id, err := g.parseMemberRef(memberRef) + if err != nil { + errorcode.InvalidRequest.Render(w, r, http.StatusInternalServerError, "Error parsing member@odata.bind values") + return + } + g.logger.Debug().Str("memberType", memberType).Str("memberid", id).Msg("Add Member") + // The MS Graph spec allows "directoryObject", "user", "group" and "organizational Contact" + // we restrict this to users for now. Might add Groups as members later + if memberType != "users" { + errorcode.InvalidRequest.Render(w, r, http.StatusInternalServerError, "Only user are allowed as group members") + return + } + memberIDs = append(memberIDs, id) + } + err = g.identityBackend.AddMembersToGroup(r.Context(), groupID, memberIDs) + } + + if err != nil { + var errcode errorcode.Error + if errors.As(err, &errcode) { + errcode.Render(w, r) + } else { + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) + } + return + } + render.Status(r, http.StatusNoContent) + render.NoContent(w, r) +} + +// GetGroup implements the Service interface. +func (g Graph) GetGroup(w http.ResponseWriter, r *http.Request) { + groupID := chi.URLParam(r, "groupID") + groupID, err := url.PathUnescape(groupID) + if err != nil { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping group id failed") + } + + if groupID == "" { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing group id") + return + } + + group, err := g.identityBackend.GetGroup(r.Context(), groupID, r.URL.Query()) + if err != nil { + var errcode errorcode.Error + if errors.As(err, &errcode) { + errcode.Render(w, r) + } else { + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) + } + } + + render.Status(r, http.StatusOK) + render.JSON(w, r, group) +} + +// DeleteGroup implements the Service interface. +func (g Graph) DeleteGroup(w http.ResponseWriter, r *http.Request) { + groupID := chi.URLParam(r, "groupID") + groupID, err := url.PathUnescape(groupID) + if err != nil { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping group id failed") + return + } + + if groupID == "" { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing group id") + return + } + + err = g.identityBackend.DeleteGroup(r.Context(), groupID) + + if err != nil { + var errcode errorcode.Error + if errors.As(err, &errcode) { + errcode.Render(w, r) + } else { + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) + } + return + } + + currentUser := ctxpkg.ContextMustGetUser(r.Context()) + g.publishEvent(events.GroupDeleted{Executant: currentUser.Id, GroupID: groupID}) + render.Status(r, http.StatusNoContent) + render.NoContent(w, r) +} + +func (g Graph) GetGroupMembers(w http.ResponseWriter, r *http.Request) { + groupID := chi.URLParam(r, "groupID") + groupID, err := url.PathUnescape(groupID) + if err != nil { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping group id failed") + return + } + + if groupID == "" { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing group id") + return + } + + members, err := g.identityBackend.GetGroupMembers(r.Context(), groupID) + if err != nil { + var errcode errorcode.Error + if errors.As(err, &errcode) { + errcode.Render(w, r) + } else { + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) + } + return + } + + render.Status(r, http.StatusOK) + render.JSON(w, r, members) +} + +// PostGroupMember implements the Service interface. +func (g Graph) PostGroupMember(w http.ResponseWriter, r *http.Request) { + g.logger.Info().Msg("Calling PostGroupMember") + + groupID := chi.URLParam(r, "groupID") + groupID, err := url.PathUnescape(groupID) + if err != nil { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping group id failed") + return + } + + if groupID == "" { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing group id") + return + } + memberRef := libregraph.NewMemberReference() + err = json.NewDecoder(r.Body).Decode(memberRef) + if err != nil { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) + return + } + memberRefURL, ok := memberRef.GetOdataIdOk() + if !ok { + errorcode.InvalidRequest.Render(w, r, http.StatusInternalServerError, "@odata.id refernce is missing") + return + } + memberType, id, err := g.parseMemberRef(*memberRefURL) + if err != nil { + errorcode.InvalidRequest.Render(w, r, http.StatusInternalServerError, "Error parsing @odata.id url") + return + } + // The MS Graph spec allows "directoryObject", "user", "group" and "organizational Contact" + // we restrict this to users for now. Might add Groups as members later + if memberType != "users" { + errorcode.InvalidRequest.Render(w, r, http.StatusInternalServerError, "Only user are allowed as group members") + return + } + + g.logger.Debug().Str("memberType", memberType).Str("id", id).Msg("Add Member") + err = g.identityBackend.AddMembersToGroup(r.Context(), groupID, []string{id}) + + if err != nil { + var errcode errorcode.Error + if errors.As(err, &errcode) { + errcode.Render(w, r) + } else { + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) + } + return + } + + currentUser := ctxpkg.ContextMustGetUser(r.Context()) + g.publishEvent(events.GroupMemberAdded{Executant: currentUser.Id, GroupID: groupID, UserID: id}) + render.Status(r, http.StatusNoContent) + render.NoContent(w, r) +} + +// DeleteGroupMember implements the Service interface. +func (g Graph) DeleteGroupMember(w http.ResponseWriter, r *http.Request) { + g.logger.Info().Msg("Calling DeleteGroupMember") + + groupID := chi.URLParam(r, "groupID") + groupID, err := url.PathUnescape(groupID) + if err != nil { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping group id failed") + return + } + + if groupID == "" { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing group id") + return + } + + memberID := chi.URLParam(r, "memberID") + memberID, err = url.PathUnescape(memberID) + if err != nil { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping group id failed") + return + } + + if memberID == "" { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing group id") + return + } + g.logger.Debug().Str("groupID", groupID).Str("memberID", memberID).Msg("DeleteGroupMember") + err = g.identityBackend.RemoveMemberFromGroup(r.Context(), groupID, memberID) + + if err != nil { + var errcode errorcode.Error + if errors.As(err, &errcode) { + errcode.Render(w, r) + } else { + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) + } + return + } + currentUser := ctxpkg.ContextMustGetUser(r.Context()) + g.publishEvent(events.GroupMemberRemoved{Executant: currentUser.Id, GroupID: groupID, UserID: memberID}) + render.Status(r, http.StatusNoContent) + render.NoContent(w, r) +} + +func (g Graph) parseMemberRef(ref string) (string, string, error) { + memberURL, err := url.ParseRequestURI(ref) + if err != nil { + return "", "", err + } + segments := strings.Split(memberURL.Path, "/") + if len(segments) < 2 { + return "", "", errors.New("invalid member reference") + } + id := segments[len(segments)-1] + memberType := segments[len(segments)-2] + return memberType, id, nil +} + +func sortGroups(req *godata.GoDataRequest, groups []*libregraph.Group) ([]*libregraph.Group, error) { + var sorter sort.Interface + if req.Query.OrderBy == nil || len(req.Query.OrderBy.OrderByItems) != 1 { + return groups, nil + } + switch req.Query.OrderBy.OrderByItems[0].Field.Value { + case "displayName": + sorter = groupsByDisplayName{groups} + default: + return nil, fmt.Errorf("we do not support <%s> as a order parameter", req.Query.OrderBy.OrderByItems[0].Field.Value) + } + + if req.Query.OrderBy.OrderByItems[0].Order == "desc" { + sorter = sort.Reverse(sorter) + } + sort.Sort(sorter) + return groups, nil +} diff --git a/services/graph/pkg/service/v0/instrument.go b/services/graph/pkg/service/v0/instrument.go new file mode 100644 index 00000000000..80503fe9be2 --- /dev/null +++ b/services/graph/pkg/service/v0/instrument.go @@ -0,0 +1,105 @@ +package svc + +import ( + "net/http" + + "github.com/owncloud/ocis/v2/services/graph/pkg/metrics" +) + +// NewInstrument returns a service that instruments metrics. +func NewInstrument(next Service, metrics *metrics.Metrics) Service { + return instrument{ + next: next, + metrics: metrics, + } +} + +type instrument struct { + next Service + metrics *metrics.Metrics +} + +// ServeHTTP implements the Service interface. +func (i instrument) ServeHTTP(w http.ResponseWriter, r *http.Request) { + i.next.ServeHTTP(w, r) +} + +// GetMe implements the Service interface. +func (i instrument) GetMe(w http.ResponseWriter, r *http.Request) { + i.next.GetMe(w, r) +} + +// GetUsers implements the Service interface. +func (i instrument) GetUsers(w http.ResponseWriter, r *http.Request) { + i.next.GetUsers(w, r) +} + +// GetUser implements the Service interface. +func (i instrument) GetUser(w http.ResponseWriter, r *http.Request) { + i.next.GetUser(w, r) +} + +// PostUser implements the Service interface. +func (i instrument) PostUser(w http.ResponseWriter, r *http.Request) { + i.next.PostUser(w, r) +} + +// DeleteUser implements the Service interface. +func (i instrument) DeleteUser(w http.ResponseWriter, r *http.Request) { + i.next.DeleteUser(w, r) +} + +// PatchUser implements the Service interface. +func (i instrument) PatchUser(w http.ResponseWriter, r *http.Request) { + i.next.PatchUser(w, r) +} + +// ChangeOwnPassword implements the Service interface. +func (i instrument) ChangeOwnPassword(w http.ResponseWriter, r *http.Request) { + i.next.ChangeOwnPassword(w, r) +} + +// GetGroups implements the Service interface. +func (i instrument) GetGroups(w http.ResponseWriter, r *http.Request) { + i.next.GetGroups(w, r) +} + +// GetGroup implements the Service interface. +func (i instrument) GetGroup(w http.ResponseWriter, r *http.Request) { + i.next.GetGroup(w, r) +} + +// PostGroup implements the Service interface. +func (i instrument) PostGroup(w http.ResponseWriter, r *http.Request) { + i.next.PostGroup(w, r) +} + +// PatchGroup implements the Service interface. +func (i instrument) PatchGroup(w http.ResponseWriter, r *http.Request) { + i.next.PatchGroup(w, r) +} + +// DeleteGroup implements the Service interface. +func (i instrument) DeleteGroup(w http.ResponseWriter, r *http.Request) { + i.next.DeleteGroup(w, r) +} + +// GetGroupMembers implements the Service interface. +func (i instrument) GetGroupMembers(w http.ResponseWriter, r *http.Request) { + i.next.GetGroupMembers(w, r) +} + +// PostGroupMember implements the Service interface. +func (i instrument) PostGroupMember(w http.ResponseWriter, r *http.Request) { + i.next.PostGroupMember(w, r) +} + +// DeleteGroupMember implements the Service interface. +func (i instrument) DeleteGroupMember(w http.ResponseWriter, r *http.Request) { + i.next.DeleteGroupMember(w, r) +} + +// GetDrives implements the Service interface. +func (i instrument) GetDrives(w http.ResponseWriter, r *http.Request) { + i.next.GetDrives(w, r) +} diff --git a/extensions/graph/pkg/service/v0/logging.go b/services/graph/pkg/service/v0/logging.go similarity index 100% rename from extensions/graph/pkg/service/v0/logging.go rename to services/graph/pkg/service/v0/logging.go diff --git a/extensions/graph/pkg/service/v0/net/headers.go b/services/graph/pkg/service/v0/net/headers.go similarity index 100% rename from extensions/graph/pkg/service/v0/net/headers.go rename to services/graph/pkg/service/v0/net/headers.go diff --git a/services/graph/pkg/service/v0/option.go b/services/graph/pkg/service/v0/option.go new file mode 100644 index 00000000000..90a2b1b72df --- /dev/null +++ b/services/graph/pkg/service/v0/option.go @@ -0,0 +1,102 @@ +package svc + +import ( + "net/http" + + "github.com/cs3org/reva/v2/pkg/events" + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/roles" + settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" + "github.com/owncloud/ocis/v2/services/graph/pkg/config" + "github.com/owncloud/ocis/v2/services/graph/pkg/identity" +) + +// 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 + Config *config.Config + Middleware []func(http.Handler) http.Handler + GatewayClient GatewayClient + IdentityBackend identity.Backend + HTTPClient HTTPClient + RoleService settingssvc.RoleService + RoleManager *roles.Manager + EventsPublisher events.Publisher +} + +// 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 + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} + +// Middleware provides a function to set the middleware option. +func Middleware(val ...func(http.Handler) http.Handler) Option { + return func(o *Options) { + o.Middleware = val + } +} + +// WithGatewayClient provides a function to set the gateway client option. +func WithGatewayClient(val GatewayClient) Option { + return func(o *Options) { + o.GatewayClient = val + } +} + +// WithIdentityBackend provides a function to set the IdentityBackend option. +func WithIdentityBackend(val identity.Backend) Option { + return func(o *Options) { + o.IdentityBackend = val + } +} + +// WithHTTPClient provides a function to set the http client option. +func WithHTTPClient(val HTTPClient) Option { + return func(o *Options) { + o.HTTPClient = val + } +} + +// RoleService provides a function to set the RoleService option. +func RoleService(val settingssvc.RoleService) Option { + return func(o *Options) { + o.RoleService = val + } +} + +// RoleManager provides a function to set the RoleManager option. +func RoleManager(val *roles.Manager) Option { + return func(o *Options) { + o.RoleManager = val + } +} + +// EventsPublisher provides a function to set the EventsPublisher option. +func EventsPublisher(val events.Publisher) Option { + return func(o *Options) { + o.EventsPublisher = val + } +} diff --git a/extensions/graph/pkg/service/v0/ordering.go b/services/graph/pkg/service/v0/ordering.go similarity index 100% rename from extensions/graph/pkg/service/v0/ordering.go rename to services/graph/pkg/service/v0/ordering.go diff --git a/extensions/graph/pkg/service/v0/password.go b/services/graph/pkg/service/v0/password.go similarity index 97% rename from extensions/graph/pkg/service/v0/password.go rename to services/graph/pkg/service/v0/password.go index c94b969f34c..b5318953dc2 100644 --- a/extensions/graph/pkg/service/v0/password.go +++ b/services/graph/pkg/service/v0/password.go @@ -13,7 +13,7 @@ import ( "github.com/cs3org/reva/v2/pkg/events" "github.com/go-chi/render" libregraph "github.com/owncloud/libre-graph-api-go" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" ) // ChangeOwnPassword implements the Service interface. It allows the user to change diff --git a/extensions/graph/pkg/service/v0/password_test.go b/services/graph/pkg/service/v0/password_test.go similarity index 94% rename from extensions/graph/pkg/service/v0/password_test.go rename to services/graph/pkg/service/v0/password_test.go index 65a0cb2edf2..e26242ca7aa 100644 --- a/extensions/graph/pkg/service/v0/password_test.go +++ b/services/graph/pkg/service/v0/password_test.go @@ -16,12 +16,12 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" libregraph "github.com/owncloud/libre-graph-api-go" - "github.com/owncloud/ocis/v2/extensions/graph/mocks" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/config" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/config/defaults" - "github.com/owncloud/ocis/v2/extensions/graph/pkg/identity" - service "github.com/owncloud/ocis/v2/extensions/graph/pkg/service/v0" "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/graph/mocks" + "github.com/owncloud/ocis/v2/services/graph/pkg/config" + "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" + "github.com/owncloud/ocis/v2/services/graph/pkg/identity" + service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" "github.com/stretchr/testify/mock" ) diff --git a/services/graph/pkg/service/v0/service.go b/services/graph/pkg/service/v0/service.go new file mode 100644 index 00000000000..75066e61d87 --- /dev/null +++ b/services/graph/pkg/service/v0/service.go @@ -0,0 +1,224 @@ +package svc + +import ( + "crypto/tls" + "crypto/x509" + "io/ioutil" + "net/http" + "strconv" + "time" + + "github.com/ReneKroon/ttlcache/v2" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" + + ocisldap "github.com/owncloud/ocis/v2/ocis-pkg/ldap" + "github.com/owncloud/ocis/v2/ocis-pkg/roles" + "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" + settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" + "github.com/owncloud/ocis/v2/services/graph/pkg/identity" + "github.com/owncloud/ocis/v2/services/graph/pkg/identity/ldap" + graphm "github.com/owncloud/ocis/v2/services/graph/pkg/middleware" +) + +const ( + // HeaderPurge defines the header name for the purge header. + HeaderPurge = "Purge" +) + +// Service defines the extension handlers. +type Service interface { + ServeHTTP(http.ResponseWriter, *http.Request) + GetMe(http.ResponseWriter, *http.Request) + GetUsers(http.ResponseWriter, *http.Request) + GetUser(http.ResponseWriter, *http.Request) + PostUser(http.ResponseWriter, *http.Request) + DeleteUser(http.ResponseWriter, *http.Request) + PatchUser(http.ResponseWriter, *http.Request) + ChangeOwnPassword(http.ResponseWriter, *http.Request) + + GetGroups(http.ResponseWriter, *http.Request) + GetGroup(http.ResponseWriter, *http.Request) + PostGroup(http.ResponseWriter, *http.Request) + PatchGroup(http.ResponseWriter, *http.Request) + DeleteGroup(http.ResponseWriter, *http.Request) + GetGroupMembers(http.ResponseWriter, *http.Request) + PostGroupMember(http.ResponseWriter, *http.Request) + DeleteGroupMember(http.ResponseWriter, *http.Request) + + GetDrives(w http.ResponseWriter, r *http.Request) +} + +// NewService returns a service implementation for Service. +func NewService(opts ...Option) Service { + options := newOptions(opts...) + + m := chi.NewMux() + m.Use(options.Middleware...) + + svc := Graph{ + config: options.Config, + mux: m, + logger: &options.Logger, + spacePropertiesCache: ttlcache.NewCache(), + eventsPublisher: options.EventsPublisher, + } + if options.GatewayClient == nil { + var err error + svc.gatewayClient, err = pool.GetGatewayServiceClient(options.Config.Reva.Address) + if err != nil { + options.Logger.Error().Err(err).Msg("Could not get gateway client") + return nil + } + } else { + svc.gatewayClient = options.GatewayClient + } + if options.IdentityBackend == nil { + switch options.Config.Identity.Backend { + case "cs3": + svc.identityBackend = &identity.CS3{ + Config: options.Config.Reva, + Logger: &options.Logger, + } + case "ldap": + var err error + + var tlsConf *tls.Config + if options.Config.Identity.LDAP.Insecure { + // When insecure is set to true then we don't need a certificate. + options.Config.Identity.LDAP.CACert = "" + tlsConf = &tls.Config{ + //nolint:gosec // We need the ability to run with "insecure" (dev/testing) + InsecureSkipVerify: options.Config.Identity.LDAP.Insecure, + } + } + + if options.Config.Identity.LDAP.CACert != "" { + if err := ocisldap.WaitForCA(options.Logger, + options.Config.Identity.LDAP.Insecure, + options.Config.Identity.LDAP.CACert); err != nil { + options.Logger.Fatal().Err(err).Msg("The configured LDAP CA cert does not exist") + } + if tlsConf == nil { + tlsConf = &tls.Config{} + } + certs := x509.NewCertPool() + pemData, err := ioutil.ReadFile(options.Config.Identity.LDAP.CACert) + if err != nil { + options.Logger.Error().Err(err).Msgf("Error initializing LDAP Backend") + return nil + } + if !certs.AppendCertsFromPEM(pemData) { + options.Logger.Error().Msgf("Error initializing LDAP Backend. Adding CA cert failed") + return nil + } + tlsConf.RootCAs = certs + } + + conn := ldap.NewLDAPWithReconnect(&options.Logger, + ldap.Config{ + URI: options.Config.Identity.LDAP.URI, + BindDN: options.Config.Identity.LDAP.BindDN, + BindPassword: options.Config.Identity.LDAP.BindPassword, + TLSConfig: tlsConf, + }, + ) + if svc.identityBackend, err = identity.NewLDAPBackend(conn, options.Config.Identity.LDAP, &options.Logger); err != nil { + options.Logger.Error().Msgf("Error initializing LDAP Backend: '%s'", err) + return nil + } + default: + options.Logger.Error().Msgf("Unknown Identity Backend: '%s'", options.Config.Identity.Backend) + return nil + } + } else { + svc.identityBackend = options.IdentityBackend + } + + if options.HTTPClient == nil { + http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{ + InsecureSkipVerify: options.Config.Spaces.Insecure, //nolint:gosec + } + svc.httpClient = &http.Client{} + } else { + svc.httpClient = options.HTTPClient + } + + if options.RoleService == nil { + svc.roleService = settingssvc.NewRoleService("com.owncloud.api.settings", grpc.DefaultClient) + } else { + svc.roleService = options.RoleService + } + + roleManager := options.RoleManager + if roleManager == nil { + m := roles.NewManager( + roles.CacheSize(1024), + roles.CacheTTL(time.Hour), + roles.Logger(options.Logger), + roles.RoleService(svc.roleService), + ) + roleManager = &m + } + + requireAdmin := graphm.RequireAdmin(roleManager, options.Logger) + + m.Route(options.Config.HTTP.Root, func(r chi.Router) { + r.Use(middleware.StripSlashes) + r.Route("/v1.0", func(r chi.Router) { + r.Route("/me", func(r chi.Router) { + r.Get("/", svc.GetMe) + r.Get("/drives", svc.GetDrives) + r.Get("/drive/root/children", svc.GetRootDriveChildren) + r.Post("/changePassword", svc.ChangeOwnPassword) + }) + r.Route("/users", func(r chi.Router) { + r.With(requireAdmin).Get("/", svc.GetUsers) + r.With(requireAdmin).Post("/", svc.PostUser) + r.Route("/{userID}", func(r chi.Router) { + r.Get("/", svc.GetUser) + r.With(requireAdmin).Delete("/", svc.DeleteUser) + r.With(requireAdmin).Patch("/", svc.PatchUser) + }) + }) + r.Route("/groups", func(r chi.Router) { + r.With(requireAdmin).Get("/", svc.GetGroups) + r.With(requireAdmin).Post("/", svc.PostGroup) + r.Route("/{groupID}", func(r chi.Router) { + r.Get("/", svc.GetGroup) + r.With(requireAdmin).Delete("/", svc.DeleteGroup) + r.With(requireAdmin).Patch("/", svc.PatchGroup) + r.Route("/members", func(r chi.Router) { + r.With(requireAdmin).Get("/", svc.GetGroupMembers) + r.With(requireAdmin).Post("/$ref", svc.PostGroupMember) + r.With(requireAdmin).Delete("/{memberID}/$ref", svc.DeleteGroupMember) + }) + }) + }) + r.Route("/drives", func(r chi.Router) { + r.Get("/", svc.GetAllDrives) + r.Post("/", svc.CreateDrive) + r.Route("/{driveID}", func(r chi.Router) { + r.Patch("/", svc.UpdateDrive) + r.Get("/", svc.GetSingleDrive) + r.Delete("/", svc.DeleteDrive) + }) + }) + }) + }) + + return svc +} + +// parseHeaderPurge parses the 'Purge' header. +// '1', 't', 'T', 'TRUE', 'true', 'True' are parsed as true +// all other values are false. +func parsePurgeHeader(h http.Header) bool { + val := h.Get(HeaderPurge) + + if b, err := strconv.ParseBool(val); err == nil { + return b + } + return false +} diff --git a/extensions/graph/pkg/service/v0/service_test.go b/services/graph/pkg/service/v0/service_test.go similarity index 100% rename from extensions/graph/pkg/service/v0/service_test.go rename to services/graph/pkg/service/v0/service_test.go diff --git a/extensions/graph/pkg/service/v0/tracing.go b/services/graph/pkg/service/v0/tracing.go similarity index 100% rename from extensions/graph/pkg/service/v0/tracing.go rename to services/graph/pkg/service/v0/tracing.go diff --git a/services/graph/pkg/service/v0/users.go b/services/graph/pkg/service/v0/users.go new file mode 100644 index 00000000000..96f8e9cc752 --- /dev/null +++ b/services/graph/pkg/service/v0/users.go @@ -0,0 +1,309 @@ +package svc + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "regexp" + "sort" + "strings" + + "github.com/CiscoM31/godata" + ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" + revactx "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/v2/pkg/events" + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" + libregraph "github.com/owncloud/libre-graph-api-go" + settings "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" + "github.com/owncloud/ocis/v2/services/graph/pkg/identity" + "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + settingssvc "github.com/owncloud/ocis/v2/services/settings/pkg/service/v0" +) + +// GetMe implements the Service interface. +func (g Graph) GetMe(w http.ResponseWriter, r *http.Request) { + + u, ok := revactx.ContextGetUser(r.Context()) + if !ok { + g.logger.Error().Msg("user not in context") + errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, "user not in context") + return + } + + g.logger.Info().Interface("user", u).Msg("User in /me") + + me := identity.CreateUserModelFromCS3(u) + + render.Status(r, http.StatusOK) + render.JSON(w, r, me) +} + +// GetUsers implements the Service interface. +func (g Graph) GetUsers(w http.ResponseWriter, r *http.Request) { + sanitizedPath := strings.TrimPrefix(r.URL.Path, "/graph/v1.0/") + odataReq, err := godata.ParseRequest(r.Context(), sanitizedPath, r.URL.Query()) + if err != nil { + g.logger.Err(err).Interface("query", r.URL.Query()).Msg("query error") + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) + return + } + users, err := g.identityBackend.GetUsers(r.Context(), r.URL.Query()) + if err != nil { + var errcode errorcode.Error + if errors.As(err, &errcode) { + errcode.Render(w, r) + } else { + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) + } + return + } + + users, err = sortUsers(odataReq, users) + if err != nil { + var errcode errorcode.Error + if errors.As(err, &errcode) { + errcode.Render(w, r) + } else { + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) + } + return + } + render.Status(r, http.StatusOK) + render.JSON(w, r, &listResponse{Value: users}) +} + +func (g Graph) PostUser(w http.ResponseWriter, r *http.Request) { + u := libregraph.NewUser() + err := json.NewDecoder(r.Body).Decode(u) + if err != nil { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) + return + } + + if _, ok := u.GetDisplayNameOk(); !ok { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing required Attribute: 'displayName'") + return + } + if accountName, ok := u.GetOnPremisesSamAccountNameOk(); ok { + if !isValidUsername(*accountName) { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, + fmt.Sprintf("username '%s' must be at least the local part of an email", *u.OnPremisesSamAccountName)) + return + } + } else { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing required Attribute: 'onPremisesSamAccountName'") + return + } + + if mail, ok := u.GetMailOk(); ok { + if !isValidEmail(*mail) { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, + fmt.Sprintf("'%s' is not a valid email address", *u.Mail)) + return + } + } else { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing required Attribute: 'mail'") + return + } + + // Disallow user-supplied IDs. It's supposed to be readonly. We're either + // generating them in the backend ourselves or rely on the Backend's + // storage (e.g. LDAP) to provide a unique ID. + if _, ok := u.GetIdOk(); ok { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "user id is a read-only attribute") + return + } + + if u, err = g.identityBackend.CreateUser(r.Context(), *u); err != nil { + var ecErr errorcode.Error + if errors.As(err, &ecErr) { + ecErr.Render(w, r) + } else { + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) + } + return + } + + // All users get the user role by default currently. + // to all new users for now, as create Account request does not have any role field + if g.roleService == nil { + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "could not assign role to account: roleService not configured") + return + } + if _, err = g.roleService.AssignRoleToUser(r.Context(), &settings.AssignRoleToUserRequest{ + AccountUuid: *u.Id, + RoleId: settingssvc.BundleUUIDRoleUser, + }); err != nil { + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, fmt.Sprintf("could not assign role to account %s", err.Error())) + return + } + + currentUser := ctxpkg.ContextMustGetUser(r.Context()) + g.publishEvent(events.UserCreated{Executant: currentUser.Id, UserID: *u.Id}) + + render.Status(r, http.StatusOK) + render.JSON(w, r, u) +} + +// GetUser implements the Service interface. +func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) { + userID := chi.URLParam(r, "userID") + userID, err := url.PathUnescape(userID) + if err != nil { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping user id failed") + } + + if userID == "" { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing user id") + return + } + + user, err := g.identityBackend.GetUser(r.Context(), userID, r.URL.Query()) + + if err != nil { + var errcode errorcode.Error + if errors.As(err, &errcode) { + errcode.Render(w, r) + } else { + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) + } + } + + render.Status(r, http.StatusOK) + render.JSON(w, r, user) +} + +func (g Graph) DeleteUser(w http.ResponseWriter, r *http.Request) { + userID := chi.URLParam(r, "userID") + userID, err := url.PathUnescape(userID) + if err != nil { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping user id failed") + } + + if userID == "" { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing user id") + return + } + + err = g.identityBackend.DeleteUser(r.Context(), userID) + + if err != nil { + var errcode errorcode.Error + if errors.As(err, &errcode) { + errcode.Render(w, r) + } else { + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) + } + } + + currentUser := ctxpkg.ContextMustGetUser(r.Context()) + g.publishEvent(events.UserDeleted{Executant: currentUser.Id, UserID: userID}) + + render.Status(r, http.StatusNoContent) + render.NoContent(w, r) +} + +// PatchUser implements the Service Interface. Updates the specified attributes of an +// ExistingUser +func (g Graph) PatchUser(w http.ResponseWriter, r *http.Request) { + nameOrID := chi.URLParam(r, "userID") + nameOrID, err := url.PathUnescape(nameOrID) + if err != nil { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping user id failed") + } + + if nameOrID == "" { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing user id") + return + } + changes := libregraph.NewUser() + err = json.NewDecoder(r.Body).Decode(changes) + if err != nil { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) + return + } + + var features []events.UserFeature + if mail, ok := changes.GetMailOk(); ok { + if !isValidEmail(*mail) { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, + fmt.Sprintf("'%s' is not a valid email address", *mail)) + return + } + features = append(features, events.UserFeature{Name: "email", Value: *mail}) + } + + if name, ok := changes.GetDisplayNameOk(); ok { + features = append(features, events.UserFeature{Name: "displayname", Value: *name}) + } + + u, err := g.identityBackend.UpdateUser(r.Context(), nameOrID, *changes) + if err != nil { + var errcode errorcode.Error + if errors.As(err, &errcode) { + errcode.Render(w, r) + } else { + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) + } + } + + currentUser := ctxpkg.ContextMustGetUser(r.Context()) + g.publishEvent( + events.UserFeatureChanged{ + Executant: currentUser.Id, + UserID: nameOrID, + Features: features, + }, + ) + render.Status(r, http.StatusOK) + render.JSON(w, r, u) + +} + +// We want to allow email addresses as usernames so they show up when using them in ACLs on storages that allow integration with our glauth LDAP service +// so we are adding a few restrictions from https://stackoverflow.com/questions/6949667/what-are-the-real-rules-for-linux-usernames-on-centos-6-and-rhel-6 +// names should not start with numbers +var usernameRegex = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]*(@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)*$") + +func isValidUsername(e string) bool { + if len(e) < 1 && len(e) > 254 { + return false + } + return usernameRegex.MatchString(e) +} + +// regex from https://www.w3.org/TR/2016/REC-html51-20161101/sec-forms.html#valid-e-mail-address +var emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") + +func isValidEmail(e string) bool { + if len(e) < 3 && len(e) > 254 { + return false + } + return emailRegex.MatchString(e) +} + +func sortUsers(req *godata.GoDataRequest, users []*libregraph.User) ([]*libregraph.User, error) { + var sorter sort.Interface + if req.Query.OrderBy == nil || len(req.Query.OrderBy.OrderByItems) != 1 { + return users, nil + } + switch req.Query.OrderBy.OrderByItems[0].Field.Value { + case "displayName": + sorter = usersByDisplayName{users} + case "mail": + sorter = usersByMail{users} + case "onPremisesSamAccountName": + sorter = usersByOnPremisesSamAccountName{users} + default: + return nil, fmt.Errorf("we do not support <%s> as a order parameter", req.Query.OrderBy.OrderByItems[0].Field.Value) + } + + if req.Query.OrderBy.OrderByItems[0].Order == "desc" { + sorter = sort.Reverse(sorter) + } + sort.Sort(sorter) + return users, nil +} diff --git a/services/graph/pkg/tracing/tracing.go b/services/graph/pkg/tracing/tracing.go new file mode 100644 index 00000000000..b0b22395b0c --- /dev/null +++ b/services/graph/pkg/tracing/tracing.go @@ -0,0 +1,23 @@ +package tracing + +import ( + pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/graph/pkg/config" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the graph service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config) error { + var err error + if cfg.Tracing.Enabled { + if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { + return err + } + } + + return nil +} diff --git a/extensions/graph/reflex.conf b/services/graph/reflex.conf similarity index 100% rename from extensions/graph/reflex.conf rename to services/graph/reflex.conf diff --git a/extensions/groups/Makefile b/services/groups/Makefile similarity index 100% rename from extensions/groups/Makefile rename to services/groups/Makefile diff --git a/services/groups/cmd/groups/main.go b/services/groups/cmd/groups/main.go new file mode 100644 index 00000000000..2172fc84505 --- /dev/null +++ b/services/groups/cmd/groups/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/groups/pkg/command" + "github.com/owncloud/ocis/v2/services/groups/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/services/groups/pkg/command/health.go b/services/groups/pkg/command/health.go new file mode 100644 index 00000000000..9925b7ce688 --- /dev/null +++ b/services/groups/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/groups/pkg/config" + "github.com/owncloud/ocis/v2/services/groups/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/groups/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/services/groups/pkg/command/root.go b/services/groups/pkg/command/root.go new file mode 100644 index 00000000000..c0de62dfd05 --- /dev/null +++ b/services/groups/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/groups/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.Groups.Commons = cfg.Commons + return SutureService{ + cfg: cfg.Groups, + } +} + +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/services/groups/pkg/command/server.go b/services/groups/pkg/command/server.go new file mode 100644 index 00000000000..f90bef7c465 --- /dev/null +++ b/services/groups/pkg/command/server.go @@ -0,0 +1,121 @@ +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/v2/ocis-pkg/ldap" + "github.com/owncloud/ocis/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/sync" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/groups/pkg/config" + "github.com/owncloud/ocis/v2/services/groups/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/groups/pkg/logging" + "github.com/owncloud/ocis/v2/services/groups/pkg/revaconfig" + "github.com/owncloud/ocis/v2/services/groups/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/groups/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) + os.Exit(1) + } + 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.GetString(), + 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/services/groups/pkg/command/version.go b/services/groups/pkg/command/version.go new file mode 100644 index 00000000000..1800ebcf272 --- /dev/null +++ b/services/groups/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/groups/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.GetString()) + 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/groups/pkg/config/config.go b/services/groups/pkg/config/config.go similarity index 100% rename from extensions/groups/pkg/config/config.go rename to services/groups/pkg/config/config.go diff --git a/services/groups/pkg/config/defaults/defaultconfig.go b/services/groups/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..e9e607b0bf6 --- /dev/null +++ b/services/groups/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,125 @@ +package defaults + +import ( + "path/filepath" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" + "github.com/owncloud/ocis/v2/services/groups/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:9161", + Token: "", + Pprof: false, + Zpages: false, + }, + GRPC: config.GRPCConfig{ + Addr: "127.0.0.1:9160", + Namespace: "com.owncloud.api", + Protocol: "tcp", + }, + Service: config.Service{ + Name: "groups", + }, + Reva: &config.Reva{ + Address: "127.0.0.1:9142", + }, + Driver: "ldap", + Drivers: config.Drivers{ + LDAP: config.LDAPDriver{ + URI: "ldaps://localhost:9235", + CACert: filepath.Join(defaults.BaseDataPath(), "idm", "ldap.crt"), + Insecure: false, + UserBaseDN: "ou=users,o=libregraph-idm", + GroupBaseDN: "ou=groups,o=libregraph-idm", + UserScope: "sub", + GroupScope: "sub", + UserFilter: "", + GroupFilter: "", + UserObjectClass: "inetOrgPerson", + GroupObjectClass: "groupOfNames", + BindDN: "uid=reva,ou=sysusers,o=libregraph-idm", + IDP: "https://localhost:9200", + UserSchema: config.LDAPUserSchema{ + ID: "ownclouduuid", + Mail: "mail", + DisplayName: "displayname", + Username: "uid", + }, + GroupSchema: config.LDAPGroupSchema{ + ID: "ownclouduuid", + Mail: "mail", + DisplayName: "cn", + Groupname: "cn", + Member: "member", + }, + }, + OwnCloudSQL: config.OwnCloudSQLDriver{ + DBUsername: "owncloud", + DBPassword: "", + DBHost: "mysql", + DBPort: 3306, + DBName: "owncloud", + IDP: "https://localhost:9200", + Nobody: 90, + JoinUsername: false, + JoinOwnCloudUUID: false, + EnableMedialSearch: false, + }, + }, + } +} + +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/services/groups/pkg/config/parser/parse.go b/services/groups/pkg/config/parser/parse.go new file mode 100644 index 00000000000..074f6972d14 --- /dev/null +++ b/services/groups/pkg/config/parser/parse.go @@ -0,0 +1,46 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/services/groups/pkg/config" + "github.com/owncloud/ocis/v2/services/groups/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + if cfg.TokenManager.JWTSecret == "" { + return shared.MissingJWTTokenError(cfg.Service.Name) + } + + if cfg.Drivers.LDAP.BindPassword == "" && cfg.Driver == "ldap" { + return shared.MissingLDAPBindPassword(cfg.Service.Name) + } + + return nil +} diff --git a/extensions/groups/pkg/config/reva.go b/services/groups/pkg/config/reva.go similarity index 100% rename from extensions/groups/pkg/config/reva.go rename to services/groups/pkg/config/reva.go diff --git a/services/groups/pkg/logging/logging.go b/services/groups/pkg/logging/logging.go new file mode 100644 index 00000000000..fa3a411197f --- /dev/null +++ b/services/groups/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/groups/pkg/config" +) + +// 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/services/groups/pkg/revaconfig/config.go b/services/groups/pkg/revaconfig/config.go new file mode 100644 index 00000000000..7e6d7fc4306 --- /dev/null +++ b/services/groups/pkg/revaconfig/config.go @@ -0,0 +1,83 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/v2/services/groups/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, + "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, + "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/services/groups/pkg/server/debug/option.go b/services/groups/pkg/server/debug/option.go new file mode 100644 index 00000000000..54c9e8fa82b --- /dev/null +++ b/services/groups/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/groups/pkg/config" +) + +// 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/services/groups/pkg/server/debug/server.go b/services/groups/pkg/server/debug/server.go new file mode 100644 index 00000000000..c27e1da6ec8 --- /dev/null +++ b/services/groups/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/groups/pkg/config" +) + +// 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.GetString()), + 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/services/groups/pkg/tracing/tracing.go b/services/groups/pkg/tracing/tracing.go new file mode 100644 index 00000000000..db09c1b292c --- /dev/null +++ b/services/groups/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/groups/pkg/config" + "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/Makefile b/services/idm/Makefile similarity index 100% rename from extensions/idm/Makefile rename to services/idm/Makefile diff --git a/services/idm/cmd/idm/main.go b/services/idm/cmd/idm/main.go new file mode 100644 index 00000000000..f30202e587d --- /dev/null +++ b/services/idm/cmd/idm/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/idm/pkg/command" + "github.com/owncloud/ocis/v2/services/idm/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/idm/idm.go b/services/idm/idm.go similarity index 100% rename from extensions/idm/idm.go rename to services/idm/idm.go diff --git a/extensions/idm/ldif/base.ldif.tmpl b/services/idm/ldif/base.ldif.tmpl similarity index 100% rename from extensions/idm/ldif/base.ldif.tmpl rename to services/idm/ldif/base.ldif.tmpl diff --git a/extensions/idm/ldif/demousers.ldif b/services/idm/ldif/demousers.ldif similarity index 100% rename from extensions/idm/ldif/demousers.ldif rename to services/idm/ldif/demousers.ldif diff --git a/services/idm/pkg/command/health.go b/services/idm/pkg/command/health.go new file mode 100644 index 00000000000..1b7d5a48410 --- /dev/null +++ b/services/idm/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/idm/pkg/config" + "github.com/owncloud/ocis/v2/services/idm/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/idm/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/services/idm/pkg/command/root.go b/services/idm/pkg/command/root.go new file mode 100644 index 00000000000..4db8342e21d --- /dev/null +++ b/services/idm/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/idm/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-idm command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "idm", + Usage: "Embedded LDAP service 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 idm command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new idm.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.IDM.Commons = cfg.Commons + return SutureService{ + cfg: cfg.IDM, + } +} + +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/services/idm/pkg/command/server.go b/services/idm/pkg/command/server.go new file mode 100644 index 00000000000..bd1cca2b10f --- /dev/null +++ b/services/idm/pkg/command/server.go @@ -0,0 +1,174 @@ +package command + +import ( + "context" + "encoding/base64" + "errors" + "fmt" + "html/template" + "os" + "strings" + + "github.com/go-ldap/ldif" + "github.com/libregraph/idm/pkg/ldappassword" + "github.com/libregraph/idm/pkg/ldbbolt" + "github.com/libregraph/idm/server" + pkgcrypto "github.com/owncloud/ocis/v2/ocis-pkg/crypto" + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/idm" + "github.com/owncloud/ocis/v2/services/idm/pkg/config" + "github.com/owncloud/ocis/v2/services/idm/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/idm/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Server is the entrypoint 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) + os.Exit(1) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + ctx, cancel := func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() + + defer cancel() + return start(ctx, logger, cfg) + }, + } +} + +func start(ctx context.Context, logger log.Logger, cfg *config.Config) error { + servercfg := server.Config{ + Logger: log.LogrusWrap(logger.Logger), + LDAPHandler: "boltdb", + LDAPSListenAddr: cfg.IDM.LDAPSAddr, + TLSCertFile: cfg.IDM.Cert, + TLSKeyFile: cfg.IDM.Key, + LDAPBaseDN: "o=libregraph-idm", + LDAPAdminDN: "uid=libregraph,ou=sysusers,o=libregraph-idm", + + BoltDBFile: cfg.IDM.DatabasePath, + } + + if cfg.IDM.LDAPSAddr != "" { + // Generate a self-signing cert if no certificate is present + if err := pkgcrypto.GenCert(cfg.IDM.Cert, cfg.IDM.Key, logger); err != nil { + logger.Fatal().Err(err).Msgf("Could not generate test-certificate") + } + } + if _, err := os.Stat(servercfg.BoltDBFile); errors.Is(err, os.ErrNotExist) { + logger.Debug().Msg("Bootstrapping IDM database") + if err = bootstrap(logger, cfg, servercfg); err != nil { + logger.Error().Err(err).Msg("failed to bootstrap idm database") + } + } + + svc, err := server.NewServer(&servercfg) + if err != nil { + return err + } + return svc.Serve(ctx) +} + +func bootstrap(logger log.Logger, cfg *config.Config, srvcfg server.Config) error { + // Hash password if the config does not supply a hash already + var err error + + type svcUser struct { + Name string + Password string + ID string + } + + serviceUsers := []svcUser{ + { + Name: "admin", + Password: cfg.ServiceUserPasswords.OcisAdmin, + ID: cfg.AdminUserID, + }, + { + Name: "libregraph", + Password: cfg.ServiceUserPasswords.Idm, + }, + { + Name: "idp", + Password: cfg.ServiceUserPasswords.Idp, + }, + { + Name: "reva", + Password: cfg.ServiceUserPasswords.Reva, + }, + } + + bdb := &ldbbolt.LdbBolt{} + + if err := bdb.Configure(srvcfg.Logger, srvcfg.LDAPBaseDN, srvcfg.BoltDBFile, nil); err != nil { + return err + } + defer bdb.Close() + + if err := bdb.Initialize(); err != nil { + return err + } + + // Prepare the initial Data from template. To be able to set the + // supplied service user passwords + tmpl, err := template.New("baseldif").Parse(idm.BaseLDIF) + if err != nil { + return err + } + + for i := range serviceUsers { + if strings.HasPrefix(serviceUsers[i].Password, "$argon2id$") { + // password is alread hashed + serviceUsers[i].Password = "{ARGON2}" + serviceUsers[i].Password + } else { + if serviceUsers[i].Password, err = ldappassword.Hash(serviceUsers[i].Password, "{ARGON2}"); err != nil { + return err + } + } + // We need to treat the hash as binary in the LDIF template to avoid + // go-ldap/ldif to to any fancy escaping + serviceUsers[i].Password = base64.StdEncoding.EncodeToString([]byte(serviceUsers[i].Password)) + } + var tmplWriter strings.Builder + err = tmpl.Execute(&tmplWriter, serviceUsers) + if err != nil { + return err + } + + bootstrapData := tmplWriter.String() + if cfg.CreateDemoUsers { + bootstrapData = bootstrapData + "\n" + idm.DemoUsersLDIF + } + + s := strings.NewReader(bootstrapData) + lf := &ldif.LDIF{} + err = ldif.Unmarshal(s, lf) + if err != nil { + return err + } + + for _, entry := range lf.AllEntries() { + logger.Debug().Str("dn", entry.DN).Msg("Adding entry") + if err := bdb.EntryPut(entry); err != nil { + return fmt.Errorf("error adding Entry '%s': %w", entry.DN, err) + } + } + + return nil +} diff --git a/services/idm/pkg/command/version.go b/services/idm/pkg/command/version.go new file mode 100644 index 00000000000..f18727e12fb --- /dev/null +++ b/services/idm/pkg/command/version.go @@ -0,0 +1,19 @@ +package command + +import ( + "github.com/owncloud/ocis/v2/services/idm/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 { + // not implemented + return nil + }, + } +} diff --git a/extensions/idm/pkg/config/config.go b/services/idm/pkg/config/config.go similarity index 100% rename from extensions/idm/pkg/config/config.go rename to services/idm/pkg/config/config.go diff --git a/extensions/idm/pkg/config/debug.go b/services/idm/pkg/config/debug.go similarity index 100% rename from extensions/idm/pkg/config/debug.go rename to services/idm/pkg/config/debug.go diff --git a/services/idm/pkg/config/defaults/defaultconfig.go b/services/idm/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..b736f06ea49 --- /dev/null +++ b/services/idm/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,66 @@ +package defaults + +import ( + "path" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" + "github.com/owncloud/ocis/v2/services/idm/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:9239", + }, + Service: config.Service{ + Name: "idm", + }, + CreateDemoUsers: false, + IDM: config.Settings{ + LDAPSAddr: "127.0.0.1:9235", + Cert: path.Join(defaults.BaseDataPath(), "idm", "ldap.crt"), + Key: path.Join(defaults.BaseDataPath(), "idm", "ldap.key"), + DatabasePath: path.Join(defaults.BaseDataPath(), "idm", "ocis.boltdb"), + }, + } +} + +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.AdminUserID == "" && cfg.Commons != nil { + cfg.AdminUserID = cfg.Commons.AdminUserID + } +} + +func Sanitize(cfg *config.Config) { + // nothing to sanitize here +} diff --git a/extensions/idm/pkg/config/log.go b/services/idm/pkg/config/log.go similarity index 100% rename from extensions/idm/pkg/config/log.go rename to services/idm/pkg/config/log.go diff --git a/services/idm/pkg/config/parser/parse.go b/services/idm/pkg/config/parser/parse.go new file mode 100644 index 00000000000..46957256004 --- /dev/null +++ b/services/idm/pkg/config/parser/parse.go @@ -0,0 +1,57 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/services/idm/pkg/config" + "github.com/owncloud/ocis/v2/services/idm/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + if cfg.AdminUserID == "" { + return shared.MissingAdminUserID(cfg.Service.Name) + } + + if cfg.ServiceUserPasswords.Idm == "" { + return shared.MissingServiceUserPassword(cfg.Service.Name, "IDM") + } + + if cfg.ServiceUserPasswords.OcisAdmin == "" { + return shared.MissingServiceUserPassword(cfg.Service.Name, "admin") + } + + if cfg.ServiceUserPasswords.Idp == "" { + return shared.MissingServiceUserPassword(cfg.Service.Name, "IDP") + } + + if cfg.ServiceUserPasswords.Reva == "" { + return shared.MissingServiceUserPassword(cfg.Service.Name, "REVA") + } + + return nil +} diff --git a/extensions/idm/pkg/config/service.go b/services/idm/pkg/config/service.go similarity index 100% rename from extensions/idm/pkg/config/service.go rename to services/idm/pkg/config/service.go diff --git a/extensions/idm/pkg/config/tracing.go b/services/idm/pkg/config/tracing.go similarity index 100% rename from extensions/idm/pkg/config/tracing.go rename to services/idm/pkg/config/tracing.go diff --git a/services/idm/pkg/logging/logging.go b/services/idm/pkg/logging/logging.go new file mode 100644 index 00000000000..615554e51c9 --- /dev/null +++ b/services/idm/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/idm/pkg/config" +) + +// 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/services/idm/pkg/server/debug/option.go b/services/idm/pkg/server/debug/option.go new file mode 100644 index 00000000000..3ed1956c11e --- /dev/null +++ b/services/idm/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/idm/pkg/config" +) + +// 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/services/idm/pkg/server/debug/server.go b/services/idm/pkg/server/debug/server.go new file mode 100644 index 00000000000..3ab54d3f705 --- /dev/null +++ b/services/idm/pkg/server/debug/server.go @@ -0,0 +1,59 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/idm/pkg/config" +) + +// 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.GetString()), + 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)), + ), 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/idp/.dockerignore b/services/idp/.dockerignore similarity index 100% rename from extensions/idp/.dockerignore rename to services/idp/.dockerignore diff --git a/extensions/idp/.env b/services/idp/.env similarity index 100% rename from extensions/idp/.env rename to services/idp/.env diff --git a/extensions/idp/.eslintignore b/services/idp/.eslintignore similarity index 100% rename from extensions/idp/.eslintignore rename to services/idp/.eslintignore diff --git a/extensions/idp/.eslintrc.json b/services/idp/.eslintrc.json similarity index 100% rename from extensions/idp/.eslintrc.json rename to services/idp/.eslintrc.json diff --git a/extensions/idp/.gitignore b/services/idp/.gitignore similarity index 100% rename from extensions/idp/.gitignore rename to services/idp/.gitignore diff --git a/extensions/idp/.yarn/releases/yarn-3.1.0.cjs b/services/idp/.yarn/releases/yarn-3.1.0.cjs similarity index 100% rename from extensions/idp/.yarn/releases/yarn-3.1.0.cjs rename to services/idp/.yarn/releases/yarn-3.1.0.cjs diff --git a/extensions/idp/.yarnrc.yml b/services/idp/.yarnrc.yml similarity index 100% rename from extensions/idp/.yarnrc.yml rename to services/idp/.yarnrc.yml diff --git a/extensions/idp/Makefile b/services/idp/Makefile similarity index 100% rename from extensions/idp/Makefile rename to services/idp/Makefile diff --git a/extensions/idp/assets/.keep b/services/idp/assets/.keep similarity index 100% rename from extensions/idp/assets/.keep rename to services/idp/assets/.keep diff --git a/services/idp/cmd/idp/main.go b/services/idp/cmd/idp/main.go new file mode 100644 index 00000000000..174283c909a --- /dev/null +++ b/services/idp/cmd/idp/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/idp/pkg/command" + "github.com/owncloud/ocis/v2/services/idp/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/idp/docker/Dockerfile.linux.amd64 b/services/idp/docker/Dockerfile.linux.amd64 similarity index 100% rename from extensions/idp/docker/Dockerfile.linux.amd64 rename to services/idp/docker/Dockerfile.linux.amd64 diff --git a/extensions/idp/docker/Dockerfile.linux.arm b/services/idp/docker/Dockerfile.linux.arm similarity index 100% rename from extensions/idp/docker/Dockerfile.linux.arm rename to services/idp/docker/Dockerfile.linux.arm diff --git a/extensions/idp/docker/Dockerfile.linux.arm64 b/services/idp/docker/Dockerfile.linux.arm64 similarity index 100% rename from extensions/idp/docker/Dockerfile.linux.arm64 rename to services/idp/docker/Dockerfile.linux.arm64 diff --git a/extensions/idp/docker/manifest.tmpl b/services/idp/docker/manifest.tmpl similarity index 100% rename from extensions/idp/docker/manifest.tmpl rename to services/idp/docker/manifest.tmpl diff --git a/extensions/idp/idp.go b/services/idp/idp.go similarity index 100% rename from extensions/idp/idp.go rename to services/idp/idp.go diff --git a/extensions/idp/package.json b/services/idp/package.json similarity index 100% rename from extensions/idp/package.json rename to services/idp/package.json diff --git a/services/idp/pkg/assets/option.go b/services/idp/pkg/assets/option.go new file mode 100644 index 00000000000..fe8dbf575e8 --- /dev/null +++ b/services/idp/pkg/assets/option.go @@ -0,0 +1,50 @@ +package assets + +import ( + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/assetsfs" + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/idp" + "github.com/owncloud/ocis/v2/services/idp/pkg/config" +) + +// New returns a new http filesystem to serve assets. +func New(opts ...Option) http.FileSystem { + options := newOptions(opts...) + return assetsfs.New(idp.Assets, options.Config.Asset.Path, options.Logger) +} + +// 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 + 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 + } +} + +// 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/services/idp/pkg/backends/cs3/bootstrap/cs3.go b/services/idp/pkg/backends/cs3/bootstrap/cs3.go new file mode 100644 index 00000000000..24bc638d11f --- /dev/null +++ b/services/idp/pkg/backends/cs3/bootstrap/cs3.go @@ -0,0 +1,129 @@ +/* + * Copyright 2021 Kopano and its licensors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package bootstrap + +import ( + "fmt" + "os" + + "github.com/libregraph/lico/bootstrap" + "github.com/libregraph/lico/identifier" + "github.com/libregraph/lico/identity" + "github.com/libregraph/lico/identity/managers" + cs3 "github.com/owncloud/ocis/v2/services/idp/pkg/backends/cs3/identifier" +) + +// Identity managers. +const ( + identityManagerName = "cs3" +) + +// Register adds the CS3 identity manager to the lico bootstrap +func Register() error { + return bootstrap.RegisterIdentityManager(identityManagerName, NewIdentityManager) +} + +// MustRegister adds the CS3 identity manager to the lico bootstrap or panics +func MustRegister() { + if err := Register(); err != nil { + panic(err) + } +} + +// NewIdentityManager produces a CS3 backed identity manager instance for the idp +func NewIdentityManager(bs bootstrap.Bootstrap) (identity.Manager, error) { + config := bs.Config() + + logger := config.Config.Logger + + if config.AuthorizationEndpointURI.String() != "" { + return nil, fmt.Errorf("cs3 backend is incompatible with authorization-endpoint-uri parameter") + } + config.AuthorizationEndpointURI.Path = bs.MakeURIPath(bootstrap.APITypeSignin, "/identifier/_/authorize") + + if config.EndSessionEndpointURI.String() != "" { + return nil, fmt.Errorf("cs3 backend is incompatible with endsession-endpoint-uri parameter") + } + config.EndSessionEndpointURI.Path = bs.MakeURIPath(bootstrap.APITypeSignin, "/identifier/_/endsession") + + if config.SignInFormURI.EscapedPath() == "" { + config.SignInFormURI.Path = bs.MakeURIPath(bootstrap.APITypeSignin, "/identifier") + } + + if config.SignedOutURI.EscapedPath() == "" { + config.SignedOutURI.Path = bs.MakeURIPath(bootstrap.APITypeSignin, "/goodbye") + } + + identifierBackend, identifierErr := cs3.NewCS3Backend( + config.Config, + config.TLSClientConfig, + // FIXME add a map[string]interface{} property to the lico config.Config so backends can pass custom config parameters through the bootstrap process + os.Getenv("CS3_GATEWAY"), + os.Getenv("CS3_MACHINE_AUTH_API_KEY"), + config.Settings.Insecure, + ) + if identifierErr != nil { + return nil, fmt.Errorf("failed to create identifier backend: %v", identifierErr) + } + + fullAuthorizationEndpointURL := bootstrap.WithSchemeAndHost(config.AuthorizationEndpointURI, config.IssuerIdentifierURI) + fullSignInFormURL := bootstrap.WithSchemeAndHost(config.SignInFormURI, config.IssuerIdentifierURI) + fullSignedOutEndpointURL := bootstrap.WithSchemeAndHost(config.SignedOutURI, config.IssuerIdentifierURI) + + activeIdentifier, err := identifier.NewIdentifier(&identifier.Config{ + Config: config.Config, + + BaseURI: config.IssuerIdentifierURI, + PathPrefix: bs.MakeURIPath(bootstrap.APITypeSignin, ""), + StaticFolder: config.IdentifierClientPath, + LogonCookieName: "__Secure-KKT", // Kopano-Konnect-Token + ScopesConf: config.IdentifierScopesConf, + WebAppDisabled: config.IdentifierClientDisabled, + + AuthorizationEndpointURI: fullAuthorizationEndpointURL, + SignedOutEndpointURI: fullSignedOutEndpointURL, + + DefaultBannerLogo: config.IdentifierDefaultBannerLogo, + DefaultSignInPageText: config.IdentifierDefaultSignInPageText, + DefaultUsernameHintText: config.IdentifierDefaultUsernameHintText, + UILocales: config.IdentifierUILocales, + + Backend: identifierBackend, + }) + if err != nil { + return nil, fmt.Errorf("failed to create identifier: %v", err) + } + err = activeIdentifier.SetKey(config.EncryptionSecret) + if err != nil { + return nil, fmt.Errorf("invalid --encryption-secret parameter value for identifier: %v", err) + } + + identityManagerConfig := &identity.Config{ + SignInFormURI: fullSignInFormURL, + SignedOutURI: fullSignedOutEndpointURL, + + Logger: logger, + + ScopesSupported: config.Config.AllowedScopes, + } + + identifierIdentityManager := managers.NewIdentifierIdentityManager(identityManagerConfig, activeIdentifier) + logger.Infoln("using identifier backed identity manager") + + return identifierIdentityManager, nil +} diff --git a/extensions/idp/pkg/backends/cs3/identifier/cs3.go b/services/idp/pkg/backends/cs3/identifier/cs3.go similarity index 100% rename from extensions/idp/pkg/backends/cs3/identifier/cs3.go rename to services/idp/pkg/backends/cs3/identifier/cs3.go diff --git a/extensions/idp/pkg/backends/cs3/identifier/session.go b/services/idp/pkg/backends/cs3/identifier/session.go similarity index 100% rename from extensions/idp/pkg/backends/cs3/identifier/session.go rename to services/idp/pkg/backends/cs3/identifier/session.go diff --git a/extensions/idp/pkg/backends/cs3/identifier/user.go b/services/idp/pkg/backends/cs3/identifier/user.go similarity index 100% rename from extensions/idp/pkg/backends/cs3/identifier/user.go rename to services/idp/pkg/backends/cs3/identifier/user.go diff --git a/services/idp/pkg/command/health.go b/services/idp/pkg/command/health.go new file mode 100644 index 00000000000..687d806fe5f --- /dev/null +++ b/services/idp/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/idp/pkg/config" + "github.com/owncloud/ocis/v2/services/idp/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/idp/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/services/idp/pkg/command/root.go b/services/idp/pkg/command/root.go new file mode 100644 index 00000000000..409c036f069 --- /dev/null +++ b/services/idp/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/idp/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-idp command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "idp", + Usage: "Serve IDP 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 idp command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new idp.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.IDP.Commons = cfg.Commons + return SutureService{ + cfg: cfg.IDP, + } +} + +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/services/idp/pkg/command/server.go b/services/idp/pkg/command/server.go new file mode 100644 index 00000000000..46a277b3829 --- /dev/null +++ b/services/idp/pkg/command/server.go @@ -0,0 +1,198 @@ +package command + +import ( + "bytes" + "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + + "github.com/oklog/run" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/idp/pkg/config" + "github.com/owncloud/ocis/v2/services/idp/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/idp/pkg/logging" + "github.com/owncloud/ocis/v2/services/idp/pkg/metrics" + "github.com/owncloud/ocis/v2/services/idp/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/idp/pkg/server/http" + "github.com/owncloud/ocis/v2/services/idp/pkg/tracing" + "github.com/urfave/cli/v2" +) + +const _rsaKeySize = 4096 + +// Server is the entrypoint 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) + os.Exit(1) + } + + if cfg.IDP.EncryptionSecretFile != "" { + if err := ensureEncryptionSecretExists(cfg.IDP.EncryptionSecretFile); err != nil { + return err + } + if err := ensureSigningPrivateKeyExists(cfg.IDP.SigningPrivateKeyFiles); err != nil { + return err + } + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg) + if err != nil { + return err + } + var ( + gr = run.Group{} + ctx, cancel = func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() + metrics = metrics.New() + ) + + defer cancel() + + metrics.BuildInfo.WithLabelValues(version.GetString()).Set(1) + + { + server, err := http.Server( + http.Logger(logger), + http.Context(ctx), + http.Config(cfg), + http.Metrics(metrics), + ) + + if err != nil { + logger.Info(). + Err(err). + Str("transport", "http"). + Msg("Failed to initialize server") + + return err + } + + gr.Add(func() error { + return server.Run() + }, func(_ error) { + logger.Info(). + Str("transport", "http"). + Msg("Shutting down server") + + cancel() + }) + } + + { + server, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("transport", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(server.ListenAndServe, func(_ error) { + _ = server.Shutdown(ctx) + cancel() + }) + } + + return gr.Run() + }, + } +} + +func ensureEncryptionSecretExists(path string) error { + _, err := os.Stat(path) + if err == nil { + // If the file exists we can just return + return nil + } + if !errors.Is(err, fs.ErrNotExist) { + return err + } + + dir := filepath.Dir(path) + err = os.MkdirAll(dir, 0700) + if err != nil { + return err + } + + f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0600) + if err != nil { + return nil + } + defer f.Close() + + secret := make([]byte, 32) + _, err = rand.Read(secret) + if err != nil { + return err + } + _, err = io.Copy(f, bytes.NewReader(secret)) + if err != nil { + return err + } + + return nil +} + +func ensureSigningPrivateKeyExists(paths []string) error { + for _, path := range paths { + _, err := os.Stat(path) + if err == nil { + // If the file exists we can just return + return nil + } + if !errors.Is(err, fs.ErrNotExist) { + return err + } + + dir := filepath.Dir(path) + err = os.MkdirAll(dir, 0700) + if err != nil { + return err + } + + f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0600) + if err != nil { + return nil + } + defer f.Close() + + pk, err := rsa.GenerateKey(rand.Reader, _rsaKeySize) + if err != nil { + return err + } + + pb := &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(pk), + } + if err := pem.Encode(f, pb); err != nil { + return err + } + } + return nil +} diff --git a/services/idp/pkg/command/version.go b/services/idp/pkg/command/version.go new file mode 100644 index 00000000000..59652acf502 --- /dev/null +++ b/services/idp/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/idp/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.GetString()) + 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/idp/pkg/config/config.go b/services/idp/pkg/config/config.go similarity index 100% rename from extensions/idp/pkg/config/config.go rename to services/idp/pkg/config/config.go diff --git a/extensions/idp/pkg/config/debug.go b/services/idp/pkg/config/debug.go similarity index 100% rename from extensions/idp/pkg/config/debug.go rename to services/idp/pkg/config/debug.go diff --git a/services/idp/pkg/config/defaults/defaultconfig.go b/services/idp/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..8953b4c6b29 --- /dev/null +++ b/services/idp/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,184 @@ +package defaults + +import ( + "path/filepath" + "strings" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" + "github.com/owncloud/ocis/v2/services/idp/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:9134", + }, + HTTP: config.HTTP{ + Addr: "127.0.0.1:9130", + Root: "/", + Namespace: "com.owncloud.web", + TLSCert: filepath.Join(defaults.BaseDataPath(), "idp", "server.crt"), + TLSKey: filepath.Join(defaults.BaseDataPath(), "idp", "server.key"), + TLS: false, + }, + Reva: &config.Reva{ + Address: "127.0.0.1:9142", + }, + Service: config.Service{ + Name: "idp", + }, + IDP: config.Settings{ + Iss: "https://localhost:9200", + IdentityManager: "ldap", + URIBasePath: "", + SignInURI: "", + SignedOutURI: "", + AuthorizationEndpointURI: "", + EndsessionEndpointURI: "", + Insecure: false, + TrustedProxy: nil, + AllowScope: nil, + AllowClientGuests: false, + AllowDynamicClientRegistration: false, + EncryptionSecretFile: filepath.Join(defaults.BaseDataPath(), "idp", "encryption.key"), + Listen: "", + IdentifierClientDisabled: true, + IdentifierClientPath: filepath.Join(defaults.BaseDataPath(), "idp"), + IdentifierRegistrationConf: filepath.Join(defaults.BaseDataPath(), "idp", "tmp", "identifier-registration.yaml"), + IdentifierScopesConf: "", + IdentifierDefaultBannerLogo: "", + IdentifierDefaultSignInPageText: "", + IdentifierDefaultUsernameHintText: "", + SigningKid: "private-key", + SigningMethod: "PS256", + SigningPrivateKeyFiles: []string{filepath.Join(defaults.BaseDataPath(), "idp", "private-key.pem")}, + ValidationKeysPath: "", + CookieBackendURI: "", + CookieNames: nil, + AccessTokenDurationSeconds: 60 * 60 * 24, // 1 day + IDTokenDurationSeconds: 60 * 60, // 1 hour + RefreshTokenDurationSeconds: 60 * 60 * 24 * 365 * 3, // 1 year + DyamicClientSecretDurationSeconds: 0, + }, + Clients: []config.Client{ + { + ID: "web", + Name: "ownCloud Web app", + Trusted: true, + RedirectURIs: []string{ + "{{OCIS_URL}}/", + "{{OCIS_URL}}/oidc-callback.html", + "{{OCIS_URL}}/oidc-silent-redirect.html", + }, + Origins: []string{ + "{{OCIS_URL}}", + }, + }, + { + ID: "ocis-explorer.js", + Name: "oCIS Graph Explorer", + Trusted: true, + RedirectURIs: []string{ + "{{OCIS_URL}}/graph-explorer/", + }, + Origins: []string{ + "{{OCIS_URL}}", + }, + }, + { + ID: "xdXOt13JKxym1B1QcEncf2XDkLAexMBFwiT9j6EfhhHFJhs2KM9jbjTmf8JBXE69", + Secret: "UBntmLjC2yYCeHwsyj73Uwo9TAaecAetRwMw0xYcvNL9yRdLSUi0hUAHfvCHFeFh", + Name: "ownCloud desktop app", + ApplicationType: "native", + RedirectURIs: []string{ + "http://127.0.0.1", + "http://localhost", + }, + }, + { + ID: "e4rAsNUSIUs0lF4nbv9FmCeUkTlV9GdgTLDH1b5uie7syb90SzEVrbN7HIpmWJeD", + Secret: "dInFYGV33xKzhbRmpqQltYNdfLdJIfJ9L5ISoKhNoT9qZftpdWSP71VrpGR9pmoD", + Name: "ownCloud Android app", + ApplicationType: "native", + RedirectURIs: []string{ + "oc://android.owncloud.com", + }, + }, + { + ID: "mxd5OQDk6es5LzOzRvidJNfXLUZS2oN3oUFeXPP8LpPrhx3UroJFduGEYIBOxkY1", + Secret: "KFeFWWEZO9TkisIQzR3fo7hfiMXlOpaqP8CFuTbSHzV1TUuGECglPxpiVKJfOXIx", + Name: "ownCloud iOS app", + ApplicationType: "native", + RedirectURIs: []string{ + "oc://ios.owncloud.com", + "oc.ios://ios.owncloud.com", + }, + }, + }, + Ldap: config.Ldap{ + URI: "ldaps://localhost:9235", + TLSCACert: filepath.Join(defaults.BaseDataPath(), "idm", "ldap.crt"), + BindDN: "uid=idp,ou=sysusers,o=libregraph-idm", + BaseDN: "ou=users,o=libregraph-idm", + Scope: "sub", + LoginAttribute: "uid", + EmailAttribute: "mail", + NameAttribute: "displayName", + UUIDAttribute: "uid", + UUIDAttributeType: "text", + Filter: "", + ObjectClass: "inetOrgPerson", + }, + } +} + +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.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" { + cfg.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey + } +} + +func Sanitize(cfg *config.Config) { + // sanitize config + if cfg.HTTP.Root != "/" { + cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") + } +} diff --git a/extensions/idp/pkg/config/http.go b/services/idp/pkg/config/http.go similarity index 100% rename from extensions/idp/pkg/config/http.go rename to services/idp/pkg/config/http.go diff --git a/extensions/idp/pkg/config/log.go b/services/idp/pkg/config/log.go similarity index 100% rename from extensions/idp/pkg/config/log.go rename to services/idp/pkg/config/log.go diff --git a/services/idp/pkg/config/parser/parse.go b/services/idp/pkg/config/parser/parse.go new file mode 100644 index 00000000000..3939baccdb9 --- /dev/null +++ b/services/idp/pkg/config/parser/parse.go @@ -0,0 +1,49 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/services/idp/pkg/config" + "github.com/owncloud/ocis/v2/services/idp/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + switch cfg.IDP.IdentityManager { + case "cs3": + if cfg.MachineAuthAPIKey == "" { + return shared.MissingMachineAuthApiKeyError(cfg.Service.Name) + } + case "ldap": + if cfg.Ldap.BindPassword == "" { + return shared.MissingLDAPBindPassword(cfg.Service.Name) + } + } + + return nil +} diff --git a/extensions/idp/pkg/config/reva.go b/services/idp/pkg/config/reva.go similarity index 100% rename from extensions/idp/pkg/config/reva.go rename to services/idp/pkg/config/reva.go diff --git a/extensions/idp/pkg/config/service.go b/services/idp/pkg/config/service.go similarity index 100% rename from extensions/idp/pkg/config/service.go rename to services/idp/pkg/config/service.go diff --git a/extensions/idp/pkg/config/tracing.go b/services/idp/pkg/config/tracing.go similarity index 100% rename from extensions/idp/pkg/config/tracing.go rename to services/idp/pkg/config/tracing.go diff --git a/services/idp/pkg/logging/logging.go b/services/idp/pkg/logging/logging.go new file mode 100644 index 00000000000..4bd94fe8429 --- /dev/null +++ b/services/idp/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/idp/pkg/config" +) + +// 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/idp/pkg/metrics/metrics.go b/services/idp/pkg/metrics/metrics.go similarity index 100% rename from extensions/idp/pkg/metrics/metrics.go rename to services/idp/pkg/metrics/metrics.go diff --git a/extensions/idp/pkg/middleware/static.go b/services/idp/pkg/middleware/static.go similarity index 95% rename from extensions/idp/pkg/middleware/static.go rename to services/idp/pkg/middleware/static.go index e7e18caa1d0..11bbe1fa819 100644 --- a/extensions/idp/pkg/middleware/static.go +++ b/services/idp/pkg/middleware/static.go @@ -4,7 +4,7 @@ import ( "net/http" "strings" - idpTracing "github.com/owncloud/ocis/v2/extensions/idp/pkg/tracing" + idpTracing "github.com/owncloud/ocis/v2/services/idp/pkg/tracing" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" ) diff --git a/services/idp/pkg/server/debug/option.go b/services/idp/pkg/server/debug/option.go new file mode 100644 index 00000000000..1f4cb85bc43 --- /dev/null +++ b/services/idp/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/idp/pkg/config" +) + +// 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/services/idp/pkg/server/debug/server.go b/services/idp/pkg/server/debug/server.go new file mode 100644 index 00000000000..8d99b20d208 --- /dev/null +++ b/services/idp/pkg/server/debug/server.go @@ -0,0 +1,70 @@ +package debug + +import ( + "io" + "net/http" + "net/url" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/idp/pkg/config" +) + +// 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.GetString()), + 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, options.Logger)), + debug.Ready(ready(options.Config)), + ), nil +} + +// health implements the health check. +func health(cfg *config.Config, l log.Logger) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + targetHost, err := url.Parse(cfg.Ldap.URI) + if err != nil { + l.Fatal().Err(err).Str("uri", cfg.Ldap.URI).Msg("invalid LDAP URI") + } + err = shared.RunChecklist(shared.TCPConnect(targetHost.Host)) + retVal := http.StatusOK + if err != nil { + l.Error().Err(err).Msg("Healtcheck failed") + retVal = http.StatusInternalServerError + } + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(retVal) + + _, err = io.WriteString(w, http.StatusText(retVal)) + if err != nil { + l.Fatal().Err(err).Msg("Could not write health check body") + } + } +} + +// 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) + // if we can call this function, a http(200) is a valid response as + // there is nothing we can check at this point for IDP + // if there is a mishap when initializing, there is a minimal (talking ms or ns window) + // timeframe where this code is callable + _, 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/services/idp/pkg/server/http/option.go b/services/idp/pkg/server/http/option.go new file mode 100644 index 00000000000..84074fb87e0 --- /dev/null +++ b/services/idp/pkg/server/http/option.go @@ -0,0 +1,76 @@ +package http + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/idp/pkg/config" + "github.com/owncloud/ocis/v2/services/idp/pkg/metrics" + "github.com/urfave/cli/v2" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Namespace string + Logger log.Logger + Context context.Context + Config *config.Config + Metrics *metrics.Metrics + Flags []cli.Flag +} + +// 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 + } +} + +// Metrics provides a function to set the metrics option. +func Metrics(val *metrics.Metrics) Option { + return func(o *Options) { + o.Metrics = val + } +} + +// Flags provides a function to set the flags option. +func Flags(val []cli.Flag) Option { + return func(o *Options) { + o.Flags = append(o.Flags, val...) + } +} + +// Namespace provides a function to set the namespace option. +func Namespace(val string) Option { + return func(o *Options) { + o.Namespace = val + } +} diff --git a/services/idp/pkg/server/http/server.go b/services/idp/pkg/server/http/server.go new file mode 100644 index 00000000000..6e03aa2ab2d --- /dev/null +++ b/services/idp/pkg/server/http/server.go @@ -0,0 +1,82 @@ +package http + +import ( + "crypto/tls" + "os" + + chimiddleware "github.com/go-chi/chi/v5/middleware" + pkgcrypto "github.com/owncloud/ocis/v2/ocis-pkg/crypto" + "github.com/owncloud/ocis/v2/ocis-pkg/middleware" + "github.com/owncloud/ocis/v2/ocis-pkg/service/http" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + svc "github.com/owncloud/ocis/v2/services/idp/pkg/service/v0" + "go-micro.dev/v4" +) + +// Server initializes the http service and server. +func Server(opts ...Option) (http.Service, error) { + options := newOptions(opts...) + + var tlsConfig *tls.Config + if options.Config.HTTP.TLS { + _, certErr := os.Stat(options.Config.HTTP.TLSCert) + _, keyErr := os.Stat(options.Config.HTTP.TLSKey) + + if os.IsNotExist(certErr) || os.IsNotExist(keyErr) { + options.Logger.Info().Msgf("Generating certs") + if err := pkgcrypto.GenCert(options.Config.HTTP.TLSCert, options.Config.HTTP.TLSKey, options.Logger); err != nil { + options.Logger.Fatal().Err(err).Msg("Could not setup TLS") + os.Exit(1) + } + } + + cer, err := tls.LoadX509KeyPair(options.Config.HTTP.TLSCert, options.Config.HTTP.TLSKey) + if err != nil { + options.Logger.Fatal().Err(err).Msg("Could not setup TLS") + os.Exit(1) + } + + tlsConfig = &tls.Config{MinVersion: tls.VersionTLS12, Certificates: []tls.Certificate{cer}} + } + + service := http.NewService( + http.Logger(options.Logger), + http.Namespace(options.Config.HTTP.Namespace), + http.Name(options.Config.Service.Name), + http.Version(version.GetString()), + http.Address(options.Config.HTTP.Addr), + http.Context(options.Context), + http.Flags(options.Flags...), + http.TLSConfig(tlsConfig), + ) + + handle := svc.NewService( + svc.Logger(options.Logger), + svc.Config(options.Config), + svc.Middleware( + chimiddleware.RealIP, + chimiddleware.RequestID, + middleware.TraceContext, + middleware.NoCache, + middleware.Secure, + middleware.Version( + options.Config.Service.Name, + version.GetString(), + ), + middleware.Logger( + options.Logger, + ), + ), + ) + + { + handle = svc.NewInstrument(handle, options.Metrics) + handle = svc.NewLoggingHandler(handle, options.Logger) + } + + if err := micro.RegisterHandler(service.Server(), handle); err != nil { + return http.Service{}, err + } + + return service, nil +} diff --git a/services/idp/pkg/service/v0/instrument.go b/services/idp/pkg/service/v0/instrument.go new file mode 100644 index 00000000000..ddc7c24c052 --- /dev/null +++ b/services/idp/pkg/service/v0/instrument.go @@ -0,0 +1,25 @@ +package svc + +import ( + "net/http" + + "github.com/owncloud/ocis/v2/services/idp/pkg/metrics" +) + +// NewInstrument returns a service that instruments metrics. +func NewInstrument(next Service, metrics *metrics.Metrics) Service { + return instrument{ + next: next, + metrics: metrics, + } +} + +type instrument struct { + next Service + metrics *metrics.Metrics +} + +// ServeHTTP implements the Service interface. +func (i instrument) ServeHTTP(w http.ResponseWriter, r *http.Request) { + i.next.ServeHTTP(w, r) +} diff --git a/extensions/idp/pkg/service/v0/logging.go b/services/idp/pkg/service/v0/logging.go similarity index 100% rename from extensions/idp/pkg/service/v0/logging.go rename to services/idp/pkg/service/v0/logging.go diff --git a/services/idp/pkg/service/v0/option.go b/services/idp/pkg/service/v0/option.go new file mode 100644 index 00000000000..e8ba550102c --- /dev/null +++ b/services/idp/pkg/service/v0/option.go @@ -0,0 +1,50 @@ +package svc + +import ( + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/idp/pkg/config" +) + +// 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 + Config *config.Config + Middleware []func(http.Handler) http.Handler +} + +// 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 + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} + +// Middleware provides a function to set the middleware option. +func Middleware(val ...func(http.Handler) http.Handler) Option { + return func(o *Options) { + o.Middleware = val + } +} diff --git a/services/idp/pkg/service/v0/service.go b/services/idp/pkg/service/v0/service.go new file mode 100644 index 00000000000..a32a0ecb298 --- /dev/null +++ b/services/idp/pkg/service/v0/service.go @@ -0,0 +1,279 @@ +package svc + +import ( + "bytes" + "context" + "fmt" + "io/ioutil" + "net/http" + "os" + "path" + "strings" + + "github.com/go-chi/chi/v5" + "github.com/gorilla/mux" + "github.com/libregraph/lico/bootstrap" + guestBackendSupport "github.com/libregraph/lico/bootstrap/backends/guest" + kcBackendSupport "github.com/libregraph/lico/bootstrap/backends/kc" + ldapBackendSupport "github.com/libregraph/lico/bootstrap/backends/ldap" + libreGraphBackendSupport "github.com/libregraph/lico/bootstrap/backends/libregraph" + licoconfig "github.com/libregraph/lico/config" + "github.com/libregraph/lico/server" + "github.com/owncloud/ocis/v2/ocis-pkg/ldap" + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/idp/pkg/assets" + cs3BackendSupport "github.com/owncloud/ocis/v2/services/idp/pkg/backends/cs3/bootstrap" + "github.com/owncloud/ocis/v2/services/idp/pkg/config" + "github.com/owncloud/ocis/v2/services/idp/pkg/middleware" + "gopkg.in/yaml.v2" + "stash.kopano.io/kgol/rndm" +) + +// Service defines the extension handlers. +type Service interface { + ServeHTTP(http.ResponseWriter, *http.Request) +} + +// NewService returns a service implementation for Service. +func NewService(opts ...Option) Service { + ctx := context.Background() + options := newOptions(opts...) + logger := options.Logger.Logger + assetVFS := assets.New( + assets.Logger(options.Logger), + assets.Config(options.Config), + ) + + if err := createTemporaryClientsConfig( + options.Config.IDP.IdentifierRegistrationConf, + options.Config.IDP.Iss, + options.Config.Clients, + ); err != nil { + logger.Fatal().Err(err).Msg("could not create default config") + } + + switch options.Config.IDP.IdentityManager { + case "cs3": + cs3BackendSupport.MustRegister() + if err := initCS3EnvVars(options.Config.Reva.Address, options.Config.MachineAuthAPIKey); err != nil { + logger.Fatal().Err(err).Msg("could not initialize cs3 backend env vars") + } + case "ldap": + + if err := ldap.WaitForCA(options.Logger, options.Config.IDP.Insecure, options.Config.Ldap.TLSCACert); err != nil { + logger.Fatal().Err(err).Msg("The configured LDAP CA cert does not exist") + } + if options.Config.IDP.Insecure { + // force CACert to be empty to avoid lico try to load it + options.Config.Ldap.TLSCACert = "" + } + + ldapBackendSupport.MustRegister() + if err := initLicoInternalLDAPEnvVars(&options.Config.Ldap); err != nil { + logger.Fatal().Err(err).Msg("could not initialize ldap env vars") + } + default: + guestBackendSupport.MustRegister() + kcBackendSupport.MustRegister() + libreGraphBackendSupport.MustRegister() + } + + // https://play.golang.org/p/Mh8AVJCd593 + idpSettings := bootstrap.Settings(options.Config.IDP) + bs, err := bootstrap.Boot(ctx, &idpSettings, &licoconfig.Config{ + Logger: log.LogrusWrap(logger), + }) + + if err != nil { + logger.Fatal().Err(err).Msg("could not bootstrap idp") + } + + managers := bs.Managers() + routes := []server.WithRoutes{managers.Must("identity").(server.WithRoutes)} + handlers := managers.Must("handler").(http.Handler) + + svc := IDP{ + logger: options.Logger, + config: options.Config, + assets: assetVFS, + } + + svc.initMux(ctx, routes, handlers, options) + + return svc +} + +type temporaryClientConfig struct { + Clients []config.Client `yaml:"clients"` +} + +func createTemporaryClientsConfig(filePath, ocisURL string, clients []config.Client) error { + + folder := path.Dir(filePath) + if _, err := os.Stat(folder); os.IsNotExist(err) { + if err := os.MkdirAll(folder, 0700); err != nil { + return err + } + } + + for i, client := range clients { + + for i, entry := range client.RedirectURIs { + client.RedirectURIs[i] = strings.ReplaceAll(entry, "{{OCIS_URL}}", strings.TrimRight(ocisURL, "/")) + } + for i, entry := range client.Origins { + client.Origins[i] = strings.ReplaceAll(entry, "{{OCIS_URL}}", strings.TrimRight(ocisURL, "/")) + } + clients[i] = client + } + + c := temporaryClientConfig{ + Clients: clients, + } + + conf, err := yaml.Marshal(c) + if err != nil { + return err + } + + confOnDisk, err := os.Create(filePath) + if err != nil { + return err + } + + defer confOnDisk.Close() + + err = ioutil.WriteFile(filePath, conf, 0600) + if err != nil { + return err + } + + return nil + +} + +// Init cs3 backend vars which are currently not accessible via idp api +func initCS3EnvVars(cs3Addr, machineAuthAPIKey string) error { + var defaults = map[string]string{ + "CS3_GATEWAY": cs3Addr, + "CS3_MACHINE_AUTH_API_KEY": machineAuthAPIKey, + } + + for k, v := range defaults { + if err := os.Setenv(k, v); err != nil { + return fmt.Errorf("could not set cs3 env var %s=%s", k, v) + } + } + + return nil +} + +// Init ldap backend vars which are currently not accessible via idp api +func initLicoInternalLDAPEnvVars(ldap *config.Ldap) error { + filter := fmt.Sprintf("(objectclass=%s)", ldap.ObjectClass) + if ldap.Filter != "" { + filter = fmt.Sprintf("(&%s%s)", ldap.Filter, filter) + } + var defaults = map[string]string{ + "LDAP_URI": ldap.URI, + "LDAP_BINDDN": ldap.BindDN, + "LDAP_BINDPW": ldap.BindPassword, + "LDAP_BASEDN": ldap.BaseDN, + "LDAP_SCOPE": ldap.Scope, + "LDAP_LOGIN_ATTRIBUTE": ldap.LoginAttribute, + "LDAP_EMAIL_ATTRIBUTE": ldap.EmailAttribute, + "LDAP_NAME_ATTRIBUTE": ldap.NameAttribute, + "LDAP_UUID_ATTRIBUTE": ldap.UUIDAttribute, + "LDAP_UUID_ATTRIBUTE_TYPE": ldap.UUIDAttributeType, + "LDAP_FILTER": filter, + } + + if ldap.TLSCACert != "" { + defaults["LDAP_TLS_CACERT"] = ldap.TLSCACert + } + + for k, v := range defaults { + if err := os.Setenv(k, v); err != nil { + return fmt.Errorf("could not set ldap env var %s=%s", k, v) + } + } + + return nil +} + +// IDP defines implements the business logic for Service. +type IDP struct { + logger log.Logger + config *config.Config + mux *chi.Mux + assets http.FileSystem +} + +// initMux initializes the internal idp gorilla mux and mounts it in to a ocis chi-router +func (idp *IDP) initMux(ctx context.Context, r []server.WithRoutes, h http.Handler, options Options) { + gm := mux.NewRouter() + for _, route := range r { + route.AddRoutes(ctx, gm) + } + + // Delegate rest to provider which is also a handler. + if h != nil { + gm.NotFoundHandler = h + } + + idp.mux = chi.NewMux() + idp.mux.Use(options.Middleware...) + + idp.mux.Use(middleware.Static( + "/signin/v1/", + assets.New( + assets.Logger(options.Logger), + assets.Config(options.Config), + ), + )) + + // handle / | index.html with a template that needs to have the BASE_PREFIX replaced + idp.mux.Get("/signin/v1/identifier", idp.Index()) + idp.mux.Get("/signin/v1/identifier/", idp.Index()) + idp.mux.Get("/signin/v1/identifier/index.html", idp.Index()) + + idp.mux.Mount("/", gm) +} + +// ServeHTTP implements the Service interface. +func (idp IDP) ServeHTTP(w http.ResponseWriter, r *http.Request) { + idp.mux.ServeHTTP(w, r) +} + +// Index renders the static html with the +func (idp IDP) Index() http.HandlerFunc { + + f, err := idp.assets.Open("/identifier/index.html") + if err != nil { + idp.logger.Fatal().Err(err).Msg("Could not open index template") + } + + template, err := ioutil.ReadAll(f) + if err != nil { + idp.logger.Fatal().Err(err).Msg("Could not read index template") + } + if err = f.Close(); err != nil { + idp.logger.Fatal().Err(err).Msg("Could not close body") + } + + // TODO add environment variable to make the path prefix configurable + pp := "/signin/v1" + indexHTML := bytes.Replace(template, []byte("__PATH_PREFIX__"), []byte(pp), 1) + + nonce := rndm.GenerateRandomString(32) + indexHTML = bytes.Replace(indexHTML, []byte("__CSP_NONCE__"), []byte(nonce), 1) + + indexHTML = bytes.Replace(indexHTML, []byte("__PASSWORD_RESET_LINK__"), []byte(idp.config.Service.PasswordResetURI), 1) + + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + if _, err := w.Write(indexHTML); err != nil { + idp.logger.Error().Err(err).Msg("could not write to response writer") + } + }) +} diff --git a/extensions/idp/pkg/service/v0/tracing.go b/services/idp/pkg/service/v0/tracing.go similarity index 100% rename from extensions/idp/pkg/service/v0/tracing.go rename to services/idp/pkg/service/v0/tracing.go diff --git a/services/idp/pkg/tracing/tracing.go b/services/idp/pkg/tracing/tracing.go new file mode 100644 index 00000000000..339597b54a6 --- /dev/null +++ b/services/idp/pkg/tracing/tracing.go @@ -0,0 +1,23 @@ +package tracing + +import ( + pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/idp/pkg/config" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the idp service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config) error { + var err error + if cfg.Tracing.Enabled { + if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { + return err + } + } + + return nil +} diff --git a/extensions/idp/reflex.conf b/services/idp/reflex.conf similarity index 100% rename from extensions/idp/reflex.conf rename to services/idp/reflex.conf diff --git a/extensions/idp/scripts/build.js b/services/idp/scripts/build.js similarity index 100% rename from extensions/idp/scripts/build.js rename to services/idp/scripts/build.js diff --git a/extensions/idp/scripts/start.js b/services/idp/scripts/start.js similarity index 100% rename from extensions/idp/scripts/start.js rename to services/idp/scripts/start.js diff --git a/extensions/idp/scripts/test.js b/services/idp/scripts/test.js similarity index 100% rename from extensions/idp/scripts/test.js rename to services/idp/scripts/test.js diff --git a/extensions/idp/ui/i18n/Makefile b/services/idp/ui/i18n/Makefile similarity index 100% rename from extensions/idp/ui/i18n/Makefile rename to services/idp/ui/i18n/Makefile diff --git a/extensions/idp/ui/i18n/de.po b/services/idp/ui/i18n/de.po similarity index 100% rename from extensions/idp/ui/i18n/de.po rename to services/idp/ui/i18n/de.po diff --git a/extensions/idp/ui/i18n/es.po b/services/idp/ui/i18n/es.po similarity index 100% rename from extensions/idp/ui/i18n/es.po rename to services/idp/ui/i18n/es.po diff --git a/extensions/idp/ui/i18n/fr.po b/services/idp/ui/i18n/fr.po similarity index 100% rename from extensions/idp/ui/i18n/fr.po rename to services/idp/ui/i18n/fr.po diff --git a/extensions/idp/ui/i18n/hi.po b/services/idp/ui/i18n/hi.po similarity index 100% rename from extensions/idp/ui/i18n/hi.po rename to services/idp/ui/i18n/hi.po diff --git a/extensions/idp/ui/i18n/hr.po b/services/idp/ui/i18n/hr.po similarity index 100% rename from extensions/idp/ui/i18n/hr.po rename to services/idp/ui/i18n/hr.po diff --git a/extensions/idp/ui/i18n/hu.po b/services/idp/ui/i18n/hu.po similarity index 100% rename from extensions/idp/ui/i18n/hu.po rename to services/idp/ui/i18n/hu.po diff --git a/extensions/idp/ui/i18n/is.po b/services/idp/ui/i18n/is.po similarity index 100% rename from extensions/idp/ui/i18n/is.po rename to services/idp/ui/i18n/is.po diff --git a/extensions/idp/ui/i18n/it.po b/services/idp/ui/i18n/it.po similarity index 100% rename from extensions/idp/ui/i18n/it.po rename to services/idp/ui/i18n/it.po diff --git a/extensions/idp/ui/i18n/ja.po b/services/idp/ui/i18n/ja.po similarity index 100% rename from extensions/idp/ui/i18n/ja.po rename to services/idp/ui/i18n/ja.po diff --git a/extensions/idp/ui/i18n/konnect-identifier.pot b/services/idp/ui/i18n/konnect-identifier.pot similarity index 100% rename from extensions/idp/ui/i18n/konnect-identifier.pot rename to services/idp/ui/i18n/konnect-identifier.pot diff --git a/extensions/idp/ui/i18n/nb.po b/services/idp/ui/i18n/nb.po similarity index 100% rename from extensions/idp/ui/i18n/nb.po rename to services/idp/ui/i18n/nb.po diff --git a/extensions/idp/ui/i18n/nl.po b/services/idp/ui/i18n/nl.po similarity index 100% rename from extensions/idp/ui/i18n/nl.po rename to services/idp/ui/i18n/nl.po diff --git a/extensions/idp/ui/i18n/pl.po b/services/idp/ui/i18n/pl.po similarity index 100% rename from extensions/idp/ui/i18n/pl.po rename to services/idp/ui/i18n/pl.po diff --git a/extensions/idp/ui/i18n/pt_PT.po b/services/idp/ui/i18n/pt_PT.po similarity index 100% rename from extensions/idp/ui/i18n/pt_PT.po rename to services/idp/ui/i18n/pt_PT.po diff --git a/extensions/idp/ui/i18n/ru.po b/services/idp/ui/i18n/ru.po similarity index 100% rename from extensions/idp/ui/i18n/ru.po rename to services/idp/ui/i18n/ru.po diff --git a/extensions/idp/ui/i18n/sl.po b/services/idp/ui/i18n/sl.po similarity index 100% rename from extensions/idp/ui/i18n/sl.po rename to services/idp/ui/i18n/sl.po diff --git a/extensions/idp/ui/i18n/src/messages.json b/services/idp/ui/i18n/src/messages.json similarity index 100% rename from extensions/idp/ui/i18n/src/messages.json rename to services/idp/ui/i18n/src/messages.json diff --git a/extensions/idp/ui/public/index.html b/services/idp/ui/public/index.html similarity index 100% rename from extensions/idp/ui/public/index.html rename to services/idp/ui/public/index.html diff --git a/extensions/idp/ui/public/static/logo.svg b/services/idp/ui/public/static/logo.svg similarity index 100% rename from extensions/idp/ui/public/static/logo.svg rename to services/idp/ui/public/static/logo.svg diff --git a/extensions/idp/ui/src/Main.js b/services/idp/ui/src/Main.js similarity index 100% rename from extensions/idp/ui/src/Main.js rename to services/idp/ui/src/Main.js diff --git a/extensions/idp/ui/src/Main.test.js b/services/idp/ui/src/Main.test.js similarity index 100% rename from extensions/idp/ui/src/Main.test.js rename to services/idp/ui/src/Main.test.js diff --git a/extensions/idp/ui/src/Routes.js b/services/idp/ui/src/Routes.js similarity index 100% rename from extensions/idp/ui/src/Routes.js rename to services/idp/ui/src/Routes.js diff --git a/extensions/idp/ui/src/actions/common.js b/services/idp/ui/src/actions/common.js similarity index 100% rename from extensions/idp/ui/src/actions/common.js rename to services/idp/ui/src/actions/common.js diff --git a/extensions/idp/ui/src/actions/login.js b/services/idp/ui/src/actions/login.js similarity index 100% rename from extensions/idp/ui/src/actions/login.js rename to services/idp/ui/src/actions/login.js diff --git a/extensions/idp/ui/src/actions/types.js b/services/idp/ui/src/actions/types.js similarity index 100% rename from extensions/idp/ui/src/actions/types.js rename to services/idp/ui/src/actions/types.js diff --git a/extensions/idp/ui/src/actions/utils.js b/services/idp/ui/src/actions/utils.js similarity index 100% rename from extensions/idp/ui/src/actions/utils.js rename to services/idp/ui/src/actions/utils.js diff --git a/extensions/idp/ui/src/app.css b/services/idp/ui/src/app.css similarity index 100% rename from extensions/idp/ui/src/app.css rename to services/idp/ui/src/app.css diff --git a/extensions/idp/ui/src/app.js b/services/idp/ui/src/app.js similarity index 100% rename from extensions/idp/ui/src/app.js rename to services/idp/ui/src/app.js diff --git a/extensions/idp/ui/src/components/ClientDisplayName.js b/services/idp/ui/src/components/ClientDisplayName.js similarity index 100% rename from extensions/idp/ui/src/components/ClientDisplayName.js rename to services/idp/ui/src/components/ClientDisplayName.js diff --git a/extensions/idp/ui/src/components/Loading.js b/services/idp/ui/src/components/Loading.js similarity index 100% rename from extensions/idp/ui/src/components/Loading.js rename to services/idp/ui/src/components/Loading.js diff --git a/extensions/idp/ui/src/components/PrivateRoute.js b/services/idp/ui/src/components/PrivateRoute.js similarity index 100% rename from extensions/idp/ui/src/components/PrivateRoute.js rename to services/idp/ui/src/components/PrivateRoute.js diff --git a/extensions/idp/ui/src/components/RedirectWithQuery.js b/services/idp/ui/src/components/RedirectWithQuery.js similarity index 100% rename from extensions/idp/ui/src/components/RedirectWithQuery.js rename to services/idp/ui/src/components/RedirectWithQuery.js diff --git a/extensions/idp/ui/src/components/ResponsiveScreen.js b/services/idp/ui/src/components/ResponsiveScreen.js similarity index 100% rename from extensions/idp/ui/src/components/ResponsiveScreen.js rename to services/idp/ui/src/components/ResponsiveScreen.js diff --git a/extensions/idp/ui/src/components/ScopesList.js b/services/idp/ui/src/components/ScopesList.js similarity index 100% rename from extensions/idp/ui/src/components/ScopesList.js rename to services/idp/ui/src/components/ScopesList.js diff --git a/extensions/idp/ui/src/components/TextInput.js b/services/idp/ui/src/components/TextInput.js similarity index 100% rename from extensions/idp/ui/src/components/TextInput.js rename to services/idp/ui/src/components/TextInput.js diff --git a/extensions/idp/ui/src/containers/Goodbye/Goodbyescreen.js b/services/idp/ui/src/containers/Goodbye/Goodbyescreen.js similarity index 100% rename from extensions/idp/ui/src/containers/Goodbye/Goodbyescreen.js rename to services/idp/ui/src/containers/Goodbye/Goodbyescreen.js diff --git a/extensions/idp/ui/src/containers/Goodbye/index.js b/services/idp/ui/src/containers/Goodbye/index.js similarity index 100% rename from extensions/idp/ui/src/containers/Goodbye/index.js rename to services/idp/ui/src/containers/Goodbye/index.js diff --git a/extensions/idp/ui/src/containers/Login/Chooseaccount.js b/services/idp/ui/src/containers/Login/Chooseaccount.js similarity index 100% rename from extensions/idp/ui/src/containers/Login/Chooseaccount.js rename to services/idp/ui/src/containers/Login/Chooseaccount.js diff --git a/extensions/idp/ui/src/containers/Login/Consent.js b/services/idp/ui/src/containers/Login/Consent.js similarity index 100% rename from extensions/idp/ui/src/containers/Login/Consent.js rename to services/idp/ui/src/containers/Login/Consent.js diff --git a/extensions/idp/ui/src/containers/Login/Login.js b/services/idp/ui/src/containers/Login/Login.js similarity index 100% rename from extensions/idp/ui/src/containers/Login/Login.js rename to services/idp/ui/src/containers/Login/Login.js diff --git a/extensions/idp/ui/src/containers/Login/Loginscreen.js b/services/idp/ui/src/containers/Login/Loginscreen.js similarity index 100% rename from extensions/idp/ui/src/containers/Login/Loginscreen.js rename to services/idp/ui/src/containers/Login/Loginscreen.js diff --git a/extensions/idp/ui/src/containers/Login/index.js b/services/idp/ui/src/containers/Login/index.js similarity index 100% rename from extensions/idp/ui/src/containers/Login/index.js rename to services/idp/ui/src/containers/Login/index.js diff --git a/extensions/idp/ui/src/containers/Welcome/Welcomescreen.js b/services/idp/ui/src/containers/Welcome/Welcomescreen.js similarity index 100% rename from extensions/idp/ui/src/containers/Welcome/Welcomescreen.js rename to services/idp/ui/src/containers/Welcome/Welcomescreen.js diff --git a/extensions/idp/ui/src/containers/Welcome/index.js b/services/idp/ui/src/containers/Welcome/index.js similarity index 100% rename from extensions/idp/ui/src/containers/Welcome/index.js rename to services/idp/ui/src/containers/Welcome/index.js diff --git a/extensions/idp/ui/src/errors/index.js b/services/idp/ui/src/errors/index.js similarity index 100% rename from extensions/idp/ui/src/errors/index.js rename to services/idp/ui/src/errors/index.js diff --git a/extensions/idp/ui/src/images/background.jpg b/services/idp/ui/src/images/background.jpg similarity index 100% rename from extensions/idp/ui/src/images/background.jpg rename to services/idp/ui/src/images/background.jpg diff --git a/extensions/idp/ui/src/index.js b/services/idp/ui/src/index.js similarity index 100% rename from extensions/idp/ui/src/index.js rename to services/idp/ui/src/index.js diff --git a/extensions/idp/ui/src/locales/de.json b/services/idp/ui/src/locales/de.json similarity index 100% rename from extensions/idp/ui/src/locales/de.json rename to services/idp/ui/src/locales/de.json diff --git a/extensions/idp/ui/src/locales/es.json b/services/idp/ui/src/locales/es.json similarity index 100% rename from extensions/idp/ui/src/locales/es.json rename to services/idp/ui/src/locales/es.json diff --git a/extensions/idp/ui/src/locales/fr.json b/services/idp/ui/src/locales/fr.json similarity index 100% rename from extensions/idp/ui/src/locales/fr.json rename to services/idp/ui/src/locales/fr.json diff --git a/extensions/idp/ui/src/locales/hi.json b/services/idp/ui/src/locales/hi.json similarity index 100% rename from extensions/idp/ui/src/locales/hi.json rename to services/idp/ui/src/locales/hi.json diff --git a/extensions/idp/ui/src/locales/hr.json b/services/idp/ui/src/locales/hr.json similarity index 100% rename from extensions/idp/ui/src/locales/hr.json rename to services/idp/ui/src/locales/hr.json diff --git a/extensions/idp/ui/src/locales/hu.json b/services/idp/ui/src/locales/hu.json similarity index 100% rename from extensions/idp/ui/src/locales/hu.json rename to services/idp/ui/src/locales/hu.json diff --git a/extensions/idp/ui/src/locales/index.js b/services/idp/ui/src/locales/index.js similarity index 100% rename from extensions/idp/ui/src/locales/index.js rename to services/idp/ui/src/locales/index.js diff --git a/extensions/idp/ui/src/locales/is.json b/services/idp/ui/src/locales/is.json similarity index 100% rename from extensions/idp/ui/src/locales/is.json rename to services/idp/ui/src/locales/is.json diff --git a/extensions/idp/ui/src/locales/it.json b/services/idp/ui/src/locales/it.json similarity index 100% rename from extensions/idp/ui/src/locales/it.json rename to services/idp/ui/src/locales/it.json diff --git a/extensions/idp/ui/src/locales/ja.json b/services/idp/ui/src/locales/ja.json similarity index 100% rename from extensions/idp/ui/src/locales/ja.json rename to services/idp/ui/src/locales/ja.json diff --git a/extensions/idp/ui/src/locales/nb.json b/services/idp/ui/src/locales/nb.json similarity index 100% rename from extensions/idp/ui/src/locales/nb.json rename to services/idp/ui/src/locales/nb.json diff --git a/extensions/idp/ui/src/locales/nl.json b/services/idp/ui/src/locales/nl.json similarity index 100% rename from extensions/idp/ui/src/locales/nl.json rename to services/idp/ui/src/locales/nl.json diff --git a/extensions/idp/ui/src/locales/pl.json b/services/idp/ui/src/locales/pl.json similarity index 100% rename from extensions/idp/ui/src/locales/pl.json rename to services/idp/ui/src/locales/pl.json diff --git a/extensions/idp/ui/src/locales/pt_PT.json b/services/idp/ui/src/locales/pt_PT.json similarity index 100% rename from extensions/idp/ui/src/locales/pt_PT.json rename to services/idp/ui/src/locales/pt_PT.json diff --git a/extensions/idp/ui/src/locales/ru.json b/services/idp/ui/src/locales/ru.json similarity index 100% rename from extensions/idp/ui/src/locales/ru.json rename to services/idp/ui/src/locales/ru.json diff --git a/extensions/idp/ui/src/locales/sl.json b/services/idp/ui/src/locales/sl.json similarity index 100% rename from extensions/idp/ui/src/locales/sl.json rename to services/idp/ui/src/locales/sl.json diff --git a/extensions/idp/ui/src/models/hello.js b/services/idp/ui/src/models/hello.js similarity index 100% rename from extensions/idp/ui/src/models/hello.js rename to services/idp/ui/src/models/hello.js diff --git a/extensions/idp/ui/src/reducers/common.js b/services/idp/ui/src/reducers/common.js similarity index 100% rename from extensions/idp/ui/src/reducers/common.js rename to services/idp/ui/src/reducers/common.js diff --git a/extensions/idp/ui/src/reducers/index.js b/services/idp/ui/src/reducers/index.js similarity index 100% rename from extensions/idp/ui/src/reducers/index.js rename to services/idp/ui/src/reducers/index.js diff --git a/extensions/idp/ui/src/reducers/login.js b/services/idp/ui/src/reducers/login.js similarity index 100% rename from extensions/idp/ui/src/reducers/login.js rename to services/idp/ui/src/reducers/login.js diff --git a/extensions/idp/ui/src/store.js b/services/idp/ui/src/store.js similarity index 100% rename from extensions/idp/ui/src/store.js rename to services/idp/ui/src/store.js diff --git a/extensions/idp/ui/src/utils.js b/services/idp/ui/src/utils.js similarity index 100% rename from extensions/idp/ui/src/utils.js rename to services/idp/ui/src/utils.js diff --git a/extensions/idp/ui/src/version.js b/services/idp/ui/src/version.js similarity index 100% rename from extensions/idp/ui/src/version.js rename to services/idp/ui/src/version.js diff --git a/extensions/idp/ui_config/env.js b/services/idp/ui_config/env.js similarity index 100% rename from extensions/idp/ui_config/env.js rename to services/idp/ui_config/env.js diff --git a/extensions/idp/ui_config/jest/cssTransform.js b/services/idp/ui_config/jest/cssTransform.js similarity index 100% rename from extensions/idp/ui_config/jest/cssTransform.js rename to services/idp/ui_config/jest/cssTransform.js diff --git a/extensions/idp/ui_config/jest/fileTransform.js b/services/idp/ui_config/jest/fileTransform.js similarity index 100% rename from extensions/idp/ui_config/jest/fileTransform.js rename to services/idp/ui_config/jest/fileTransform.js diff --git a/extensions/idp/ui_config/modules.js b/services/idp/ui_config/modules.js similarity index 100% rename from extensions/idp/ui_config/modules.js rename to services/idp/ui_config/modules.js diff --git a/extensions/idp/ui_config/paths.js b/services/idp/ui_config/paths.js similarity index 100% rename from extensions/idp/ui_config/paths.js rename to services/idp/ui_config/paths.js diff --git a/extensions/idp/ui_config/pnpTs.js b/services/idp/ui_config/pnpTs.js similarity index 100% rename from extensions/idp/ui_config/pnpTs.js rename to services/idp/ui_config/pnpTs.js diff --git a/extensions/idp/ui_config/webpack.config.js b/services/idp/ui_config/webpack.config.js similarity index 100% rename from extensions/idp/ui_config/webpack.config.js rename to services/idp/ui_config/webpack.config.js diff --git a/extensions/idp/ui_config/webpackDevServer.config.js b/services/idp/ui_config/webpackDevServer.config.js similarity index 100% rename from extensions/idp/ui_config/webpackDevServer.config.js rename to services/idp/ui_config/webpackDevServer.config.js diff --git a/extensions/idp/yarn.lock b/services/idp/yarn.lock similarity index 100% rename from extensions/idp/yarn.lock rename to services/idp/yarn.lock diff --git a/extensions/nats/Makefile b/services/nats/Makefile similarity index 100% rename from extensions/nats/Makefile rename to services/nats/Makefile diff --git a/services/nats/cmd/nats/main.go b/services/nats/cmd/nats/main.go new file mode 100644 index 00000000000..4b5803ac78c --- /dev/null +++ b/services/nats/cmd/nats/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/nats/pkg/command" + "github.com/owncloud/ocis/v2/services/nats/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/services/nats/pkg/command/health.go b/services/nats/pkg/command/health.go new file mode 100644 index 00000000000..75e5ab838e7 --- /dev/null +++ b/services/nats/pkg/command/health.go @@ -0,0 +1,18 @@ +package command + +import ( + "github.com/owncloud/ocis/v2/services/nats/pkg/config" + "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", + Action: func(c *cli.Context) error { + // Not implemented + return nil + }, + } +} diff --git a/services/nats/pkg/command/root.go b/services/nats/pkg/command/root.go new file mode 100644 index 00000000000..b5167462842 --- /dev/null +++ b/services/nats/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/nats/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 nats command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "nats", + Usage: "starts nats server", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the nats command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new nats.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.Nats.Commons = cfg.Commons + return SutureService{ + cfg: cfg.Nats, + } +} + +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/services/nats/pkg/command/server.go b/services/nats/pkg/command/server.go new file mode 100644 index 00000000000..5bed4387e9b --- /dev/null +++ b/services/nats/pkg/command/server.go @@ -0,0 +1,76 @@ +package command + +import ( + "context" + "fmt" + "os" + + "github.com/oklog/run" + + "github.com/owncloud/ocis/v2/services/nats/pkg/config" + "github.com/owncloud/ocis/v2/services/nats/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/nats/pkg/logging" + "github.com/owncloud/ocis/v2/services/nats/pkg/server/nats" + "github.com/urfave/cli/v2" +) + +// Server is the entrypoint 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) + os.Exit(1) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + 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() + + natsServer, err := nats.NewNATSServer( + ctx, + logging.NewLogWrapper(logger), + nats.Host(cfg.Nats.Host), + nats.Port(cfg.Nats.Port), + nats.ClusterID(cfg.Nats.ClusterID), + nats.StoreDir(cfg.Nats.StoreDir), + ) + if err != nil { + return err + } + + gr.Add(func() error { + err := make(chan error) + select { + case <-ctx.Done(): + return nil + case err <- natsServer.ListenAndServe(): + return <-err + } + + }, func(_ error) { + logger.Info(). + Msg("Shutting down server") + + natsServer.Shutdown() + cancel() + }) + + return gr.Run() + }, + } +} diff --git a/services/nats/pkg/command/version.go b/services/nats/pkg/command/version.go new file mode 100644 index 00000000000..cedb412b1e8 --- /dev/null +++ b/services/nats/pkg/command/version.go @@ -0,0 +1,19 @@ +package command + +import ( + "github.com/owncloud/ocis/v2/services/nats/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 { + // not implemented + return nil + }, + } +} diff --git a/extensions/nats/pkg/config/config.go b/services/nats/pkg/config/config.go similarity index 100% rename from extensions/nats/pkg/config/config.go rename to services/nats/pkg/config/config.go diff --git a/extensions/nats/pkg/config/debug.go b/services/nats/pkg/config/debug.go similarity index 100% rename from extensions/nats/pkg/config/debug.go rename to services/nats/pkg/config/debug.go diff --git a/services/nats/pkg/config/defaults/defaultconfig.go b/services/nats/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..d130a8ac2ae --- /dev/null +++ b/services/nats/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,53 @@ +package defaults + +import ( + "path" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" + "github.com/owncloud/ocis/v2/services/nats/pkg/config" +) + +// NOTE: Most of this configuration is not needed to keep it as simple as possible +// TODO: Clean up unneeded configuration + +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:9234", + }, + Service: config.Service{ + Name: "nats", + }, + Nats: config.Nats{ + Host: "127.0.0.1", + Port: 9233, + ClusterID: "ocis-cluster", + StoreDir: path.Join(defaults.BaseDataPath(), "nats"), + }, + } +} + +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{} + } +} + +func Sanitize(cfg *config.Config) { + // nothing to sanitize here atm +} diff --git a/extensions/nats/pkg/config/log.go b/services/nats/pkg/config/log.go similarity index 100% rename from extensions/nats/pkg/config/log.go rename to services/nats/pkg/config/log.go diff --git a/services/nats/pkg/config/parser/parse.go b/services/nats/pkg/config/parser/parse.go new file mode 100644 index 00000000000..b741cf3a091 --- /dev/null +++ b/services/nats/pkg/config/parser/parse.go @@ -0,0 +1,37 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/nats/pkg/config" + "github.com/owncloud/ocis/v2/services/nats/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + return nil +} diff --git a/extensions/nats/pkg/config/service.go b/services/nats/pkg/config/service.go similarity index 100% rename from extensions/nats/pkg/config/service.go rename to services/nats/pkg/config/service.go diff --git a/services/nats/pkg/logging/logging.go b/services/nats/pkg/logging/logging.go new file mode 100644 index 00000000000..230b1d7f794 --- /dev/null +++ b/services/nats/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/nats/pkg/config" +) + +// 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/nats/pkg/logging/nats.go b/services/nats/pkg/logging/nats.go similarity index 100% rename from extensions/nats/pkg/logging/nats.go rename to services/nats/pkg/logging/nats.go diff --git a/extensions/nats/pkg/server/nats/nats.go b/services/nats/pkg/server/nats/nats.go similarity index 100% rename from extensions/nats/pkg/server/nats/nats.go rename to services/nats/pkg/server/nats/nats.go diff --git a/extensions/nats/pkg/server/nats/options.go b/services/nats/pkg/server/nats/options.go similarity index 100% rename from extensions/nats/pkg/server/nats/options.go rename to services/nats/pkg/server/nats/options.go diff --git a/extensions/notifications/Makefile b/services/notifications/Makefile similarity index 100% rename from extensions/notifications/Makefile rename to services/notifications/Makefile diff --git a/services/notifications/cmd/notifications/main.go b/services/notifications/cmd/notifications/main.go new file mode 100644 index 00000000000..5d5752af388 --- /dev/null +++ b/services/notifications/cmd/notifications/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/notifications/pkg/command" + "github.com/owncloud/ocis/v2/services/notifications/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/notifications/pkg/channels/channels.go b/services/notifications/pkg/channels/channels.go similarity index 97% rename from extensions/notifications/pkg/channels/channels.go rename to services/notifications/pkg/channels/channels.go index 62881dd750a..e058e2db290 100644 --- a/extensions/notifications/pkg/channels/channels.go +++ b/services/notifications/pkg/channels/channels.go @@ -9,8 +9,8 @@ import ( groups "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" - "github.com/owncloud/ocis/v2/extensions/notifications/pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/notifications/pkg/config" "github.com/pkg/errors" ) diff --git a/services/notifications/pkg/command/health.go b/services/notifications/pkg/command/health.go new file mode 100644 index 00000000000..bcc1df71abb --- /dev/null +++ b/services/notifications/pkg/command/health.go @@ -0,0 +1,18 @@ +package command + +import ( + "github.com/owncloud/ocis/v2/services/notifications/pkg/config" + "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", + Action: func(c *cli.Context) error { + // Not implemented + return nil + }, + } +} diff --git a/services/notifications/pkg/command/root.go b/services/notifications/pkg/command/root.go new file mode 100644 index 00000000000..17d52afe638 --- /dev/null +++ b/services/notifications/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/notifications/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 notifications command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "notifications", + Usage: "starts notifications service", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the notifications command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new notifications.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.Notifications.Commons = cfg.Commons + return SutureService{ + cfg: cfg.Notifications, + } +} + +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/services/notifications/pkg/command/server.go b/services/notifications/pkg/command/server.go new file mode 100644 index 00000000000..91f43b1b93d --- /dev/null +++ b/services/notifications/pkg/command/server.go @@ -0,0 +1,59 @@ +package command + +import ( + "fmt" + "os" + + "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/events/server" + "github.com/go-micro/plugins/v4/events/natsjs" + "github.com/owncloud/ocis/v2/services/notifications/pkg/channels" + "github.com/owncloud/ocis/v2/services/notifications/pkg/config" + "github.com/owncloud/ocis/v2/services/notifications/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/notifications/pkg/logging" + "github.com/owncloud/ocis/v2/services/notifications/pkg/service" + "github.com/urfave/cli/v2" +) + +// Server is the entrypoint 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) + os.Exit(1) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + evs := []events.Unmarshaller{ + events.ShareCreated{}, + } + + evtsCfg := cfg.Notifications.Events + client, err := server.NewNatsStream( + natsjs.Address(evtsCfg.Endpoint), + natsjs.ClusterID(evtsCfg.Cluster), + ) + if err != nil { + return err + } + evts, err := events.Consume(client, evtsCfg.ConsumerGroup, evs...) + if err != nil { + return err + } + channel, err := channels.NewMailChannel(*cfg, logger) + if err != nil { + return err + } + svc := service.NewEventsNotifier(evts, channel, logger) + return svc.Run() + }, + } +} diff --git a/services/notifications/pkg/command/version.go b/services/notifications/pkg/command/version.go new file mode 100644 index 00000000000..153f65af383 --- /dev/null +++ b/services/notifications/pkg/command/version.go @@ -0,0 +1,19 @@ +package command + +import ( + "github.com/owncloud/ocis/v2/services/notifications/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 { + // not implemented + return nil + }, + } +} diff --git a/extensions/notifications/pkg/config/config.go b/services/notifications/pkg/config/config.go similarity index 100% rename from extensions/notifications/pkg/config/config.go rename to services/notifications/pkg/config/config.go diff --git a/extensions/notifications/pkg/config/debug.go b/services/notifications/pkg/config/debug.go similarity index 100% rename from extensions/notifications/pkg/config/debug.go rename to services/notifications/pkg/config/debug.go diff --git a/services/notifications/pkg/config/defaults/defaultconfig.go b/services/notifications/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..c01c4d2f62a --- /dev/null +++ b/services/notifications/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,61 @@ +package defaults + +import ( + "github.com/owncloud/ocis/v2/services/notifications/pkg/config" +) + +func FullDefaultConfig() *config.Config { + cfg := DefaultConfig() + EnsureDefaults(cfg) + Sanitize(cfg) + return cfg +} + +// NOTE: Most of this configuration is not needed to keep it as simple as possible +// TODO: Clean up unneeded configuration + +func DefaultConfig() *config.Config { + return &config.Config{ + Debug: config.Debug{ + Addr: "127.0.0.1:9174", + }, + Service: config.Service{ + Name: "notifications", + }, + Notifications: config.Notifications{ + SMTP: config.SMTP{ + Host: "127.0.0.1", + Port: "1025", + Sender: "noreply@example.com", + }, + Events: config.Events{ + Endpoint: "127.0.0.1:9233", + Cluster: "ocis-cluster", + ConsumerGroup: "notifications", + }, + RevaGateway: "127.0.0.1:9142", + }, + } +} + +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{} + } + + if cfg.Notifications.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" { + cfg.Notifications.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey + } +} + +func Sanitize(cfg *config.Config) { + // nothing to sanitize here atm +} diff --git a/extensions/notifications/pkg/config/log.go b/services/notifications/pkg/config/log.go similarity index 100% rename from extensions/notifications/pkg/config/log.go rename to services/notifications/pkg/config/log.go diff --git a/services/notifications/pkg/config/parser/parse.go b/services/notifications/pkg/config/parser/parse.go new file mode 100644 index 00000000000..72ccfb22f99 --- /dev/null +++ b/services/notifications/pkg/config/parser/parse.go @@ -0,0 +1,42 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/services/notifications/pkg/config" + "github.com/owncloud/ocis/v2/services/notifications/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + if cfg.Notifications.MachineAuthAPIKey == "" { + return shared.MissingMachineAuthApiKeyError(cfg.Service.Name) + } + + return nil +} diff --git a/extensions/notifications/pkg/config/service.go b/services/notifications/pkg/config/service.go similarity index 100% rename from extensions/notifications/pkg/config/service.go rename to services/notifications/pkg/config/service.go diff --git a/services/notifications/pkg/logging/logging.go b/services/notifications/pkg/logging/logging.go new file mode 100644 index 00000000000..982389bcf50 --- /dev/null +++ b/services/notifications/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/notifications/pkg/config" +) + +// 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/services/notifications/pkg/service/service.go b/services/notifications/pkg/service/service.go new file mode 100644 index 00000000000..d6c8f970517 --- /dev/null +++ b/services/notifications/pkg/service/service.go @@ -0,0 +1,64 @@ +package service + +import ( + "os" + "os/signal" + "syscall" + + "github.com/cs3org/reva/v2/pkg/events" + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/notifications/pkg/channels" +) + +type Service interface { + Run() error +} + +func NewEventsNotifier(events <-chan interface{}, channel channels.Channel, logger log.Logger) Service { + return eventsNotifier{ + logger: logger, + channel: channel, + events: events, + signals: make(chan os.Signal, 1), + } +} + +type eventsNotifier struct { + logger log.Logger + channel channels.Channel + events <-chan interface{} + signals chan os.Signal +} + +func (s eventsNotifier) Run() error { + signal.Notify(s.signals, syscall.SIGINT, syscall.SIGTERM) + s.logger.Debug(). + Msg("eventsNotifier started") + for { + select { + case evt := <-s.events: + go func() { + switch e := evt.(type) { + case events.ShareCreated: + msg := "You got a share!" + var err error + if e.GranteeUserID != nil { + err = s.channel.SendMessage([]string{e.GranteeUserID.OpaqueId}, msg) + } else if e.GranteeGroupID != nil { + err = s.channel.SendMessageToGroup(e.GranteeGroupID, msg) + } + if err != nil { + s.logger.Error(). + Err(err). + Str("event", "ShareCreated"). + Msg("failed to send a message") + } + } + }() + case <-s.signals: + s.logger.Debug(). + Msg("eventsNotifier stopped") + return nil + } + } +} diff --git a/extensions/ocdav/Makefile b/services/ocdav/Makefile similarity index 100% rename from extensions/ocdav/Makefile rename to services/ocdav/Makefile diff --git a/services/ocdav/cmd/ocdav/main.go b/services/ocdav/cmd/ocdav/main.go new file mode 100644 index 00000000000..9875027bdb5 --- /dev/null +++ b/services/ocdav/cmd/ocdav/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/ocdav/pkg/command" + "github.com/owncloud/ocis/v2/services/ocdav/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/services/ocdav/pkg/command/health.go b/services/ocdav/pkg/command/health.go new file mode 100644 index 00000000000..8724a43adbe --- /dev/null +++ b/services/ocdav/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/ocdav/pkg/config" + "github.com/owncloud/ocis/v2/services/ocdav/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/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/services/ocdav/pkg/command/root.go b/services/ocdav/pkg/command/root.go new file mode 100644 index 00000000000..b96cc01813a --- /dev/null +++ b/services/ocdav/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/ocdav/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/services/ocdav/pkg/command/server.go b/services/ocdav/pkg/command/server.go new file mode 100644 index 00000000000..7af4075539e --- /dev/null +++ b/services/ocdav/pkg/command/server.go @@ -0,0 +1,117 @@ +package command + +import ( + "context" + "fmt" + "os" + + "github.com/cs3org/reva/v2/pkg/micro/ocdav" + "github.com/oklog/run" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/ocdav/pkg/config" + "github.com/owncloud/ocis/v2/services/ocdav/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/ocdav/pkg/logging" + "github.com/owncloud/ocis/v2/services/ocdav/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/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) + os.Exit(1) + } + 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 { + + opts := []ocdav.Option{ + ocdav.Name(cfg.HTTP.Namespace + "." + cfg.Service.Name), + ocdav.Version(version.GetString()), + 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.ProductName(cfg.Status.ProductName), + ocdav.ProductVersion(cfg.Status.ProductVersion), + ocdav.Product(cfg.Status.Product), + ocdav.Version(cfg.Status.Version), + ocdav.VersionString(cfg.Status.VersionString), + ocdav.Edition(cfg.Status.Edition), + // ocdav.FavoriteManager() // FIXME needs a proper persistence implementation https://github.com/owncloud/ocis/issues/1228 + // ocdav.LockSystem(), // will default to the CS3 lock system + // ocdav.TLSConfig() // tls config for the http server + } + + if cfg.Tracing.Enabled { + opts = append(opts, ocdav.Tracing(cfg.Tracing.Endpoint, cfg.Tracing.Collector)) + } + + s, err := ocdav.Service(opts...) + 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) { + _ = debugServer.Shutdown(ctx) + 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/services/ocdav/pkg/command/version.go b/services/ocdav/pkg/command/version.go new file mode 100644 index 00000000000..71ab3f90192 --- /dev/null +++ b/services/ocdav/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/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.GetString()) + 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/services/ocdav/pkg/config/config.go similarity index 100% rename from extensions/ocdav/pkg/config/config.go rename to services/ocdav/pkg/config/config.go diff --git a/services/ocdav/pkg/config/defaults/defaultconfig.go b/services/ocdav/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..804e565284e --- /dev/null +++ b/services/ocdav/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,100 @@ +package defaults + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/ocdav/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:9163", + Token: "", + Pprof: false, + Zpages: false, + }, + HTTP: config.HTTPConfig{ + Addr: "127.0.0.1:0", // :0 to pick any free local port + Namespace: "com.owncloud.web", + Protocol: "tcp", + Prefix: "", + }, + Service: config.Service{ + Name: "ocdav", + }, + Reva: &config.Reva{ + Address: "127.0.0.1:9142", + }, + WebdavNamespace: "/users/{{.Id.OpaqueId}}", + FilesNamespace: "/users/{{.Id.OpaqueId}}", + SharesNamespace: "/Shares", + PublicURL: "https://localhost:9200", + Insecure: false, + Timeout: 84300, + Middleware: config.Middleware{ + Auth: config.Auth{ + CredentialsByUserAgent: map[string]string{}, + }, + }, + Status: config.Status{ + Version: version.Legacy, + VersionString: version.LegacyString, + ProductVersion: version.GetString(), + Product: "Infinite Scale", + ProductName: "Infinite Scale", + Edition: "Community", + }, + } +} + +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/services/ocdav/pkg/config/parser/parse.go b/services/ocdav/pkg/config/parser/parse.go new file mode 100644 index 00000000000..6a1de96b28b --- /dev/null +++ b/services/ocdav/pkg/config/parser/parse.go @@ -0,0 +1,42 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/services/ocdav/pkg/config" + "github.com/owncloud/ocis/v2/services/ocdav/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + if cfg.TokenManager.JWTSecret == "" { + return shared.MissingJWTTokenError(cfg.Service.Name) + } + + return nil +} diff --git a/extensions/ocdav/pkg/config/reva.go b/services/ocdav/pkg/config/reva.go similarity index 100% rename from extensions/ocdav/pkg/config/reva.go rename to services/ocdav/pkg/config/reva.go diff --git a/services/ocdav/pkg/logging/logging.go b/services/ocdav/pkg/logging/logging.go new file mode 100644 index 00000000000..f5341b839fa --- /dev/null +++ b/services/ocdav/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/ocdav/pkg/config" +) + +// 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/services/ocdav/pkg/server/debug/option.go b/services/ocdav/pkg/server/debug/option.go new file mode 100644 index 00000000000..76dc12aa511 --- /dev/null +++ b/services/ocdav/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/ocdav/pkg/config" +) + +// 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/services/ocdav/pkg/server/debug/server.go b/services/ocdav/pkg/server/debug/server.go new file mode 100644 index 00000000000..43f2b2e748f --- /dev/null +++ b/services/ocdav/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/ocdav/pkg/config" +) + +// 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.GetString()), + 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/services/ocdav/pkg/tracing/tracing.go b/services/ocdav/pkg/tracing/tracing.go new file mode 100644 index 00000000000..b9f661c8619 --- /dev/null +++ b/services/ocdav/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/ocdav/pkg/config" + "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/.dockerignore b/services/ocs/.dockerignore similarity index 100% rename from extensions/ocs/.dockerignore rename to services/ocs/.dockerignore diff --git a/extensions/ocs/Makefile b/services/ocs/Makefile similarity index 100% rename from extensions/ocs/Makefile rename to services/ocs/Makefile diff --git a/services/ocs/cmd/ocs/main.go b/services/ocs/cmd/ocs/main.go new file mode 100644 index 00000000000..a2b9021ead9 --- /dev/null +++ b/services/ocs/cmd/ocs/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/ocs/pkg/command" + "github.com/owncloud/ocis/v2/services/ocs/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/ocs/config/.gitignore b/services/ocs/config/.gitignore similarity index 100% rename from extensions/ocs/config/.gitignore rename to services/ocs/config/.gitignore diff --git a/extensions/ocs/docker/Dockerfile.linux.amd64 b/services/ocs/docker/Dockerfile.linux.amd64 similarity index 100% rename from extensions/ocs/docker/Dockerfile.linux.amd64 rename to services/ocs/docker/Dockerfile.linux.amd64 diff --git a/extensions/ocs/docker/Dockerfile.linux.arm b/services/ocs/docker/Dockerfile.linux.arm similarity index 100% rename from extensions/ocs/docker/Dockerfile.linux.arm rename to services/ocs/docker/Dockerfile.linux.arm diff --git a/extensions/ocs/docker/Dockerfile.linux.arm64 b/services/ocs/docker/Dockerfile.linux.arm64 similarity index 100% rename from extensions/ocs/docker/Dockerfile.linux.arm64 rename to services/ocs/docker/Dockerfile.linux.arm64 diff --git a/extensions/ocs/docker/manifest.tmpl b/services/ocs/docker/manifest.tmpl similarity index 100% rename from extensions/ocs/docker/manifest.tmpl rename to services/ocs/docker/manifest.tmpl diff --git a/services/ocs/pkg/command/health.go b/services/ocs/pkg/command/health.go new file mode 100644 index 00000000000..197c2e1ca82 --- /dev/null +++ b/services/ocs/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/ocs/pkg/config" + "github.com/owncloud/ocis/v2/services/ocs/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/ocs/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/services/ocs/pkg/command/root.go b/services/ocs/pkg/command/root.go new file mode 100644 index 00000000000..9e3436e0791 --- /dev/null +++ b/services/ocs/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/ocs/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-ocs command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "ocs", + Usage: "Serve OCS 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 ocs command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new ocs.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.OCS.Commons = cfg.Commons + return SutureService{ + cfg: cfg.OCS, + } +} + +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/services/ocs/pkg/command/server.go b/services/ocs/pkg/command/server.go new file mode 100644 index 00000000000..8de4dd160da --- /dev/null +++ b/services/ocs/pkg/command/server.go @@ -0,0 +1,106 @@ +package command + +import ( + "context" + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/ocs/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/ocs/pkg/logging" + "github.com/owncloud/ocis/v2/services/ocs/pkg/tracing" + + "github.com/oklog/run" + "github.com/owncloud/ocis/v2/services/ocs/pkg/config" + "github.com/owncloud/ocis/v2/services/ocs/pkg/metrics" + "github.com/owncloud/ocis/v2/services/ocs/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/ocs/pkg/server/http" + "github.com/urfave/cli/v2" +) + +// Server is the entrypoint 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) + os.Exit(1) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg) + if err != nil { + return err + } + + var ( + gr = run.Group{} + ctx, cancel = func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() + metrics = metrics.New() + ) + + defer cancel() + + metrics.BuildInfo.WithLabelValues(version.GetString()).Set(1) + + { + server, err := http.Server( + http.Logger(logger), + http.Context(ctx), + http.Config(cfg), + http.Metrics(metrics), + ) + + if err != nil { + logger.Info(). + Err(err). + Str("transport", "http"). + Msg("Failed to initialize server") + + return err + } + + gr.Add(func() error { + return server.Run() + }, func(_ error) { + logger.Info(). + Str("transport", "http"). + Msg("Shutting down server") + + cancel() + }) + } + + { + server, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("transport", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(server.ListenAndServe, func(_ error) { + _ = server.Shutdown(ctx) + cancel() + }) + } + + return gr.Run() + }, + } +} diff --git a/services/ocs/pkg/command/version.go b/services/ocs/pkg/command/version.go new file mode 100644 index 00000000000..4d3bf8bba22 --- /dev/null +++ b/services/ocs/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/ocs/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.GetString()) + 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/ocs/pkg/config/config.go b/services/ocs/pkg/config/config.go similarity index 100% rename from extensions/ocs/pkg/config/config.go rename to services/ocs/pkg/config/config.go diff --git a/extensions/ocs/pkg/config/debug.go b/services/ocs/pkg/config/debug.go similarity index 100% rename from extensions/ocs/pkg/config/debug.go rename to services/ocs/pkg/config/debug.go diff --git a/services/ocs/pkg/config/defaults/defaultconfig.go b/services/ocs/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..d2ce6df71b2 --- /dev/null +++ b/services/ocs/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,98 @@ +package defaults + +import ( + "strings" + + "github.com/owncloud/ocis/v2/services/ocs/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:9114", + Token: "", + Pprof: false, + Zpages: false, + }, + HTTP: config.HTTP{ + Addr: "127.0.0.1:9110", + Root: "/ocs", + Namespace: "com.owncloud.web", + CORS: config.CORS{ + AllowedOrigins: []string{"*"}, + AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, + AllowedHeaders: []string{"Authorization", "Origin", "Content-Type", "Accept", "X-Requested-With"}, + AllowCredentials: true, + }, + }, + Service: config.Service{ + Name: "ocs", + }, + AccountBackend: "cs3", + Reva: &config.Reva{ + Address: "127.0.0.1:9142", + }, + IdentityManagement: config.IdentityManagement{ + Address: "https://localhost:9200", + }, + } +} + +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{} + } + + if cfg.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" { + cfg.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey + } +} + +func Sanitize(cfg *config.Config) { + // sanitize config + if cfg.HTTP.Root != "/" { + cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") + } +} diff --git a/extensions/ocs/pkg/config/http.go b/services/ocs/pkg/config/http.go similarity index 100% rename from extensions/ocs/pkg/config/http.go rename to services/ocs/pkg/config/http.go diff --git a/extensions/ocs/pkg/config/log.go b/services/ocs/pkg/config/log.go similarity index 100% rename from extensions/ocs/pkg/config/log.go rename to services/ocs/pkg/config/log.go diff --git a/services/ocs/pkg/config/parser/parse.go b/services/ocs/pkg/config/parser/parse.go new file mode 100644 index 00000000000..ecb6b0e184b --- /dev/null +++ b/services/ocs/pkg/config/parser/parse.go @@ -0,0 +1,47 @@ +package parser + +import ( + "errors" + + "github.com/owncloud/ocis/v2/services/ocs/pkg/config" + "github.com/owncloud/ocis/v2/services/ocs/pkg/config/defaults" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + if cfg.TokenManager.JWTSecret == "" { + return shared.MissingJWTTokenError(cfg.Service.Name) + } + + if cfg.MachineAuthAPIKey == "" { + return shared.MissingMachineAuthApiKeyError(cfg.Service.Name) + } + + return nil +} diff --git a/extensions/ocs/pkg/config/reva.go b/services/ocs/pkg/config/reva.go similarity index 100% rename from extensions/ocs/pkg/config/reva.go rename to services/ocs/pkg/config/reva.go diff --git a/extensions/ocs/pkg/config/service.go b/services/ocs/pkg/config/service.go similarity index 100% rename from extensions/ocs/pkg/config/service.go rename to services/ocs/pkg/config/service.go diff --git a/extensions/ocs/pkg/config/tracing.go b/services/ocs/pkg/config/tracing.go similarity index 100% rename from extensions/ocs/pkg/config/tracing.go rename to services/ocs/pkg/config/tracing.go diff --git a/services/ocs/pkg/logging/logging.go b/services/ocs/pkg/logging/logging.go new file mode 100644 index 00000000000..9156b2b814a --- /dev/null +++ b/services/ocs/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/ocs/pkg/config" +) + +// 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/ocs/pkg/metrics/metrics.go b/services/ocs/pkg/metrics/metrics.go similarity index 100% rename from extensions/ocs/pkg/metrics/metrics.go rename to services/ocs/pkg/metrics/metrics.go diff --git a/extensions/ocs/pkg/middleware/format.go b/services/ocs/pkg/middleware/format.go similarity index 100% rename from extensions/ocs/pkg/middleware/format.go rename to services/ocs/pkg/middleware/format.go diff --git a/extensions/ocs/pkg/middleware/logtrace.go b/services/ocs/pkg/middleware/logtrace.go similarity index 89% rename from extensions/ocs/pkg/middleware/logtrace.go rename to services/ocs/pkg/middleware/logtrace.go index 7e51bd7f6de..a9444902d96 100644 --- a/extensions/ocs/pkg/middleware/logtrace.go +++ b/services/ocs/pkg/middleware/logtrace.go @@ -3,7 +3,7 @@ package middleware import ( "net/http" - ocstracing "github.com/owncloud/ocis/v2/extensions/ocs/pkg/tracing" + ocstracing "github.com/owncloud/ocis/v2/services/ocs/pkg/tracing" "go.opentelemetry.io/otel/propagation" ) diff --git a/extensions/ocs/pkg/middleware/options.go b/services/ocs/pkg/middleware/options.go similarity index 100% rename from extensions/ocs/pkg/middleware/options.go rename to services/ocs/pkg/middleware/options.go diff --git a/services/ocs/pkg/middleware/requireadmin.go b/services/ocs/pkg/middleware/requireadmin.go new file mode 100644 index 00000000000..87f58eebe09 --- /dev/null +++ b/services/ocs/pkg/middleware/requireadmin.go @@ -0,0 +1,42 @@ +package middleware + +import ( + "net/http" + + "github.com/go-chi/render" + "github.com/owncloud/ocis/v2/ocis-pkg/roles" + "github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/data" + "github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/response" + settings "github.com/owncloud/ocis/v2/services/settings/pkg/service/v0" +) + +// RequireAdmin middleware is used to require the user in context to be an admin / have account management permissions +func RequireAdmin(opts ...Option) func(next http.Handler) http.Handler { + opt := newOptions(opts...) + + mustRender := func(w http.ResponseWriter, r *http.Request, renderer render.Renderer) { + if err := render.Render(w, r, renderer); err != nil { + opt.Logger.Err(err).Msgf("failed to write response for ocs request %s on %s", r.Method, r.URL) + } + } + + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + // get roles from context + roleIDs, ok := roles.ReadRoleIDsFromContext(r.Context()) + if !ok { + mustRender(w, r, response.ErrRender(data.MetaUnauthorized.StatusCode, "Unauthorized")) + return + } + + // check if permission is present in roles of the authenticated account + if opt.RoleManager.FindPermissionByID(r.Context(), roleIDs, settings.AccountManagementPermissionID) != nil { + next.ServeHTTP(w, r) + return + } + + mustRender(w, r, response.ErrRender(data.MetaUnauthorized.StatusCode, "Unauthorized")) + }) + } +} diff --git a/extensions/ocs/pkg/middleware/requireselforadmin.go b/services/ocs/pkg/middleware/requireselforadmin.go similarity index 90% rename from extensions/ocs/pkg/middleware/requireselforadmin.go rename to services/ocs/pkg/middleware/requireselforadmin.go index 38cef448271..51ce63d1e72 100644 --- a/extensions/ocs/pkg/middleware/requireselforadmin.go +++ b/services/ocs/pkg/middleware/requireselforadmin.go @@ -7,11 +7,11 @@ import ( revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/go-chi/chi/v5" "github.com/go-chi/render" - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/service/v0/data" - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/service/v0/response" - settings "github.com/owncloud/ocis/v2/extensions/settings/pkg/service/v0" - settingsService "github.com/owncloud/ocis/v2/extensions/settings/pkg/service/v0" "github.com/owncloud/ocis/v2/ocis-pkg/roles" + "github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/data" + "github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/response" + settings "github.com/owncloud/ocis/v2/services/settings/pkg/service/v0" + settingsService "github.com/owncloud/ocis/v2/services/settings/pkg/service/v0" ) // RequireSelfOrAdmin middleware is used to require the requesting user to be an admin or the requested user himself diff --git a/extensions/ocs/pkg/middleware/requireuser.go b/services/ocs/pkg/middleware/requireuser.go similarity index 88% rename from extensions/ocs/pkg/middleware/requireuser.go rename to services/ocs/pkg/middleware/requireuser.go index 1cb30477a11..2b17f178b04 100644 --- a/extensions/ocs/pkg/middleware/requireuser.go +++ b/services/ocs/pkg/middleware/requireuser.go @@ -5,8 +5,8 @@ import ( revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/go-chi/render" - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/service/v0/data" - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/service/v0/response" + "github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/data" + "github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/response" ) // RequireUser middleware is used to require a user in context diff --git a/services/ocs/pkg/server/debug/option.go b/services/ocs/pkg/server/debug/option.go new file mode 100644 index 00000000000..2533141d951 --- /dev/null +++ b/services/ocs/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/ocs/pkg/config" +) + +// 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/services/ocs/pkg/server/debug/server.go b/services/ocs/pkg/server/debug/server.go new file mode 100644 index 00000000000..ec144649474 --- /dev/null +++ b/services/ocs/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/ocs/pkg/config" +) + +// 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.GetString()), + 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/services/ocs/pkg/server/http/option.go b/services/ocs/pkg/server/http/option.go new file mode 100644 index 00000000000..dd10e91e2c2 --- /dev/null +++ b/services/ocs/pkg/server/http/option.go @@ -0,0 +1,76 @@ +package http + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/ocs/pkg/config" + "github.com/owncloud/ocis/v2/services/ocs/pkg/metrics" + "github.com/urfave/cli/v2" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Namespace string + Logger log.Logger + Context context.Context + Config *config.Config + Metrics *metrics.Metrics + Flags []cli.Flag +} + +// 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 + } +} + +// Metrics provides a function to set the metrics option. +func Metrics(val *metrics.Metrics) Option { + return func(o *Options) { + o.Metrics = val + } +} + +// Flags provides a function to set the flags option. +func Flags(val []cli.Flag) Option { + return func(o *Options) { + o.Flags = append(o.Flags, val...) + } +} + +// Namespace provides a function to set the Namespace option. +func Namespace(val string) Option { + return func(o *Options) { + o.Namespace = val + } +} diff --git a/services/ocs/pkg/server/http/server.go b/services/ocs/pkg/server/http/server.go new file mode 100644 index 00000000000..1f3a4b608d5 --- /dev/null +++ b/services/ocs/pkg/server/http/server.go @@ -0,0 +1,63 @@ +package http + +import ( + chimiddleware "github.com/go-chi/chi/v5/middleware" + "github.com/owncloud/ocis/v2/ocis-pkg/cors" + "github.com/owncloud/ocis/v2/ocis-pkg/middleware" + "github.com/owncloud/ocis/v2/ocis-pkg/service/http" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + ocsmw "github.com/owncloud/ocis/v2/services/ocs/pkg/middleware" + svc "github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0" + "go-micro.dev/v4" +) + +// Server initializes the http service and server. +func Server(opts ...Option) (http.Service, error) { + options := newOptions(opts...) + + service := http.NewService( + http.Logger(options.Logger), + http.Name(options.Config.Service.Name), + http.Version(version.GetString()), + http.Namespace(options.Config.HTTP.Namespace), + http.Address(options.Config.HTTP.Addr), + http.Context(options.Context), + http.Flags(options.Flags...), + ) + + handle := svc.NewService( + svc.Logger(options.Logger), + svc.Config(options.Config), + svc.Middleware( + chimiddleware.RealIP, + chimiddleware.RequestID, + middleware.NoCache, + middleware.Cors( + cors.Logger(options.Logger), + cors.AllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins), + cors.AllowedMethods(options.Config.HTTP.CORS.AllowedMethods), + cors.AllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders), + cors.AllowCredentials(options.Config.HTTP.CORS.AllowCredentials), + ), + middleware.Secure, + middleware.Version( + options.Config.Service.Name, + version.GetString(), + ), + middleware.Logger(options.Logger), + ocsmw.LogTrace, + ), + ) + + { + handle = svc.NewInstrument(handle, options.Metrics) + handle = svc.NewLogging(handle, options.Logger) + handle = svc.NewTracing(handle) + } + + if err := micro.RegisterHandler(service.Server(), handle); err != nil { + return http.Service{}, err + } + + return service, nil +} diff --git a/services/ocs/pkg/service/v0/config.go b/services/ocs/pkg/service/v0/config.go new file mode 100644 index 00000000000..9ff28bad191 --- /dev/null +++ b/services/ocs/pkg/service/v0/config.go @@ -0,0 +1,19 @@ +package svc + +import ( + "net/http" + + "github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/data" + "github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/response" +) + +// GetConfig renders the ocs config endpoint +func (o Ocs) GetConfig(w http.ResponseWriter, r *http.Request) { + o.mustRender(w, r, response.DataRender(&data.ConfigData{ + Version: "1.7", // TODO get from env + Website: "ocis", // TODO get from env + Host: "", // TODO get from FRONTEND config + Contact: "", // TODO get from env + SSL: "true", // TODO get from env + })) +} diff --git a/extensions/ocs/pkg/service/v0/data/config.go b/services/ocs/pkg/service/v0/data/config.go similarity index 100% rename from extensions/ocs/pkg/service/v0/data/config.go rename to services/ocs/pkg/service/v0/data/config.go diff --git a/extensions/ocs/pkg/service/v0/data/group.go b/services/ocs/pkg/service/v0/data/group.go similarity index 100% rename from extensions/ocs/pkg/service/v0/data/group.go rename to services/ocs/pkg/service/v0/data/group.go diff --git a/extensions/ocs/pkg/service/v0/data/meta.go b/services/ocs/pkg/service/v0/data/meta.go similarity index 100% rename from extensions/ocs/pkg/service/v0/data/meta.go rename to services/ocs/pkg/service/v0/data/meta.go diff --git a/extensions/ocs/pkg/service/v0/data/user.go b/services/ocs/pkg/service/v0/data/user.go similarity index 100% rename from extensions/ocs/pkg/service/v0/data/user.go rename to services/ocs/pkg/service/v0/data/user.go diff --git a/services/ocs/pkg/service/v0/groups.go b/services/ocs/pkg/service/v0/groups.go new file mode 100644 index 00000000000..be933718efb --- /dev/null +++ b/services/ocs/pkg/service/v0/groups.go @@ -0,0 +1,98 @@ +package svc + +import ( + "net/http" + "net/url" + + "github.com/go-chi/chi/v5" + "github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/data" + "github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/response" +) + +// ListUserGroups lists a users groups +func (o Ocs) ListUserGroups(w http.ResponseWriter, r *http.Request) { + userid := chi.URLParam(r, "userid") + userid, _ = url.PathUnescape(userid) + switch o.config.AccountBackend { + case "cs3": + // TODO + o.mustRender(w, r, response.DataRender(&data.Groups{})) + return + default: + o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) + } + return +} + +// AddToGroup adds a user to a group +func (o Ocs) AddToGroup(w http.ResponseWriter, r *http.Request) { + switch o.config.AccountBackend { + case "cs3": + // TODO + o.cs3WriteNotSupported(w, r) + return + default: + o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) + } +} + +// RemoveFromGroup removes a user from a group +func (o Ocs) RemoveFromGroup(w http.ResponseWriter, r *http.Request) { + switch o.config.AccountBackend { + case "cs3": + // TODO + o.cs3WriteNotSupported(w, r) + return + default: + o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) + } +} + +// ListGroups lists all groups +func (o Ocs) ListGroups(w http.ResponseWriter, r *http.Request) { + switch o.config.AccountBackend { + case "cs3": + // TODO + o.mustRender(w, r, response.DataRender(&data.Groups{})) + return + default: + o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) + } + return +} + +// AddGroup adds a group +// oC10 implementation: https://github.com/owncloud/core/blob/762780a23c9eadda4fb5fa8db99eba66a5100b6e/apps/provisioning_api/lib/Groups.php#L126-L154 +func (o Ocs) AddGroup(w http.ResponseWriter, r *http.Request) { + switch o.config.AccountBackend { + case "cs3": + o.cs3WriteNotSupported(w, r) + return + default: + o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) + } +} + +// DeleteGroup deletes a group +func (o Ocs) DeleteGroup(w http.ResponseWriter, r *http.Request) { + switch o.config.AccountBackend { + case "cs3": + o.cs3WriteNotSupported(w, r) + return + default: + o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) + } +} + +// GetGroupMembers lists all members of a group +func (o Ocs) GetGroupMembers(w http.ResponseWriter, r *http.Request) { + switch o.config.AccountBackend { + case "cs3": + // TODO + o.mustRender(w, r, response.DataRender(&data.Users{})) + return + default: + o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) + } + return +} diff --git a/services/ocs/pkg/service/v0/instrument.go b/services/ocs/pkg/service/v0/instrument.go new file mode 100644 index 00000000000..15c42b348f9 --- /dev/null +++ b/services/ocs/pkg/service/v0/instrument.go @@ -0,0 +1,30 @@ +package svc + +import ( + "net/http" + + "github.com/owncloud/ocis/v2/services/ocs/pkg/metrics" +) + +// NewInstrument returns a service that instruments metrics. +func NewInstrument(next Service, metrics *metrics.Metrics) Service { + return instrument{ + next: next, + metrics: metrics, + } +} + +type instrument struct { + next Service + metrics *metrics.Metrics +} + +// ServeHTTP implements the Service interface. +func (i instrument) ServeHTTP(w http.ResponseWriter, r *http.Request) { + i.next.ServeHTTP(w, r) +} + +// GetConfig implements the Service interface. +func (i instrument) GetConfig(w http.ResponseWriter, r *http.Request) { + i.next.GetConfig(w, r) +} diff --git a/extensions/ocs/pkg/service/v0/logging.go b/services/ocs/pkg/service/v0/logging.go similarity index 100% rename from extensions/ocs/pkg/service/v0/logging.go rename to services/ocs/pkg/service/v0/logging.go diff --git a/services/ocs/pkg/service/v0/option.go b/services/ocs/pkg/service/v0/option.go new file mode 100644 index 00000000000..bfefbe61f09 --- /dev/null +++ b/services/ocs/pkg/service/v0/option.go @@ -0,0 +1,68 @@ +package svc + +import ( + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/roles" + settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" + "github.com/owncloud/ocis/v2/services/ocs/pkg/config" +) + +// 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 + Config *config.Config + Middleware []func(http.Handler) http.Handler + RoleService settingssvc.RoleService + RoleManager *roles.Manager +} + +// 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 + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} + +// Middleware provides a function to set the middleware option. +func Middleware(val ...func(http.Handler) http.Handler) Option { + return func(o *Options) { + o.Middleware = val + } +} + +// RoleService provides a function to set the RoleService option. +func RoleService(val settingssvc.RoleService) Option { + return func(o *Options) { + o.RoleService = val + } +} + +// RoleManager provides a function to set the RoleManager option. +func RoleManager(val *roles.Manager) Option { + return func(o *Options) { + o.RoleManager = val + } +} diff --git a/extensions/ocs/pkg/service/v0/response/response.go b/services/ocs/pkg/service/v0/response/response.go similarity index 97% rename from extensions/ocs/pkg/service/v0/response/response.go rename to services/ocs/pkg/service/v0/response/response.go index 6731d1fa48a..d1035fcd744 100644 --- a/extensions/ocs/pkg/service/v0/response/response.go +++ b/services/ocs/pkg/service/v0/response/response.go @@ -6,7 +6,7 @@ import ( "reflect" "github.com/go-chi/render" - "github.com/owncloud/ocis/v2/extensions/ocs/pkg/service/v0/data" + "github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/data" ) // Response is the top level response structure diff --git a/services/ocs/pkg/service/v0/response/version.go b/services/ocs/pkg/service/v0/response/version.go new file mode 100644 index 00000000000..5e72bc257a5 --- /dev/null +++ b/services/ocs/pkg/service/v0/response/version.go @@ -0,0 +1,92 @@ +package response + +import ( + "context" + "net/http" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" + "github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/data" +) + +type key int + +const ( + apiVersionKey key = iota + ocsVersion1 = "1" + ocsVersion2 = "2" +) + +var ( + defaultStatusCodeMapper = OcsV2StatusCodes +) + +// APIVersion retrieves the api version from the context. +func APIVersion(ctx context.Context) string { + value := ctx.Value(apiVersionKey) + if value != nil { + return value.(string) + } + return "" +} + +// OcsV1StatusCodes returns the http status codes for the OCS API v1. +func OcsV1StatusCodes(meta data.Meta) int { + if meta.StatusCode == data.MetaUnauthorized.StatusCode { + return http.StatusUnauthorized + } + return http.StatusOK +} + +// OcsV2StatusCodes maps the OCS codes to http status codes for the ocs API v2. +// see https://github.com/owncloud/core/blob/c08baf580927ecb8ec179028dda255fdd85b4568/lib/private/legacy/api.php#L528 +// also HTTP status codes for apps are the same as OCS codes +// see https://github.com/owncloud/core/blob/b9ff4c93e051c94adfb301545098ae627e52ef76/lib/public/AppFramework/OCSController.php#L142-L150 +// I think this is a bug in the ocs v2 api, but since we are going to mimic bugs in ocis ... here goes +func OcsV2StatusCodes(meta data.Meta) int { + sc := meta.StatusCode + switch sc { + case data.MetaNotFound.StatusCode: + return http.StatusNotFound + case data.MetaUnknownError.StatusCode: + fallthrough + case data.MetaServerError.StatusCode: + return http.StatusInternalServerError + case data.MetaUnauthorized.StatusCode: + return http.StatusUnauthorized + case data.MetaOK.StatusCode: + // TODO mustn't data.Meta be a pointer so this assignment has an effect + meta.StatusCode = http.StatusOK + return http.StatusOK + } + // any 2xx, 4xx and 5xx will be used as is + if sc >= 200 && sc < 600 { + return sc + } + + // any error codes > 100 are treated as client errors + if sc > 100 && sc < 200 { + return http.StatusBadRequest + } + + // TODO change this status code? yes, align with oc10 core mapStatusCodes + return http.StatusOK +} + +// VersionCtx middleware is used to determine the response mapper from +// the URL parameters passed through as the request. In case +// the Version is unknown, we stop here and return a 404. +func VersionCtx(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + version := chi.URLParam(r, "version") + if version == "" { + _ = render.Render(w, r, ErrRender(data.MetaBadRequest.StatusCode, "unknown ocs api version")) + return + } + w.Header().Set("Ocs-Api-Version", version) + + // store version in context so handlers can access it + ctx := context.WithValue(r.Context(), apiVersionKey, version) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} diff --git a/services/ocs/pkg/service/v0/service.go b/services/ocs/pkg/service/v0/service.go new file mode 100644 index 00000000000..372d0c9c955 --- /dev/null +++ b/services/ocs/pkg/service/v0/service.go @@ -0,0 +1,133 @@ +package svc + +import ( + "net/http" + "time" + + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" + "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" + "github.com/go-chi/render" + + "github.com/owncloud/ocis/v2/ocis-pkg/account" + "github.com/owncloud/ocis/v2/ocis-pkg/log" + opkgm "github.com/owncloud/ocis/v2/ocis-pkg/middleware" + "github.com/owncloud/ocis/v2/ocis-pkg/roles" + settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" + "github.com/owncloud/ocis/v2/services/ocs/pkg/config" + ocsm "github.com/owncloud/ocis/v2/services/ocs/pkg/middleware" + "github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/data" + "github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/response" + "github.com/owncloud/ocis/v2/services/proxy/pkg/user/backend" +) + +// Service defines the extension handlers. +type Service interface { + ServeHTTP(http.ResponseWriter, *http.Request) + GetConfig(http.ResponseWriter, *http.Request) +} + +// NewService returns a service implementation for Service. +func NewService(opts ...Option) Service { + options := newOptions(opts...) + + m := chi.NewMux() + m.Use(options.Middleware...) + + roleService := options.RoleService + if roleService == nil { + roleService = settingssvc.NewRoleService("com.owncloud.api.settings", grpc.DefaultClient) + } + roleManager := options.RoleManager + if roleManager == nil { + m := roles.NewManager( + roles.CacheSize(1024), + roles.CacheTTL(time.Hour*24*7), + roles.Logger(options.Logger), + roles.RoleService(roleService), + ) + roleManager = &m + } + + svc := Ocs{ + config: options.Config, + mux: m, + RoleManager: roleManager, + logger: options.Logger, + } + + if svc.config.AccountBackend == "" { + svc.config.AccountBackend = "cs3" + } + + requireUser := ocsm.RequireUser( + ocsm.Logger(options.Logger), + ) + + m.Route(options.Config.HTTP.Root, func(r chi.Router) { + r.NotFound(svc.NotFound) + r.Use(middleware.StripSlashes) + r.Use(opkgm.ExtractAccountUUID( + account.Logger(options.Logger), + account.JWTSecret(options.Config.TokenManager.JWTSecret)), + ) + r.Use(ocsm.OCSFormatCtx) // updates request Accept header according to format=(json|xml) query parameter + r.Route("/v{version:(1|2)}.php", func(r chi.Router) { + r.Use(response.VersionCtx) // stores version in context + r.Route("/apps/files_sharing/api/v1", func(r chi.Router) {}) + r.Route("/apps/notifications/api/v1", func(r chi.Router) {}) + r.Route("/cloud", func(r chi.Router) { + r.Route("/capabilities", func(r chi.Router) {}) + // TODO /apps + r.Route("/user", func(r chi.Router) { + r.Get("/signing-key", svc.GetSigningKey) + }) + }) + r.Route("/config", func(r chi.Router) { + r.With(requireUser).Get("/", svc.GetConfig) + }) + }) + }) + + return svc +} + +// Ocs defines implements the business logic for Service. +type Ocs struct { + config *config.Config + logger log.Logger + RoleService settingssvc.RoleService + RoleManager *roles.Manager + mux *chi.Mux +} + +// ServeHTTP implements the Service interface. +func (o Ocs) ServeHTTP(w http.ResponseWriter, r *http.Request) { + o.mux.ServeHTTP(w, r) +} + +// NotFound uses ErrRender to always return a proper OCS payload +func (o Ocs) NotFound(w http.ResponseWriter, r *http.Request) { + o.mustRender(w, r, response.ErrRender(data.MetaNotFound.StatusCode, "not found")) +} + +func (o Ocs) getCS3Backend() backend.UserBackend { + revaClient, err := pool.GetGatewayServiceClient(o.config.Reva.Address) + if err != nil { + o.logger.Fatal().Msgf("could not get reva client at address %s", o.config.Reva.Address) + } + return backend.NewCS3UserBackend(nil, revaClient, o.config.MachineAuthAPIKey, "", nil, o.logger) +} + +// NotImplementedStub returns a not implemented error +func (o Ocs) NotImplementedStub(w http.ResponseWriter, r *http.Request) { + o.mustRender(w, r, response.ErrRender(data.MetaUnknownError.StatusCode, "Not implemented")) +} + +func (o Ocs) mustRender(w http.ResponseWriter, r *http.Request, renderer render.Renderer) { + if err := render.Render(w, r, renderer); err != nil { + o.logger.Err(err).Msgf("failed to write response for ocs request %s on %s", r.Method, r.URL) + } +} diff --git a/extensions/ocs/pkg/service/v0/tracing.go b/services/ocs/pkg/service/v0/tracing.go similarity index 100% rename from extensions/ocs/pkg/service/v0/tracing.go rename to services/ocs/pkg/service/v0/tracing.go diff --git a/services/ocs/pkg/service/v0/users.go b/services/ocs/pkg/service/v0/users.go new file mode 100644 index 00000000000..d666033d45b --- /dev/null +++ b/services/ocs/pkg/service/v0/users.go @@ -0,0 +1,257 @@ +package svc + +import ( + "context" + "crypto/rand" + "encoding/hex" + "net/http" + "net/url" + "strings" + + storemsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/store/v0" + storesvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/store/v0" + + cs3 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + revactx "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/go-chi/chi/v5" + "github.com/go-micro/plugins/v4/client/grpc" + "github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/data" + "github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/response" + ocstracing "github.com/owncloud/ocis/v2/services/ocs/pkg/tracing" + merrors "go-micro.dev/v4/errors" +) + +// GetSelf returns the currently logged in user +func (o Ocs) GetSelf(w http.ResponseWriter, r *http.Request) { + u, ok := revactx.ContextGetUser(r.Context()) + if !ok || u.Id == nil || u.Id.OpaqueId == "" { + o.mustRender(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "user is missing an id")) + return + } + d := &data.User{ + UserID: u.Username, + DisplayName: u.DisplayName, + LegacyDisplayName: u.DisplayName, + Email: u.Mail, + UIDNumber: u.UidNumber, + GIDNumber: u.GidNumber, + } + o.mustRender(w, r, response.DataRender(d)) + return +} + +// GetUser returns the user with the given userid +func (o Ocs) GetUser(w http.ResponseWriter, r *http.Request) { + userid := chi.URLParam(r, "userid") + userid, err := url.PathUnescape(userid) + if err != nil { + o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error())) + } + + var user *cs3.User + switch { + case userid == "": + o.mustRender(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "missing user in context")) + case o.config.AccountBackend == "cs3": + user, err = o.fetchAccountFromCS3Backend(r.Context(), userid) + default: + o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) + } + + if err != nil { + merr := merrors.FromError(err) + if merr.Code == http.StatusNotFound { + o.mustRender(w, r, response.ErrRender(data.MetaNotFound.StatusCode, data.MessageUserNotFound)) + } else { + o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error())) + } + o.logger.Error().Err(merr).Str("userid", userid).Msg("could not get account for user") + return + } + + o.logger.Debug().Interface("user", user).Msg("got user") + + d := &data.User{ + UserID: user.Username, + DisplayName: user.DisplayName, + LegacyDisplayName: user.DisplayName, + Email: user.Mail, + UIDNumber: user.UidNumber, + GIDNumber: user.GidNumber, + Enabled: "true", // TODO include in response only when admin? + // TODO query storage registry for free space? of home storage, maybe... + Quota: &data.Quota{ + Free: 2840756224000, + Used: 5059416668, + Total: 2845815640668, + Relative: 0.18, + Definition: "default", + }, + } + + _, span := ocstracing.TraceProvider. + Tracer("ocs"). + Start(r.Context(), "GetUser") + defer span.End() + + o.mustRender(w, r, response.DataRender(d)) +} + +// AddUser creates a new user account +func (o Ocs) AddUser(w http.ResponseWriter, r *http.Request) { + switch o.config.AccountBackend { + case "cs3": + o.cs3WriteNotSupported(w, r) + return + default: + o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) + } +} + +// EditUser creates a new user account +func (o Ocs) EditUser(w http.ResponseWriter, r *http.Request) { + switch o.config.AccountBackend { + case "cs3": + o.cs3WriteNotSupported(w, r) + return + default: + o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) + } +} + +// DeleteUser deletes a user +func (o Ocs) DeleteUser(w http.ResponseWriter, r *http.Request) { + switch o.config.AccountBackend { + case "cs3": + o.cs3WriteNotSupported(w, r) + return + default: + o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) + } +} + +// EnableUser enables a user +func (o Ocs) EnableUser(w http.ResponseWriter, r *http.Request) { + switch o.config.AccountBackend { + case "cs3": + o.cs3WriteNotSupported(w, r) + return + default: + o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) + } +} + +// DisableUser disables a user +func (o Ocs) DisableUser(w http.ResponseWriter, r *http.Request) { + switch o.config.AccountBackend { + case "cs3": + o.cs3WriteNotSupported(w, r) + return + default: + o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) + } +} + +// GetSigningKey returns the signing key for the current user. It will create it on the fly if it does not exist +// The signing key is part of the user settings and is used by the proxy to authenticate requests +// Currently, the username is used as the OC-Credential +func (o Ocs) GetSigningKey(w http.ResponseWriter, r *http.Request) { + u, ok := revactx.ContextGetUser(r.Context()) + if !ok { + //o.logger.Error().Msg("missing user in context") + o.mustRender(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "missing user in context")) + return + } + + // use the user's UUID + userID := u.Id.OpaqueId + + c := storesvc.NewStoreService("com.owncloud.api.store", grpc.NewClient()) + res, err := c.Read(r.Context(), &storesvc.ReadRequest{ + Options: &storemsg.ReadOptions{ + Database: "proxy", + Table: "signing-keys", + }, + Key: userID, + }) + if err == nil && len(res.Records) > 0 { + o.mustRender(w, r, response.DataRender(&data.SigningKey{ + User: userID, + SigningKey: string(res.Records[0].Value), + })) + return + } + if err != nil { + e := merrors.Parse(err.Error()) + if e.Code == http.StatusNotFound { + // not found is ok, so we can continue and generate the key on the fly + } else { + o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, "error reading from store")) + return + } + } + + // try creating it + key := make([]byte, 64) + _, err = rand.Read(key[:]) + if err != nil { + o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, "could not generate signing key")) + return + } + signingKey := hex.EncodeToString(key) + + _, err = c.Write(r.Context(), &storesvc.WriteRequest{ + Options: &storemsg.WriteOptions{ + Database: "proxy", + Table: "signing-keys", + }, + Record: &storemsg.Record{ + Key: userID, + Value: []byte(signingKey), + // TODO Expiry? + }, + }) + + if err != nil { + //o.logger.Error().Err(err).Msg("error writing key") + o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, "could not persist signing key")) + return + } + + o.mustRender(w, r, response.DataRender(&data.SigningKey{ + User: userID, + SigningKey: signingKey, + })) +} + +// ListUsers lists the users +func (o Ocs) ListUsers(w http.ResponseWriter, r *http.Request) { + switch o.config.AccountBackend { + case "cs3": + // TODO + o.cs3WriteNotSupported(w, r) + return + default: + o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend) + } +} + +// escapeValue escapes all special characters in the value +func escapeValue(value string) string { + return strings.ReplaceAll(value, "'", "''") +} + +func (o Ocs) fetchAccountFromCS3Backend(ctx context.Context, name string) (*cs3.User, error) { + backend := o.getCS3Backend() + u, _, err := backend.GetUserByClaims(ctx, "username", name, false) + if err != nil { + return nil, err + } + return u, nil +} + +func (o Ocs) cs3WriteNotSupported(w http.ResponseWriter, r *http.Request) { + o.logger.Warn().Msg("the CS3 backend does not support adding or updating users") + o.NotImplementedStub(w, r) + return +} diff --git a/services/ocs/pkg/tracing/tracing.go b/services/ocs/pkg/tracing/tracing.go new file mode 100644 index 00000000000..6119fac108e --- /dev/null +++ b/services/ocs/pkg/tracing/tracing.go @@ -0,0 +1,23 @@ +package tracing + +import ( + pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/ocs/pkg/config" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the ocs service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config) error { + var err error + if cfg.Tracing.Enabled { + if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { + return err + } + } + + return nil +} diff --git a/extensions/ocs/reflex.conf b/services/ocs/reflex.conf similarity index 100% rename from extensions/ocs/reflex.conf rename to services/ocs/reflex.conf diff --git a/extensions/proxy/.dockerignore b/services/proxy/.dockerignore similarity index 100% rename from extensions/proxy/.dockerignore rename to services/proxy/.dockerignore diff --git a/extensions/proxy/Makefile b/services/proxy/Makefile similarity index 100% rename from extensions/proxy/Makefile rename to services/proxy/Makefile diff --git a/services/proxy/cmd/proxy/main.go b/services/proxy/cmd/proxy/main.go new file mode 100644 index 00000000000..7db75b5de08 --- /dev/null +++ b/services/proxy/cmd/proxy/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/proxy/pkg/command" + "github.com/owncloud/ocis/v2/services/proxy/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/proxy/docker/Dockerfile.linux.amd64 b/services/proxy/docker/Dockerfile.linux.amd64 similarity index 100% rename from extensions/proxy/docker/Dockerfile.linux.amd64 rename to services/proxy/docker/Dockerfile.linux.amd64 diff --git a/extensions/proxy/docker/Dockerfile.linux.arm b/services/proxy/docker/Dockerfile.linux.arm similarity index 100% rename from extensions/proxy/docker/Dockerfile.linux.arm rename to services/proxy/docker/Dockerfile.linux.arm diff --git a/extensions/proxy/docker/Dockerfile.linux.arm64 b/services/proxy/docker/Dockerfile.linux.arm64 similarity index 100% rename from extensions/proxy/docker/Dockerfile.linux.arm64 rename to services/proxy/docker/Dockerfile.linux.arm64 diff --git a/extensions/proxy/docker/manifest.tmpl b/services/proxy/docker/manifest.tmpl similarity index 100% rename from extensions/proxy/docker/manifest.tmpl rename to services/proxy/docker/manifest.tmpl diff --git a/services/proxy/pkg/command/health.go b/services/proxy/pkg/command/health.go new file mode 100644 index 00000000000..cb9ee814bf4 --- /dev/null +++ b/services/proxy/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/proxy/pkg/config" + "github.com/owncloud/ocis/v2/services/proxy/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/proxy/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/services/proxy/pkg/command/root.go b/services/proxy/pkg/command/root.go new file mode 100644 index 00000000000..1f809d049fc --- /dev/null +++ b/services/proxy/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/proxy/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-proxy command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "proxy", + Usage: "proxy 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 proxy command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new proxy.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.Proxy.Commons = cfg.Commons + return SutureService{ + cfg: cfg.Proxy, + } +} + +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/services/proxy/pkg/command/server.go b/services/proxy/pkg/command/server.go new file mode 100644 index 00000000000..b41633ea951 --- /dev/null +++ b/services/proxy/pkg/command/server.go @@ -0,0 +1,233 @@ +package command + +import ( + "context" + "crypto/tls" + "fmt" + "net/http" + "os" + "time" + + "github.com/coreos/go-oidc/v3/oidc" + "github.com/cs3org/reva/v2/pkg/token/manager/jwt" + chimiddleware "github.com/go-chi/chi/v5/middleware" + "github.com/justinas/alice" + "github.com/oklog/run" + "github.com/owncloud/ocis/v2/ocis-pkg/log" + pkgmiddleware "github.com/owncloud/ocis/v2/ocis-pkg/middleware" + "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" + storesvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/store/v0" + "github.com/owncloud/ocis/v2/services/proxy/pkg/config" + "github.com/owncloud/ocis/v2/services/proxy/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/proxy/pkg/cs3" + "github.com/owncloud/ocis/v2/services/proxy/pkg/logging" + "github.com/owncloud/ocis/v2/services/proxy/pkg/metrics" + "github.com/owncloud/ocis/v2/services/proxy/pkg/middleware" + "github.com/owncloud/ocis/v2/services/proxy/pkg/proxy" + "github.com/owncloud/ocis/v2/services/proxy/pkg/server/debug" + proxyHTTP "github.com/owncloud/ocis/v2/services/proxy/pkg/server/http" + "github.com/owncloud/ocis/v2/services/proxy/pkg/tracing" + "github.com/owncloud/ocis/v2/services/proxy/pkg/user/backend" + "github.com/urfave/cli/v2" + "golang.org/x/oauth2" +) + +// Server is the entrypoint 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) + os.Exit(1) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg) + if err != nil { + return err + } + + var ( + m = metrics.New() + ) + + 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() + + m.BuildInfo.WithLabelValues(version.GetString()).Set(1) + + rp := proxy.NewMultiHostReverseProxy( + proxy.Logger(logger), + proxy.Config(cfg), + ) + + { + server, err := proxyHTTP.Server( + proxyHTTP.Handler(rp), + proxyHTTP.Logger(logger), + proxyHTTP.Context(ctx), + proxyHTTP.Config(cfg), + proxyHTTP.Metrics(metrics.New()), + proxyHTTP.Middlewares(loadMiddlewares(ctx, logger, cfg)), + ) + + if err != nil { + logger.Error(). + Err(err). + Str("server", "http"). + Msg("Failed to initialize server") + + return err + } + + gr.Add(func() error { + return server.Run() + }, func(_ error) { + logger.Info(). + Str("server", "http"). + Msg("Shutting down server") + + cancel() + }) + } + + { + server, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Error().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(server.ListenAndServe, func(_ error) { + _ = server.Shutdown(ctx) + cancel() + }) + } + + return gr.Run() + }, + } +} + +func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config) alice.Chain { + rolesClient := settingssvc.NewRoleService("com.owncloud.api.settings", grpc.DefaultClient) + revaClient, err := cs3.GetGatewayServiceClient(cfg.Reva.Address) + var userProvider backend.UserBackend + switch cfg.AccountBackend { + case "cs3": + tokenManager, err := jwt.New(map[string]interface{}{ + "secret": cfg.TokenManager.JWTSecret, + }) + if err != nil { + logger.Error().Err(err). + Msg("Failed to create token manager") + } + + userProvider = backend.NewCS3UserBackend(rolesClient, revaClient, cfg.MachineAuthAPIKey, cfg.OIDC.Issuer, tokenManager, logger) + default: + logger.Fatal().Msgf("Invalid accounts backend type '%s'", cfg.AccountBackend) + } + + storeClient := storesvc.NewStoreService("com.owncloud.api.store", grpc.DefaultClient) + if err != nil { + logger.Error().Err(err). + Str("gateway", cfg.Reva.Address). + Msg("Failed to create reva gateway service client") + } + + var oidcHTTPClient = &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: cfg.OIDC.Insecure, //nolint:gosec + }, + DisableKeepAlives: true, + }, + Timeout: time.Second * 10, + } + + return alice.New( + // first make sure we log all requests and redirect to https if necessary + pkgmiddleware.TraceContext, + chimiddleware.RealIP, + chimiddleware.RequestID, + middleware.AccessLog(logger), + middleware.HTTPSRedirect, + + // now that we established the basics, on with authentication middleware + middleware.Authentication( + // OIDC Options + middleware.OIDCProviderFunc(func() (middleware.OIDCProvider, error) { + // Initialize a provider by specifying the issuer URL. + // it will fetch the keys from the issuer using the .well-known + // endpoint + return oidc.NewProvider( + context.WithValue(ctx, oauth2.HTTPClient, oidcHTTPClient), + cfg.OIDC.Issuer, + ) + }), + middleware.HTTPClient(oidcHTTPClient), + middleware.TokenCacheSize(cfg.OIDC.UserinfoCache.Size), + middleware.TokenCacheTTL(time.Second*time.Duration(cfg.OIDC.UserinfoCache.TTL)), + + // basic Options + middleware.Logger(logger), + middleware.EnableBasicAuth(cfg.EnableBasicAuth), + middleware.UserProvider(userProvider), + middleware.OIDCIss(cfg.OIDC.Issuer), + middleware.UserOIDCClaim(cfg.UserOIDCClaim), + middleware.UserCS3Claim(cfg.UserCS3Claim), + middleware.CredentialsByUserAgent(cfg.AuthMiddleware.CredentialsByUserAgent), + ), + middleware.SignedURLAuth( + middleware.Logger(logger), + middleware.PreSignedURLConfig(cfg.PreSignedURL), + middleware.UserProvider(userProvider), + middleware.Store(storeClient), + ), + middleware.AccountResolver( + middleware.Logger(logger), + middleware.UserProvider(userProvider), + middleware.TokenManagerConfig(*cfg.TokenManager), + middleware.UserOIDCClaim(cfg.UserOIDCClaim), + middleware.UserCS3Claim(cfg.UserCS3Claim), + middleware.AutoprovisionAccounts(cfg.AutoprovisionAccounts), + ), + + middleware.SelectorCookie( + middleware.Logger(logger), + middleware.UserProvider(userProvider), + middleware.PolicySelectorConfig(*cfg.PolicySelector), + ), + + // finally, trigger home creation when a user logs in + middleware.CreateHome( + middleware.Logger(logger), + middleware.TokenManagerConfig(*cfg.TokenManager), + middleware.RevaGatewayClient(revaClient), + ), + middleware.PublicShareAuth( + middleware.Logger(logger), + middleware.RevaGatewayClient(revaClient), + ), + ) +} diff --git a/services/proxy/pkg/command/version.go b/services/proxy/pkg/command/version.go new file mode 100644 index 00000000000..b24caa11bac --- /dev/null +++ b/services/proxy/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/proxy/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: "Version", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.GetString()) + 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/proxy/pkg/config/config.go b/services/proxy/pkg/config/config.go similarity index 100% rename from extensions/proxy/pkg/config/config.go rename to services/proxy/pkg/config/config.go diff --git a/extensions/proxy/pkg/config/debug.go b/services/proxy/pkg/config/debug.go similarity index 100% rename from extensions/proxy/pkg/config/debug.go rename to services/proxy/pkg/config/debug.go diff --git a/services/proxy/pkg/config/defaults/defaultconfig.go b/services/proxy/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..566373002ad --- /dev/null +++ b/services/proxy/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,238 @@ +package defaults + +import ( + "path" + "strings" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" + "github.com/owncloud/ocis/v2/services/proxy/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:9205", + Token: "", + }, + HTTP: config.HTTP{ + Addr: "0.0.0.0:9200", + Root: "/", + Namespace: "com.owncloud.web", + TLSCert: path.Join(defaults.BaseDataPath(), "proxy", "server.crt"), + TLSKey: path.Join(defaults.BaseDataPath(), "proxy", "server.key"), + TLS: true, + }, + Service: config.Service{ + Name: "proxy", + }, + OIDC: config.OIDC{ + Issuer: "https://localhost:9200", + Insecure: true, + //Insecure: true, + UserinfoCache: config.UserinfoCache{ + Size: 1024, + TTL: 10, + }, + }, + PolicySelector: nil, + Reva: &config.Reva{ + Address: "127.0.0.1:9142", + }, + PreSignedURL: config.PreSignedURL{ + AllowedHTTPMethods: []string{"GET"}, + Enabled: true, + }, + AccountBackend: "cs3", + UserOIDCClaim: "email", + UserCS3Claim: "mail", + AutoprovisionAccounts: false, + EnableBasicAuth: false, + InsecureBackends: false, + } +} + +func DefaultPolicies() []config.Policy { + return []config.Policy{ + { + Name: "ocis", + Routes: []config.Route{ + { + Endpoint: "/", + Backend: "http://localhost:9100", + }, + { + Endpoint: "/.well-known/", + Backend: "http://localhost:9130", + }, + { + Endpoint: "/konnect/", + Backend: "http://localhost:9130", + }, + { + Endpoint: "/signin/", + Backend: "http://localhost:9130", + }, + { + Endpoint: "/archiver", + Backend: "http://localhost:9140", + }, + { + Type: config.RegexRoute, + Endpoint: "/ocs/v[12].php/cloud/user/signing-key", // only `user/signing-key` is left in ocis-ocs + Backend: "http://localhost:9110", + }, + { + Endpoint: "/ocs/", + Backend: "http://localhost:9140", + }, + { + Type: config.QueryRoute, + Endpoint: "/remote.php/?preview=1", + Backend: "http://localhost:9115", + }, + { + // TODO the actual REPORT goes to /dav/files/{username}, which is user specific ... how would this work in a spaces world? + // TODO what paths are returned? the href contains the full path so it should be possible to return urls from other spaces? + // TODO or we allow a REPORT on /dav/spaces to search all spaces and /dav/space/{spaceid} to search a specific space + // send webdav REPORT requests to search service + Method: "REPORT", + Endpoint: "/remote.php/dav/", + Backend: "http://localhost:9115", // TODO use registry? + }, + { + Type: config.QueryRoute, + Endpoint: "/dav/?preview=1", + Backend: "http://localhost:9115", + }, + { + Type: config.QueryRoute, + Endpoint: "/webdav/?preview=1", + Backend: "http://localhost:9115", + }, + { + Endpoint: "/remote.php/", + Service: "com.owncloud.web.ocdav", + }, + { + Endpoint: "/dav/", + Service: "com.owncloud.web.ocdav", + }, + { + Endpoint: "/webdav/", + Service: "com.owncloud.web.ocdav", + }, + { + Endpoint: "/status", + Service: "com.owncloud.web.ocdav", + }, + { + Endpoint: "/status.php", + Service: "com.owncloud.web.ocdav", + }, + { + Endpoint: "/index.php/", + Service: "com.owncloud.web.ocdav", + }, + { + Endpoint: "/apps/", + Service: "com.owncloud.web.ocdav", + }, + { + Endpoint: "/data", + Backend: "http://localhost:9140", + }, + { + Endpoint: "/app/", // /app or /apps? ocdav only handles /apps + Backend: "http://localhost:9140", + }, + { + Endpoint: "/graph/", + Backend: "http://localhost:9120", + }, + { + Endpoint: "/graph-explorer", + Backend: "http://localhost:9135", + }, + { + Endpoint: "/api/v0/settings", + Backend: "http://localhost:9190", + }, + { + Endpoint: "/settings.js", + Backend: "http://localhost:9190", + }, + }, + }, + } +} + +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.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{} + } + + if cfg.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" { + cfg.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey + } + + 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{} + } +} + +func Sanitize(cfg *config.Config) { + // sanitize config + if cfg.Policies == nil { + cfg.Policies = DefaultPolicies() + } + + if cfg.PolicySelector == nil { + cfg.PolicySelector = &config.PolicySelector{ + Static: &config.StaticSelectorConf{ + Policy: "ocis", + }, + } + } + + if cfg.HTTP.Root != "/" { + cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") + } +} diff --git a/extensions/proxy/pkg/config/http.go b/services/proxy/pkg/config/http.go similarity index 100% rename from extensions/proxy/pkg/config/http.go rename to services/proxy/pkg/config/http.go diff --git a/extensions/proxy/pkg/config/log.go b/services/proxy/pkg/config/log.go similarity index 100% rename from extensions/proxy/pkg/config/log.go rename to services/proxy/pkg/config/log.go diff --git a/services/proxy/pkg/config/parser/parse.go b/services/proxy/pkg/config/parser/parse.go new file mode 100644 index 00000000000..14218d9e8e9 --- /dev/null +++ b/services/proxy/pkg/config/parser/parse.go @@ -0,0 +1,45 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/services/proxy/pkg/config" + "github.com/owncloud/ocis/v2/services/proxy/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + if cfg.TokenManager.JWTSecret == "" { + return shared.MissingJWTTokenError(cfg.Service.Name) + } + + if cfg.MachineAuthAPIKey == "" { + return shared.MissingMachineAuthApiKeyError(cfg.Service.Name) + } + + return nil +} diff --git a/extensions/proxy/pkg/config/reva.go b/services/proxy/pkg/config/reva.go similarity index 100% rename from extensions/proxy/pkg/config/reva.go rename to services/proxy/pkg/config/reva.go diff --git a/extensions/proxy/pkg/config/service.go b/services/proxy/pkg/config/service.go similarity index 100% rename from extensions/proxy/pkg/config/service.go rename to services/proxy/pkg/config/service.go diff --git a/extensions/proxy/pkg/config/tracing.go b/services/proxy/pkg/config/tracing.go similarity index 100% rename from extensions/proxy/pkg/config/tracing.go rename to services/proxy/pkg/config/tracing.go diff --git a/extensions/proxy/pkg/cs3/client.go b/services/proxy/pkg/cs3/client.go similarity index 92% rename from extensions/proxy/pkg/cs3/client.go rename to services/proxy/pkg/cs3/client.go index 10b650aef7e..bbc4dcc3d36 100644 --- a/extensions/proxy/pkg/cs3/client.go +++ b/services/proxy/pkg/cs3/client.go @@ -2,7 +2,7 @@ package cs3 import ( gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" - proxytracing "github.com/owncloud/ocis/v2/extensions/proxy/pkg/tracing" + proxytracing "github.com/owncloud/ocis/v2/services/proxy/pkg/tracing" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" diff --git a/services/proxy/pkg/logging/logging.go b/services/proxy/pkg/logging/logging.go new file mode 100644 index 00000000000..f4a0b6959f2 --- /dev/null +++ b/services/proxy/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/proxy/pkg/config" +) + +// 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/proxy/pkg/metrics/metrics.go b/services/proxy/pkg/metrics/metrics.go similarity index 100% rename from extensions/proxy/pkg/metrics/metrics.go rename to services/proxy/pkg/metrics/metrics.go diff --git a/extensions/proxy/pkg/middleware/accesslog.go b/services/proxy/pkg/middleware/accesslog.go similarity index 100% rename from extensions/proxy/pkg/middleware/accesslog.go rename to services/proxy/pkg/middleware/accesslog.go diff --git a/extensions/proxy/pkg/middleware/account_resolver.go b/services/proxy/pkg/middleware/account_resolver.go similarity index 98% rename from extensions/proxy/pkg/middleware/account_resolver.go rename to services/proxy/pkg/middleware/account_resolver.go index 451eeee7a1a..2972264d5d8 100644 --- a/extensions/proxy/pkg/middleware/account_resolver.go +++ b/services/proxy/pkg/middleware/account_resolver.go @@ -4,7 +4,7 @@ import ( "errors" "net/http" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/user/backend" + "github.com/owncloud/ocis/v2/services/proxy/pkg/user/backend" revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/owncloud/ocis/v2/ocis-pkg/log" diff --git a/extensions/proxy/pkg/middleware/account_resolver_test.go b/services/proxy/pkg/middleware/account_resolver_test.go similarity index 95% rename from extensions/proxy/pkg/middleware/account_resolver_test.go rename to services/proxy/pkg/middleware/account_resolver_test.go index 7da0d9a5e7a..14a74225cb0 100644 --- a/extensions/proxy/pkg/middleware/account_resolver_test.go +++ b/services/proxy/pkg/middleware/account_resolver_test.go @@ -10,11 +10,11 @@ import ( "github.com/cs3org/reva/v2/pkg/auth/scope" revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/token/manager/jwt" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/user/backend" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/user/backend/test" "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/ocis-pkg/oidc" + "github.com/owncloud/ocis/v2/services/proxy/pkg/config" + "github.com/owncloud/ocis/v2/services/proxy/pkg/user/backend" + "github.com/owncloud/ocis/v2/services/proxy/pkg/user/backend/test" "github.com/stretchr/testify/assert" ) diff --git a/extensions/proxy/pkg/middleware/authentication.go b/services/proxy/pkg/middleware/authentication.go similarity index 100% rename from extensions/proxy/pkg/middleware/authentication.go rename to services/proxy/pkg/middleware/authentication.go diff --git a/extensions/proxy/pkg/middleware/basic_auth.go b/services/proxy/pkg/middleware/basic_auth.go similarity index 97% rename from extensions/proxy/pkg/middleware/basic_auth.go rename to services/proxy/pkg/middleware/basic_auth.go index 5d54283cbcc..b7d480d87bc 100644 --- a/extensions/proxy/pkg/middleware/basic_auth.go +++ b/services/proxy/pkg/middleware/basic_auth.go @@ -5,10 +5,10 @@ import ( "net/http" "strings" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/user/backend" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/webdav" "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/ocis-pkg/oidc" + "github.com/owncloud/ocis/v2/services/proxy/pkg/user/backend" + "github.com/owncloud/ocis/v2/services/proxy/pkg/webdav" ) // BasicAuth provides a middleware to check if BasicAuth is provided diff --git a/extensions/proxy/pkg/middleware/basic_auth_test.go b/services/proxy/pkg/middleware/basic_auth_test.go similarity index 100% rename from extensions/proxy/pkg/middleware/basic_auth_test.go rename to services/proxy/pkg/middleware/basic_auth_test.go diff --git a/extensions/proxy/pkg/middleware/create_home.go b/services/proxy/pkg/middleware/create_home.go similarity index 100% rename from extensions/proxy/pkg/middleware/create_home.go rename to services/proxy/pkg/middleware/create_home.go diff --git a/extensions/proxy/pkg/middleware/https_redirect.go b/services/proxy/pkg/middleware/https_redirect.go similarity index 100% rename from extensions/proxy/pkg/middleware/https_redirect.go rename to services/proxy/pkg/middleware/https_redirect.go diff --git a/extensions/proxy/pkg/middleware/oidc_auth.go b/services/proxy/pkg/middleware/oidc_auth.go similarity index 98% rename from extensions/proxy/pkg/middleware/oidc_auth.go rename to services/proxy/pkg/middleware/oidc_auth.go index a115fd10aa9..332b5eb6e3c 100644 --- a/extensions/proxy/pkg/middleware/oidc_auth.go +++ b/services/proxy/pkg/middleware/oidc_auth.go @@ -9,10 +9,10 @@ import ( "github.com/golang-jwt/jwt/v4" gOidc "github.com/coreos/go-oidc/v3/oidc" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/ocis-pkg/oidc" "github.com/owncloud/ocis/v2/ocis-pkg/sync" + "github.com/owncloud/ocis/v2/services/proxy/pkg/config" "golang.org/x/oauth2" ) diff --git a/extensions/proxy/pkg/middleware/oidc_auth_test.go b/services/proxy/pkg/middleware/oidc_auth_test.go similarity index 100% rename from extensions/proxy/pkg/middleware/oidc_auth_test.go rename to services/proxy/pkg/middleware/oidc_auth_test.go diff --git a/extensions/proxy/pkg/middleware/options.go b/services/proxy/pkg/middleware/options.go similarity index 97% rename from extensions/proxy/pkg/middleware/options.go rename to services/proxy/pkg/middleware/options.go index 6701e71b164..63994ee8e41 100644 --- a/extensions/proxy/pkg/middleware/options.go +++ b/services/proxy/pkg/middleware/options.go @@ -4,14 +4,14 @@ import ( "net/http" "time" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/user/backend" + "github.com/owncloud/ocis/v2/services/proxy/pkg/user/backend" settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" storesvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/store/v0" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/proxy/pkg/config" ) // Option defines a single option function. diff --git a/extensions/proxy/pkg/middleware/public_share_auth.go b/services/proxy/pkg/middleware/public_share_auth.go similarity index 100% rename from extensions/proxy/pkg/middleware/public_share_auth.go rename to services/proxy/pkg/middleware/public_share_auth.go diff --git a/extensions/proxy/pkg/middleware/selector_cookie.go b/services/proxy/pkg/middleware/selector_cookie.go similarity index 92% rename from extensions/proxy/pkg/middleware/selector_cookie.go rename to services/proxy/pkg/middleware/selector_cookie.go index 1c99ba411ed..f0ec0d1f243 100644 --- a/extensions/proxy/pkg/middleware/selector_cookie.go +++ b/services/proxy/pkg/middleware/selector_cookie.go @@ -3,10 +3,10 @@ package middleware import ( "net/http" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/proxy/policy" "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/ocis-pkg/oidc" + "github.com/owncloud/ocis/v2/services/proxy/pkg/config" + "github.com/owncloud/ocis/v2/services/proxy/pkg/proxy/policy" ) // SelectorCookie provides a middleware which diff --git a/extensions/proxy/pkg/middleware/signed_url_auth.go b/services/proxy/pkg/middleware/signed_url_auth.go similarity index 98% rename from extensions/proxy/pkg/middleware/signed_url_auth.go rename to services/proxy/pkg/middleware/signed_url_auth.go index e917d3e7a9b..d71036d5a7f 100644 --- a/extensions/proxy/pkg/middleware/signed_url_auth.go +++ b/services/proxy/pkg/middleware/signed_url_auth.go @@ -12,11 +12,11 @@ import ( "time" revactx "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/user/backend" "github.com/owncloud/ocis/v2/ocis-pkg/log" storemsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/store/v0" storesvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/store/v0" + "github.com/owncloud/ocis/v2/services/proxy/pkg/config" + "github.com/owncloud/ocis/v2/services/proxy/pkg/user/backend" "golang.org/x/crypto/pbkdf2" ) diff --git a/extensions/proxy/pkg/middleware/signed_url_auth_test.go b/services/proxy/pkg/middleware/signed_url_auth_test.go similarity index 100% rename from extensions/proxy/pkg/middleware/signed_url_auth_test.go rename to services/proxy/pkg/middleware/signed_url_auth_test.go diff --git a/services/proxy/pkg/proxy/option.go b/services/proxy/pkg/proxy/option.go new file mode 100644 index 00000000000..628e9087c76 --- /dev/null +++ b/services/proxy/pkg/proxy/option.go @@ -0,0 +1,40 @@ +package proxy + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/proxy/pkg/config" +) + +// 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 + 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 + } +} + +// 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/proxy/pkg/proxy/policy/selector.go b/services/proxy/pkg/proxy/policy/selector.go similarity index 99% rename from extensions/proxy/pkg/proxy/policy/selector.go rename to services/proxy/pkg/proxy/policy/selector.go index f462d26c8e8..05cf5d3a4ce 100644 --- a/extensions/proxy/pkg/proxy/policy/selector.go +++ b/services/proxy/pkg/proxy/policy/selector.go @@ -7,8 +7,8 @@ import ( "sort" revactx "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/oidc" + "github.com/owncloud/ocis/v2/services/proxy/pkg/config" ) var ( diff --git a/extensions/proxy/pkg/proxy/policy/selector_test.go b/services/proxy/pkg/proxy/policy/selector_test.go similarity index 98% rename from extensions/proxy/pkg/proxy/policy/selector_test.go rename to services/proxy/pkg/proxy/policy/selector_test.go index ebe344fd32d..1821382262d 100644 --- a/extensions/proxy/pkg/proxy/policy/selector_test.go +++ b/services/proxy/pkg/proxy/policy/selector_test.go @@ -8,8 +8,8 @@ import ( userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" revactx "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/oidc" + "github.com/owncloud/ocis/v2/services/proxy/pkg/config" ) func TestLoadSelector(t *testing.T) { diff --git a/extensions/proxy/pkg/proxy/proxy.go b/services/proxy/pkg/proxy/proxy.go similarity index 97% rename from extensions/proxy/pkg/proxy/proxy.go rename to services/proxy/pkg/proxy/proxy.go index 6d27096ecb4..a275d788b1d 100644 --- a/extensions/proxy/pkg/proxy/proxy.go +++ b/services/proxy/pkg/proxy/proxy.go @@ -16,12 +16,12 @@ import ( "go.opentelemetry.io/otel/attribute" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/proxy/policy" - proxytracing "github.com/owncloud/ocis/v2/extensions/proxy/pkg/tracing" "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/ocis-pkg/registry" pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/proxy/pkg/config" + "github.com/owncloud/ocis/v2/services/proxy/pkg/proxy/policy" + proxytracing "github.com/owncloud/ocis/v2/services/proxy/pkg/tracing" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) diff --git a/extensions/proxy/pkg/proxy/proxy_integration_test.go b/services/proxy/pkg/proxy/proxy_integration_test.go similarity index 98% rename from extensions/proxy/pkg/proxy/proxy_integration_test.go rename to services/proxy/pkg/proxy/proxy_integration_test.go index afe0145cc85..7f173d6894e 100644 --- a/extensions/proxy/pkg/proxy/proxy_integration_test.go +++ b/services/proxy/pkg/proxy/proxy_integration_test.go @@ -10,7 +10,7 @@ import ( "net/url" "testing" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config" + "github.com/owncloud/ocis/v2/services/proxy/pkg/config" ) func TestProxyIntegration(t *testing.T) { diff --git a/extensions/proxy/pkg/proxy/proxy_test.go b/services/proxy/pkg/proxy/proxy_test.go similarity index 97% rename from extensions/proxy/pkg/proxy/proxy_test.go rename to services/proxy/pkg/proxy/proxy_test.go index e270b640f1c..0fa67a77b1a 100644 --- a/extensions/proxy/pkg/proxy/proxy_test.go +++ b/services/proxy/pkg/proxy/proxy_test.go @@ -7,8 +7,8 @@ import ( "net/url" "testing" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/config/defaults" + "github.com/owncloud/ocis/v2/services/proxy/pkg/config" + "github.com/owncloud/ocis/v2/services/proxy/pkg/config/defaults" ) type matchertest struct { diff --git a/services/proxy/pkg/server/debug/option.go b/services/proxy/pkg/server/debug/option.go new file mode 100644 index 00000000000..5174c60d460 --- /dev/null +++ b/services/proxy/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/proxy/pkg/config" +) + +// 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/services/proxy/pkg/server/debug/server.go b/services/proxy/pkg/server/debug/server.go new file mode 100644 index 00000000000..ccc766ab32c --- /dev/null +++ b/services/proxy/pkg/server/debug/server.go @@ -0,0 +1,75 @@ +package debug + +import ( + "encoding/json" + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/proxy/pkg/config" +) + +// 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.GetString()), + 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.ConfigDump(configDump(options.Config)), + ), 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) + } + } +} + +// configDump implements the config dump +func configDump(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + b, err := json.Marshal(cfg) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + } + + _, _ = w.Write(b) + } +} diff --git a/services/proxy/pkg/server/http/option.go b/services/proxy/pkg/server/http/option.go new file mode 100644 index 00000000000..3955118fd77 --- /dev/null +++ b/services/proxy/pkg/server/http/option.go @@ -0,0 +1,86 @@ +package http + +import ( + "context" + "net/http" + + "github.com/justinas/alice" + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/proxy/pkg/config" + "github.com/owncloud/ocis/v2/services/proxy/pkg/metrics" + "github.com/urfave/cli/v2" +) + +// 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 + Handler http.Handler + Metrics *metrics.Metrics + Flags []cli.Flag + Middlewares alice.Chain +} + +// 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 + } +} + +// Metrics provides a function to set the metrics option. +func Metrics(val *metrics.Metrics) Option { + return func(o *Options) { + o.Metrics = val + } +} + +// Flags provides a function to set the flags option. +func Flags(val []cli.Flag) Option { + return func(o *Options) { + o.Flags = append(o.Flags, val...) + } +} + +// Handler provides a function to set the Handler option. +func Handler(h http.Handler) Option { + return func(o *Options) { + o.Handler = h + } +} + +// Middlewares provides a function to register middlewares +func Middlewares(val alice.Chain) Option { + return func(o *Options) { + o.Middlewares = val + } +} diff --git a/extensions/proxy/pkg/server/http/server.go b/services/proxy/pkg/server/http/server.go similarity index 100% rename from extensions/proxy/pkg/server/http/server.go rename to services/proxy/pkg/server/http/server.go diff --git a/services/proxy/pkg/tracing/tracing.go b/services/proxy/pkg/tracing/tracing.go new file mode 100644 index 00000000000..b8a07b12ca9 --- /dev/null +++ b/services/proxy/pkg/tracing/tracing.go @@ -0,0 +1,23 @@ +package tracing + +import ( + pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/proxy/pkg/config" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the proxy service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config) error { + var err error + if cfg.Tracing.Enabled { + if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { + return err + } + } + + return nil +} diff --git a/extensions/proxy/pkg/user/backend/backend.go b/services/proxy/pkg/user/backend/backend.go similarity index 100% rename from extensions/proxy/pkg/user/backend/backend.go rename to services/proxy/pkg/user/backend/backend.go diff --git a/services/proxy/pkg/user/backend/cs3.go b/services/proxy/pkg/user/backend/cs3.go new file mode 100644 index 00000000000..cad1139a2da --- /dev/null +++ b/services/proxy/pkg/user/backend/cs3.go @@ -0,0 +1,327 @@ +package backend + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + cs3 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/v2/pkg/auth/scope" + revactx "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/v2/pkg/token" + libregraph "github.com/owncloud/libre-graph-api-go" + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/oidc" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" + "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" + settingsService "github.com/owncloud/ocis/v2/services/settings/pkg/service/v0" + merrors "go-micro.dev/v4/errors" + "go-micro.dev/v4/selector" +) + +type cs3backend struct { + graphSelector selector.Selector + settingsRoleService settingssvc.RoleService + authProvider RevaAuthenticator + oidcISS string + machineAuthAPIKey string + tokenManager token.Manager + logger log.Logger +} + +// NewCS3UserBackend creates a user-provider which fetches users from a CS3 UserBackend +func NewCS3UserBackend(rs settingssvc.RoleService, ap RevaAuthenticator, machineAuthAPIKey string, oidcISS string, tokenManager token.Manager, logger log.Logger) UserBackend { + reg := registry.GetRegistry() + sel := selector.NewSelector(selector.Registry(reg)) + return &cs3backend{ + graphSelector: sel, + settingsRoleService: rs, + authProvider: ap, + oidcISS: oidcISS, + machineAuthAPIKey: machineAuthAPIKey, + tokenManager: tokenManager, + logger: logger, + } +} + +func (c *cs3backend) GetUserByClaims(ctx context.Context, claim, value string, withRoles bool) (*cs3.User, string, error) { + res, err := c.authProvider.Authenticate(ctx, &gateway.AuthenticateRequest{ + Type: "machine", + ClientId: claim + ":" + value, + ClientSecret: c.machineAuthAPIKey, + }) + + switch { + case err != nil: + return nil, "", fmt.Errorf("could not get user by claim %v with value %v: %w", claim, value, err) + case res.Status.Code != rpcv1beta1.Code_CODE_OK: + if res.Status.Code == rpcv1beta1.Code_CODE_NOT_FOUND { + return nil, "", ErrAccountNotFound + } + return nil, "", fmt.Errorf("could not get user by claim %v with value %v : %w ", claim, value, err) + } + + user := res.User + + if !withRoles { + return user, res.Token, nil + } + + var roleIDs []string + if user.Id.Type != cs3.UserType_USER_TYPE_LIGHTWEIGHT { + roleIDs, err = loadRolesIDs(ctx, user.Id.OpaqueId, c.settingsRoleService) + if err != nil { + var merr *merrors.Error + if errors.As(err, &merr) && merr.Code == http.StatusNotFound { + // This user doesn't have a role assignment yet. Assign a + // default user role. At least until proper roles are provided. See + // https://github.com/owncloud/ocis/v2/issues/1825 for more context. + if user.Id.Type == cs3.UserType_USER_TYPE_PRIMARY { + c.logger.Info().Str("userid", user.Id.OpaqueId).Msg("user has no role assigned, assigning default user role") + _, err := c.settingsRoleService.AssignRoleToUser(ctx, &settingssvc.AssignRoleToUserRequest{ + AccountUuid: user.Id.OpaqueId, + RoleId: settingsService.BundleUUIDRoleUser, + }) + if err != nil { + c.logger.Error().Err(err).Msg("Could not add default role") + return nil, "", err + } + roleIDs = append(roleIDs, settingsService.BundleUUIDRoleUser) + } + } else { + c.logger.Error().Err(err).Msgf("Could not load roles") + return nil, "", err + } + } + } + + enc, err := encodeRoleIDs(roleIDs) + if err != nil { + c.logger.Error().Err(err).Msg("Could not encode loaded roles") + } + + if user.Opaque == nil { + user.Opaque = &types.Opaque{ + Map: map[string]*types.OpaqueEntry{ + "roles": enc, + }, + } + } else { + user.Opaque.Map["roles"] = enc + } + + return user, res.Token, nil +} + +func (c *cs3backend) Authenticate(ctx context.Context, username string, password string) (*cs3.User, string, error) { + res, err := c.authProvider.Authenticate(ctx, &gateway.AuthenticateRequest{ + Type: "basic", + ClientId: username, + ClientSecret: password, + }) + + switch { + case err != nil: + return nil, "", fmt.Errorf("could not authenticate with username and password user: %s, %w", username, err) + case res.Status.Code != rpcv1beta1.Code_CODE_OK: + return nil, "", fmt.Errorf("could not authenticate with username and password user: %s, got code: %d", username, res.Status.Code) + } + + return res.User, res.Token, nil +} + +// CreateUserFromClaims creates a new user via libregraph users API, taking the +// attributes from the provided `claims` map. On success it returns the new +// user. If the user already exist this is not considered an error and the +// function will just return the existing user. +func (c *cs3backend) CreateUserFromClaims(ctx context.Context, claims map[string]interface{}) (*cs3.User, error) { + newctx := context.Background() + token, err := c.generateAutoProvisionAdminToken(newctx) + if err != nil { + c.logger.Error().Err(err).Msg("Error generating token for autoprovisioning user.") + return nil, err + } + lgClient, err := c.setupLibregraphClient(ctx, token) + if err != nil { + c.logger.Error().Err(err).Msg("Error setting up libregraph client.") + return nil, err + } + + newUser, err := c.libregraphUserFromClaims(newctx, claims) + if err != nil { + c.logger.Error().Err(err).Interface("claims", claims).Msg("Error creating user from claims") + return nil, fmt.Errorf("Error creating user from claims: %w", err) + } + + req := lgClient.UsersApi.CreateUser(newctx).User(newUser) + + created, resp, err := req.Execute() + var reread bool + if err != nil { + if resp == nil { + return nil, err + } + + // If the user already exists here, some other request did already create it in parallel. + // So just issue a Debug message and ignore the libregraph error otherwise + var lerr error + if reread, lerr = c.isAlreadyExists(resp); lerr != nil { + c.logger.Error().Err(lerr).Msg("extracting error from ibregraph response body failed.") + return nil, err + } + if !reread { + c.logger.Error().Err(err).Msg("Error creating user") + return nil, err + } + } + + // User has been created meanwhile, re-read it to get the user id + if reread { + c.logger.Debug().Msg("User already exist, re-reading via libregraph") + gureq := lgClient.UserApi.GetUser(newctx, newUser.GetOnPremisesSamAccountName()) + created, resp, err = gureq.Execute() + if err != nil { + c.logger.Error().Err(err).Msg("Error trying to re-read user from graphAPI") + return nil, err + } + } + + cs3UserCreated := c.cs3UserFromLibregraph(newctx, created) + + return &cs3UserCreated, nil +} + +func (c cs3backend) GetUserGroups(ctx context.Context, userID string) { + panic("implement me") +} + +func (c cs3backend) setupLibregraphClient(ctx context.Context, cs3token string) (*libregraph.APIClient, error) { + // Use micro registry to resolve next graph service endpoint + next, err := c.graphSelector.Select("com.owncloud.graph.graph") + if err != nil { + c.logger.Debug().Err(err).Msg("setupLibregraphClient: error during Select") + return nil, err + } + node, err := next() + if err != nil { + c.logger.Debug().Err(err).Msg("setupLibregraphClient: error getting next Node") + return nil, err + } + lgconf := libregraph.NewConfiguration() + lgconf.Servers = libregraph.ServerConfigurations{ + { + URL: fmt.Sprintf("%s://%s/graph/v1.0", node.Metadata["protocol"], node.Address), + }, + } + + lgconf.DefaultHeader = map[string]string{revactx.TokenHeader: cs3token} + return libregraph.NewAPIClient(lgconf), nil +} + +func (c cs3backend) isAlreadyExists(resp *http.Response) (bool, error) { + oDataErr := libregraph.NewOdataErrorWithDefaults() + body, err := io.ReadAll(resp.Body) + if err != nil { + c.logger.Debug().Err(err).Msg("Error trying to read libregraph response") + return false, err + } + err = json.Unmarshal(body, oDataErr) + if err != nil { + c.logger.Debug().Err(err).Msg("Error unmarshalling libregraph response") + return false, err + } + + if oDataErr.Error.Code == errorcode.NameAlreadyExists.String() { + return true, nil + } + return false, nil +} + +func (c cs3backend) libregraphUserFromClaims(ctx context.Context, claims map[string]interface{}) (libregraph.User, error) { + var ok bool + var dn, mail, username string + user := libregraph.User{} + if dn, ok = claims[oidc.Name].(string); !ok { + return user, fmt.Errorf("Missing claim '%s'", oidc.Name) + } + if mail, ok = claims[oidc.Email].(string); !ok { + return user, fmt.Errorf("Missing claim '%s'", oidc.Email) + } + if username, ok = claims[oidc.PreferredUsername].(string); !ok { + c.logger.Warn().Str("claim", oidc.PreferredUsername).Msg("Missing claim for username, falling back to email address") + username = mail + } + user.DisplayName = &dn + user.OnPremisesSamAccountName = &username + user.Mail = &mail + return user, nil +} + +func (c cs3backend) cs3UserFromLibregraph(ctx context.Context, lu *libregraph.User) cs3.User { + cs3id := cs3.UserId{ + Type: cs3.UserType_USER_TYPE_PRIMARY, + Idp: c.oidcISS, + } + + cs3id.OpaqueId = lu.GetId() + + cs3user := cs3.User{ + Id: &cs3id, + } + cs3user.Username = lu.GetOnPremisesSamAccountName() + cs3user.DisplayName = lu.GetDisplayName() + cs3user.Mail = lu.GetMail() + return cs3user +} + +// This returns an hardcoded internal User, that is privileged to create new User via +// the Graph API. This user is needed for autoprovisioning of users from incoming OIDC +// claims. +func getAutoProvisionUserCreator() (*cs3.User, error) { + encRoleID, err := encodeRoleIDs([]string{settingsService.BundleUUIDRoleAdmin}) + if err != nil { + return nil, err + } + + autoProvisionUserCreator := &cs3.User{ + DisplayName: "Autoprovision User", + Username: "autoprovisioner", + Id: &cs3.UserId{ + Idp: "internal", + OpaqueId: "autoprov-user-id00-0000-000000000000", + }, + Opaque: &types.Opaque{ + Map: map[string]*types.OpaqueEntry{ + "roles": encRoleID, + }, + }, + } + return autoProvisionUserCreator, nil +} + +func (c cs3backend) generateAutoProvisionAdminToken(ctx context.Context) (string, error) { + userCreator, err := getAutoProvisionUserCreator() + if err != nil { + return "", err + } + + s, err := scope.AddOwnerScope(nil) + if err != nil { + c.logger.Error().Err(err).Msg("could not get owner scope") + return "", err + } + + token, err := c.tokenManager.MintToken(ctx, userCreator, s) + if err != nil { + c.logger.Error().Err(err).Msg("could not mint token") + return "", err + } + return token, nil +} diff --git a/extensions/proxy/pkg/user/backend/test/backend_mock.go b/services/proxy/pkg/user/backend/test/backend_mock.go similarity index 99% rename from extensions/proxy/pkg/user/backend/test/backend_mock.go rename to services/proxy/pkg/user/backend/test/backend_mock.go index fbcaf5d7ab3..49c642a29ac 100644 --- a/extensions/proxy/pkg/user/backend/test/backend_mock.go +++ b/services/proxy/pkg/user/backend/test/backend_mock.go @@ -8,7 +8,7 @@ import ( "sync" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - "github.com/owncloud/ocis/v2/extensions/proxy/pkg/user/backend" + "github.com/owncloud/ocis/v2/services/proxy/pkg/user/backend" ) // Ensure, that UserBackendMock does implement UserBackend. diff --git a/extensions/proxy/pkg/webdav/response.go b/services/proxy/pkg/webdav/response.go similarity index 100% rename from extensions/proxy/pkg/webdav/response.go rename to services/proxy/pkg/webdav/response.go diff --git a/extensions/proxy/pkg/webdav/webdav.go b/services/proxy/pkg/webdav/webdav.go similarity index 100% rename from extensions/proxy/pkg/webdav/webdav.go rename to services/proxy/pkg/webdav/webdav.go diff --git a/services/search/cmd/search/main.go b/services/search/cmd/search/main.go new file mode 100644 index 00000000000..70103e7bf00 --- /dev/null +++ b/services/search/cmd/search/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/search/pkg/command" + "github.com/owncloud/ocis/v2/services/search/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/services/search/pkg/command/health.go b/services/search/pkg/command/health.go new file mode 100644 index 00000000000..21060de80d8 --- /dev/null +++ b/services/search/pkg/command/health.go @@ -0,0 +1,53 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/search/pkg/config" + "github.com/owncloud/ocis/v2/services/search/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/search/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 { + return parser.ParseConfig(cfg) + }, + 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/search/pkg/command/index.go b/services/search/pkg/command/index.go similarity index 91% rename from extensions/search/pkg/command/index.go rename to services/search/pkg/command/index.go index 4e6ecc65c91..981395164cd 100644 --- a/extensions/search/pkg/command/index.go +++ b/services/search/pkg/command/index.go @@ -6,10 +6,10 @@ import ( "github.com/urfave/cli/v2" - "github.com/owncloud/ocis/v2/extensions/search/pkg/config" - "github.com/owncloud/ocis/v2/extensions/search/pkg/config/parser" "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" searchsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/search/v0" + "github.com/owncloud/ocis/v2/services/search/pkg/config" + "github.com/owncloud/ocis/v2/services/search/pkg/config/parser" ) // Index is the entrypoint for the server command. diff --git a/services/search/pkg/command/root.go b/services/search/pkg/command/root.go new file mode 100644 index 00000000000..204aab9f84e --- /dev/null +++ b/services/search/pkg/command/root.go @@ -0,0 +1,65 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + "github.com/thejerf/suture/v4" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/search/pkg/config" + "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 + Index(cfg), + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-search command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "search", + Usage: "Serve search 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 search command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new search.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.Search.Commons = cfg.Commons + return SutureService{ + cfg: cfg.Search, + } +} + +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/services/search/pkg/command/server.go b/services/search/pkg/command/server.go new file mode 100644 index 00000000000..ab6284edf26 --- /dev/null +++ b/services/search/pkg/command/server.go @@ -0,0 +1,85 @@ +package command + +import ( + "context" + "fmt" + "os" + + "github.com/oklog/run" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/search/pkg/config" + "github.com/owncloud/ocis/v2/services/search/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/search/pkg/logging" + "github.com/owncloud/ocis/v2/services/search/pkg/metrics" + "github.com/owncloud/ocis/v2/services/search/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/search/pkg/server/grpc" + "github.com/owncloud/ocis/v2/services/search/pkg/tracing" + "github.com/urfave/cli/v2" +) + +// Server is the entrypoint 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) + os.Exit(1) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg) + if err != nil { + return err + } + + 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() + + mtrcs := metrics.New() + mtrcs.BuildInfo.WithLabelValues(version.GetString()).Set(1) + + grpcServer := grpc.Server( + grpc.Config(cfg), + grpc.Logger(logger), + grpc.Name(cfg.Service.Name), + grpc.Context(ctx), + grpc.Metrics(mtrcs), + ) + + gr.Add(grpcServer.Run, func(_ error) { + logger.Info().Str("server", "grpc").Msg("shutting down server") + cancel() + }) + + server, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("transport", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(server.ListenAndServe, func(_ error) { + _ = server.Shutdown(ctx) + cancel() + }) + + return gr.Run() + }, + } +} diff --git a/services/search/pkg/command/version.go b/services/search/pkg/command/version.go new file mode 100644 index 00000000000..9aba6033b00 --- /dev/null +++ b/services/search/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/search/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.GetString()) + 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/search/pkg/config/config.go b/services/search/pkg/config/config.go similarity index 100% rename from extensions/search/pkg/config/config.go rename to services/search/pkg/config/config.go diff --git a/extensions/search/pkg/config/debug.go b/services/search/pkg/config/debug.go similarity index 100% rename from extensions/search/pkg/config/debug.go rename to services/search/pkg/config/debug.go diff --git a/services/search/pkg/config/defaults/defaultconfig.go b/services/search/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..213dfc5ab1c --- /dev/null +++ b/services/search/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,75 @@ +package defaults + +import ( + "path" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" + "github.com/owncloud/ocis/v2/services/search/pkg/config" +) + +func FullDefaultConfig() *config.Config { + cfg := DefaultConfig() + + EnsureDefaults(cfg) + + return cfg +} + +func DefaultConfig() *config.Config { + return &config.Config{ + Debug: config.Debug{ + Addr: "127.0.0.1:9224", + Token: "", + }, + GRPC: config.GRPC{ + Addr: "127.0.0.1:9220", + Namespace: "com.owncloud.api", + }, + Service: config.Service{ + Name: "search", + }, + Datapath: path.Join(defaults.BaseDataPath(), "search"), + Reva: config.Reva{ + Address: "127.0.0.1:9142", + }, + Events: config.Events{ + Endpoint: "127.0.0.1:9233", + Cluster: "ocis-cluster", + ConsumerGroup: "search", + }, + MachineAuthAPIKey: "", + } +} + +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.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" { + cfg.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey + } +} + +func Sanitize(cfg *config.Config) { + // no http endpoint to be sanitized +} diff --git a/extensions/search/pkg/config/grpc.go b/services/search/pkg/config/grpc.go similarity index 100% rename from extensions/search/pkg/config/grpc.go rename to services/search/pkg/config/grpc.go diff --git a/extensions/search/pkg/config/http.go b/services/search/pkg/config/http.go similarity index 100% rename from extensions/search/pkg/config/http.go rename to services/search/pkg/config/http.go diff --git a/extensions/search/pkg/config/log.go b/services/search/pkg/config/log.go similarity index 100% rename from extensions/search/pkg/config/log.go rename to services/search/pkg/config/log.go diff --git a/services/search/pkg/config/parser/parse.go b/services/search/pkg/config/parser/parse.go new file mode 100644 index 00000000000..fafad8f8482 --- /dev/null +++ b/services/search/pkg/config/parser/parse.go @@ -0,0 +1,41 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/services/search/pkg/config" + "github.com/owncloud/ocis/v2/services/search/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + if cfg.MachineAuthAPIKey == "" { + return shared.MissingMachineAuthApiKeyError(cfg.Service.Name) + } + return nil +} diff --git a/extensions/search/pkg/config/reva.go b/services/search/pkg/config/reva.go similarity index 100% rename from extensions/search/pkg/config/reva.go rename to services/search/pkg/config/reva.go diff --git a/extensions/search/pkg/config/service.go b/services/search/pkg/config/service.go similarity index 100% rename from extensions/search/pkg/config/service.go rename to services/search/pkg/config/service.go diff --git a/extensions/search/pkg/config/tracing.go b/services/search/pkg/config/tracing.go similarity index 100% rename from extensions/search/pkg/config/tracing.go rename to services/search/pkg/config/tracing.go diff --git a/services/search/pkg/logging/logging.go b/services/search/pkg/logging/logging.go new file mode 100644 index 00000000000..28f1aebef13 --- /dev/null +++ b/services/search/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/search/pkg/config" +) + +// 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/search/pkg/metrics/metrics.go b/services/search/pkg/metrics/metrics.go similarity index 100% rename from extensions/search/pkg/metrics/metrics.go rename to services/search/pkg/metrics/metrics.go diff --git a/extensions/search/pkg/search/index/index.go b/services/search/pkg/search/index/index.go similarity index 100% rename from extensions/search/pkg/search/index/index.go rename to services/search/pkg/search/index/index.go diff --git a/extensions/search/pkg/search/index/index_suite_test.go b/services/search/pkg/search/index/index_suite_test.go similarity index 100% rename from extensions/search/pkg/search/index/index_suite_test.go rename to services/search/pkg/search/index/index_suite_test.go diff --git a/extensions/search/pkg/search/index/index_test.go b/services/search/pkg/search/index/index_test.go similarity index 99% rename from extensions/search/pkg/search/index/index_test.go rename to services/search/pkg/search/index/index_test.go index 7d7b0afc49e..cbc77cf16a9 100644 --- a/extensions/search/pkg/search/index/index_test.go +++ b/services/search/pkg/search/index/index_test.go @@ -6,9 +6,9 @@ import ( "github.com/blevesearch/bleve/v2" sprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/owncloud/ocis/v2/extensions/search/pkg/search/index" searchmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/search/v0" searchsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/search/v0" + "github.com/owncloud/ocis/v2/services/search/pkg/search/index" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" diff --git a/extensions/search/pkg/search/index/mocks/BleveIndex.go b/services/search/pkg/search/index/mocks/BleveIndex.go similarity index 100% rename from extensions/search/pkg/search/index/mocks/BleveIndex.go rename to services/search/pkg/search/index/mocks/BleveIndex.go diff --git a/extensions/search/pkg/search/mocks/IndexClient.go b/services/search/pkg/search/mocks/IndexClient.go similarity index 100% rename from extensions/search/pkg/search/mocks/IndexClient.go rename to services/search/pkg/search/mocks/IndexClient.go diff --git a/extensions/search/pkg/search/mocks/ProviderClient.go b/services/search/pkg/search/mocks/ProviderClient.go similarity index 100% rename from extensions/search/pkg/search/mocks/ProviderClient.go rename to services/search/pkg/search/mocks/ProviderClient.go diff --git a/extensions/search/pkg/search/provider/events.go b/services/search/pkg/search/provider/events.go similarity index 100% rename from extensions/search/pkg/search/provider/events.go rename to services/search/pkg/search/provider/events.go diff --git a/extensions/search/pkg/search/provider/events_test.go b/services/search/pkg/search/provider/events_test.go similarity index 97% rename from extensions/search/pkg/search/provider/events_test.go rename to services/search/pkg/search/provider/events_test.go index f90b9b0e269..b1c9dfdc513 100644 --- a/extensions/search/pkg/search/provider/events_test.go +++ b/services/search/pkg/search/provider/events_test.go @@ -13,9 +13,9 @@ import ( "github.com/cs3org/reva/v2/pkg/events" "github.com/cs3org/reva/v2/pkg/rgrpc/status" cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" - "github.com/owncloud/ocis/v2/extensions/search/pkg/search/mocks" - provider "github.com/owncloud/ocis/v2/extensions/search/pkg/search/provider" "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/search/pkg/search/mocks" + provider "github.com/owncloud/ocis/v2/services/search/pkg/search/provider" ) var _ = Describe("Searchprovider", func() { diff --git a/extensions/search/pkg/search/provider/provider_suite_test.go b/services/search/pkg/search/provider/provider_suite_test.go similarity index 100% rename from extensions/search/pkg/search/provider/provider_suite_test.go rename to services/search/pkg/search/provider/provider_suite_test.go diff --git a/extensions/search/pkg/search/provider/searchprovider.go b/services/search/pkg/search/provider/searchprovider.go similarity index 99% rename from extensions/search/pkg/search/provider/searchprovider.go rename to services/search/pkg/search/provider/searchprovider.go index 5b0301c8e62..310909021f3 100644 --- a/extensions/search/pkg/search/provider/searchprovider.go +++ b/services/search/pkg/search/provider/searchprovider.go @@ -19,8 +19,8 @@ import ( "github.com/cs3org/reva/v2/pkg/storage/utils/walker" "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" - "github.com/owncloud/ocis/v2/extensions/search/pkg/search" "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/search/pkg/search" "google.golang.org/grpc/metadata" searchmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/search/v0" diff --git a/extensions/search/pkg/search/provider/searchprovider_test.go b/services/search/pkg/search/provider/searchprovider_test.go similarity index 98% rename from extensions/search/pkg/search/provider/searchprovider_test.go rename to services/search/pkg/search/provider/searchprovider_test.go index 5f727827350..ee131614a89 100644 --- a/extensions/search/pkg/search/provider/searchprovider_test.go +++ b/services/search/pkg/search/provider/searchprovider_test.go @@ -13,11 +13,11 @@ import ( typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/v2/pkg/rgrpc/status" cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" - "github.com/owncloud/ocis/v2/extensions/search/pkg/search/mocks" - provider "github.com/owncloud/ocis/v2/extensions/search/pkg/search/provider" "github.com/owncloud/ocis/v2/ocis-pkg/log" searchmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/search/v0" searchsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/search/v0" + "github.com/owncloud/ocis/v2/services/search/pkg/search/mocks" + provider "github.com/owncloud/ocis/v2/services/search/pkg/search/provider" ) var _ = Describe("Searchprovider", func() { diff --git a/extensions/search/pkg/search/search.go b/services/search/pkg/search/search.go similarity index 100% rename from extensions/search/pkg/search/search.go rename to services/search/pkg/search/search.go diff --git a/extensions/search/pkg/search/search_suite_test.go b/services/search/pkg/search/search_suite_test.go similarity index 100% rename from extensions/search/pkg/search/search_suite_test.go rename to services/search/pkg/search/search_suite_test.go diff --git a/services/search/pkg/server/debug/option.go b/services/search/pkg/server/debug/option.go new file mode 100644 index 00000000000..9ee33299298 --- /dev/null +++ b/services/search/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/search/pkg/config" +) + +// 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/services/search/pkg/server/debug/server.go b/services/search/pkg/server/debug/server.go new file mode 100644 index 00000000000..db1e4cedd43 --- /dev/null +++ b/services/search/pkg/server/debug/server.go @@ -0,0 +1,59 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/search/pkg/config" +) + +// 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.GetString()), + 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)), + ), 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/services/search/pkg/server/grpc/option.go b/services/search/pkg/server/grpc/option.go new file mode 100644 index 00000000000..1aec67b0033 --- /dev/null +++ b/services/search/pkg/server/grpc/option.go @@ -0,0 +1,85 @@ +package grpc + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/search/pkg/config" + "github.com/owncloud/ocis/v2/services/search/pkg/metrics" + svc "github.com/owncloud/ocis/v2/services/search/pkg/service/v0" + "github.com/urfave/cli/v2" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Name string + Logger log.Logger + Context context.Context + Config *config.Config + Metrics *metrics.Metrics + Flags []cli.Flag + Handler *svc.Service +} + +// newOptions initializes the available default options. +func newOptions(opts ...Option) Options { + opt := Options{} + + for _, o := range opts { + o(&opt) + } + + return opt +} + +// Name provides a name for the service. +func Name(val string) Option { + return func(o *Options) { + o.Name = val + } +} + +// 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 + } +} + +// Metrics provides a function to set the metrics option. +func Metrics(val *metrics.Metrics) Option { + return func(o *Options) { + o.Metrics = val + } +} + +// Flags provides a function to set the flags option. +func Flags(val []cli.Flag) Option { + return func(o *Options) { + o.Flags = append(o.Flags, val...) + } +} + +// Handler provides a function to set the handler option. +func Handler(val *svc.Service) Option { + return func(o *Options) { + o.Handler = val + } +} diff --git a/services/search/pkg/server/grpc/server.go b/services/search/pkg/server/grpc/server.go new file mode 100644 index 00000000000..28bad68a3ce --- /dev/null +++ b/services/search/pkg/server/grpc/server.go @@ -0,0 +1,39 @@ +package grpc + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + searchsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/search/v0" + svc "github.com/owncloud/ocis/v2/services/search/pkg/service/v0" +) + +// Server initializes a new go-micro service ready to run +func Server(opts ...Option) grpc.Service { + options := newOptions(opts...) + + service := grpc.NewService( + grpc.Name(options.Config.Service.Name), + grpc.Context(options.Context), + grpc.Address(options.Config.GRPC.Addr), + grpc.Namespace(options.Config.GRPC.Namespace), + grpc.Logger(options.Logger), + grpc.Flags(options.Flags...), + grpc.Version(version.GetString()), + ) + + handle, err := svc.NewHandler( + svc.Config(options.Config), + svc.Logger(options.Logger), + ) + if err != nil { + options.Logger.Error(). + Err(err). + Msg("Error initializing search service") + return grpc.Service{} + } + _ = searchsvc.RegisterSearchProviderHandler( + service.Server(), + handle, + ) + return service +} diff --git a/services/search/pkg/service/v0/option.go b/services/search/pkg/service/v0/option.go new file mode 100644 index 00000000000..9f1a7d19eb4 --- /dev/null +++ b/services/search/pkg/service/v0/option.go @@ -0,0 +1,39 @@ +package service + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/search/pkg/config" +) + +// 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 + Config *config.Config +} + +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 + } +} + +// 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/services/search/pkg/service/v0/service.go b/services/search/pkg/service/v0/service.go new file mode 100644 index 00000000000..d3fd24724a9 --- /dev/null +++ b/services/search/pkg/service/v0/service.go @@ -0,0 +1,109 @@ +package service + +import ( + "context" + "errors" + "path/filepath" + + "github.com/blevesearch/bleve/v2" + revactx "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/events/server" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" + "github.com/go-micro/plugins/v4/events/natsjs" + "go-micro.dev/v4/metadata" + grpcmetadata "google.golang.org/grpc/metadata" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + searchsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/search/v0" + "github.com/owncloud/ocis/v2/services/search/pkg/config" + "github.com/owncloud/ocis/v2/services/search/pkg/search" + "github.com/owncloud/ocis/v2/services/search/pkg/search/index" + searchprovider "github.com/owncloud/ocis/v2/services/search/pkg/search/provider" +) + +// NewHandler returns a service implementation for Service. +func NewHandler(opts ...Option) (searchsvc.SearchProviderHandler, error) { + options := newOptions(opts...) + logger := options.Logger + cfg := options.Config + + // Connect to nats to listen for changes that need to trigger an index update + evtsCfg := cfg.Events + client, err := server.NewNatsStream( + natsjs.Address(evtsCfg.Endpoint), + natsjs.ClusterID(evtsCfg.Cluster), + ) + if err != nil { + return nil, err + } + evts, err := events.Consume(client, evtsCfg.ConsumerGroup, searchprovider.ListenEvents...) + if err != nil { + return nil, err + } + + indexDir := filepath.Join(cfg.Datapath, "index.bleve") + bleveIndex, err := bleve.Open(indexDir) + if err != nil { + mapping, err := index.BuildMapping() + if err != nil { + return nil, err + } + bleveIndex, err = bleve.New(indexDir, mapping) + if err != nil { + return nil, err + } + } + index, err := index.New(bleveIndex) + if err != nil { + return nil, err + } + + gwclient, err := pool.GetGatewayServiceClient(cfg.Reva.Address) + if err != nil { + logger.Fatal().Err(err).Str("addr", cfg.Reva.Address).Msg("could not get reva client") + } + + provider := searchprovider.New(gwclient, index, cfg.MachineAuthAPIKey, evts, logger) + + return &Service{ + id: cfg.GRPC.Namespace + "." + cfg.Service.Name, + log: logger, + Config: cfg, + provider: provider, + }, nil +} + +// Service implements the searchServiceHandler interface +type Service struct { + id string + log log.Logger + Config *config.Config + provider search.ProviderClient +} + +func (s Service) Search(ctx context.Context, in *searchsvc.SearchRequest, out *searchsvc.SearchResponse) error { + // Get token from the context (go-micro) and make it known to the reva client too (grpc) + t, ok := metadata.Get(ctx, revactx.TokenHeader) + if !ok { + s.log.Error().Msg("Could not get token from context") + return errors.New("could not get token from context") + } + ctx = grpcmetadata.AppendToOutgoingContext(ctx, revactx.TokenHeader, t) + + res, err := s.provider.Search(ctx, &searchsvc.SearchRequest{ + Query: in.Query, + }) + if err != nil { + return err + } + + out.Matches = res.Matches + out.NextPageToken = res.NextPageToken + return nil +} + +func (s Service) IndexSpace(ctx context.Context, in *searchsvc.IndexSpaceRequest, out *searchsvc.IndexSpaceResponse) error { + _, err := s.provider.IndexSpace(ctx, in) + return err +} diff --git a/services/search/pkg/tracing/tracing.go b/services/search/pkg/tracing/tracing.go new file mode 100644 index 00000000000..a9778903dbc --- /dev/null +++ b/services/search/pkg/tracing/tracing.go @@ -0,0 +1,23 @@ +package tracing + +import ( + pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/search/pkg/config" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the proxy service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config) error { + var err error + if cfg.Tracing.Enabled { + if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { + return err + } + } + + return nil +} diff --git a/extensions/settings/.dockerignore b/services/settings/.dockerignore similarity index 100% rename from extensions/settings/.dockerignore rename to services/settings/.dockerignore diff --git a/extensions/settings/.eslintrc.json b/services/settings/.eslintrc.json similarity index 100% rename from extensions/settings/.eslintrc.json rename to services/settings/.eslintrc.json diff --git a/extensions/settings/.gitignore b/services/settings/.gitignore similarity index 100% rename from extensions/settings/.gitignore rename to services/settings/.gitignore diff --git a/extensions/settings/.golangci.yml b/services/settings/.golangci.yml similarity index 100% rename from extensions/settings/.golangci.yml rename to services/settings/.golangci.yml diff --git a/extensions/settings/.yarn/releases/yarn-stable-temp.cjs b/services/settings/.yarn/releases/yarn-stable-temp.cjs similarity index 100% rename from extensions/settings/.yarn/releases/yarn-stable-temp.cjs rename to services/settings/.yarn/releases/yarn-stable-temp.cjs diff --git a/extensions/settings/Makefile b/services/settings/Makefile similarity index 100% rename from extensions/settings/Makefile rename to services/settings/Makefile diff --git a/extensions/settings/assets/.keep b/services/settings/assets/.keep similarity index 100% rename from extensions/settings/assets/.keep rename to services/settings/assets/.keep diff --git a/extensions/settings/babel.config.js b/services/settings/babel.config.js similarity index 100% rename from extensions/settings/babel.config.js rename to services/settings/babel.config.js diff --git a/services/settings/cmd/settings/main.go b/services/settings/cmd/settings/main.go new file mode 100644 index 00000000000..1e21338dd80 --- /dev/null +++ b/services/settings/cmd/settings/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/settings/pkg/command" + "github.com/owncloud/ocis/v2/services/settings/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/settings/docker/Dockerfile.linux.amd64 b/services/settings/docker/Dockerfile.linux.amd64 similarity index 100% rename from extensions/settings/docker/Dockerfile.linux.amd64 rename to services/settings/docker/Dockerfile.linux.amd64 diff --git a/extensions/settings/docker/Dockerfile.linux.arm b/services/settings/docker/Dockerfile.linux.arm similarity index 100% rename from extensions/settings/docker/Dockerfile.linux.arm rename to services/settings/docker/Dockerfile.linux.arm diff --git a/extensions/settings/docker/Dockerfile.linux.arm64 b/services/settings/docker/Dockerfile.linux.arm64 similarity index 100% rename from extensions/settings/docker/Dockerfile.linux.arm64 rename to services/settings/docker/Dockerfile.linux.arm64 diff --git a/extensions/settings/docker/manifest.tmpl b/services/settings/docker/manifest.tmpl similarity index 100% rename from extensions/settings/docker/manifest.tmpl rename to services/settings/docker/manifest.tmpl diff --git a/extensions/settings/l10n/.tx/config b/services/settings/l10n/.tx/config similarity index 100% rename from extensions/settings/l10n/.tx/config rename to services/settings/l10n/.tx/config diff --git a/extensions/settings/l10n/translations.json b/services/settings/l10n/translations.json similarity index 100% rename from extensions/settings/l10n/translations.json rename to services/settings/l10n/translations.json diff --git a/extensions/settings/nightwatch.conf.js b/services/settings/nightwatch.conf.js similarity index 100% rename from extensions/settings/nightwatch.conf.js rename to services/settings/nightwatch.conf.js diff --git a/extensions/settings/package.json b/services/settings/package.json similarity index 100% rename from extensions/settings/package.json rename to services/settings/package.json diff --git a/services/settings/pkg/assets/option.go b/services/settings/pkg/assets/option.go new file mode 100644 index 00000000000..75104c2f0e5 --- /dev/null +++ b/services/settings/pkg/assets/option.go @@ -0,0 +1,50 @@ +package assets + +import ( + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/assetsfs" + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/settings" + "github.com/owncloud/ocis/v2/services/settings/pkg/config" +) + +// New returns a new http filesystem to serve assets. +func New(opts ...Option) http.FileSystem { + options := newOptions(opts...) + return assetsfs.New(settings.Assets, options.Config.Asset.Path, options.Logger) +} + +// 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 + 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 + } +} + +// 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/services/settings/pkg/command/health.go b/services/settings/pkg/command/health.go new file mode 100644 index 00000000000..68dee3672a7 --- /dev/null +++ b/services/settings/pkg/command/health.go @@ -0,0 +1,56 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/settings/pkg/config" + "github.com/owncloud/ocis/v2/services/settings/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/settings/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", + 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/services/settings/pkg/command/root.go b/services/settings/pkg/command/root.go new file mode 100644 index 00000000000..dd53b3cae11 --- /dev/null +++ b/services/settings/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/settings/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-settings command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "settings", + Usage: "Provide settings and permissions 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 settings command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new settings.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.Settings.Commons = cfg.Commons + return SutureService{ + cfg: cfg.Settings, + } +} + +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/services/settings/pkg/command/server.go b/services/settings/pkg/command/server.go new file mode 100644 index 00000000000..9306b00bf9d --- /dev/null +++ b/services/settings/pkg/command/server.go @@ -0,0 +1,89 @@ +package command + +import ( + "context" + "fmt" + "os" + + "github.com/oklog/run" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/settings/pkg/config" + "github.com/owncloud/ocis/v2/services/settings/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/settings/pkg/logging" + "github.com/owncloud/ocis/v2/services/settings/pkg/metrics" + "github.com/owncloud/ocis/v2/services/settings/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/settings/pkg/server/grpc" + "github.com/owncloud/ocis/v2/services/settings/pkg/server/http" + "github.com/owncloud/ocis/v2/services/settings/pkg/tracing" + "github.com/urfave/cli/v2" +) + +// Server is the entrypoint 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) + os.Exit(1) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg) + if err != nil { + return err + } + + servers := 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() + + mtrcs := metrics.New() + mtrcs.BuildInfo.WithLabelValues(version.GetString()).Set(1) + + // prepare an HTTP server and add it to the group run. + httpServer := http.Server( + http.Name(cfg.Service.Name), + http.Logger(logger), + http.Context(ctx), + http.Config(cfg), + http.Metrics(mtrcs), + ) + servers.Add(httpServer.Run, func(_ error) { + logger.Info().Str("server", "http").Msg("Shutting down server") + cancel() + }) + + // prepare a gRPC server and add it to the group run. + grpcServer := grpc.Server(grpc.Name(cfg.Service.Name), grpc.Logger(logger), grpc.Context(ctx), grpc.Config(cfg), grpc.Metrics(mtrcs)) + servers.Add(grpcServer.Run, func(_ error) { + logger.Info().Str("server", "grpc").Msg("Shutting down server") + cancel() + }) + + // prepare a debug server and add it to the group run. + debugServer, err := debug.Server(debug.Logger(logger), debug.Context(ctx), debug.Config(cfg)) + if err != nil { + logger.Error().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + servers.Add(debugServer.ListenAndServe, func(_ error) { + _ = debugServer.Shutdown(ctx) + cancel() + }) + + return servers.Run() + }, + } +} diff --git a/services/settings/pkg/command/version.go b/services/settings/pkg/command/version.go new file mode 100644 index 00000000000..9ac087f77d7 --- /dev/null +++ b/services/settings/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/settings/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.GetString()) + 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/settings/pkg/config/config.go b/services/settings/pkg/config/config.go similarity index 100% rename from extensions/settings/pkg/config/config.go rename to services/settings/pkg/config/config.go diff --git a/extensions/settings/pkg/config/debug.go b/services/settings/pkg/config/debug.go similarity index 100% rename from extensions/settings/pkg/config/debug.go rename to services/settings/pkg/config/debug.go diff --git a/services/settings/pkg/config/defaults/defaultconfig.go b/services/settings/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..05e3d70b3d9 --- /dev/null +++ b/services/settings/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,110 @@ +package defaults + +import ( + "path" + "strings" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" + "github.com/owncloud/ocis/v2/services/settings/pkg/config" +) + +func FullDefaultConfig() *config.Config { + cfg := DefaultConfig() + EnsureDefaults(cfg) + Sanitize(cfg) + return cfg +} + +// DefaultConfig returns the default config +func DefaultConfig() *config.Config { + return &config.Config{ + Service: config.Service{ + Name: "settings", + }, + Debug: config.Debug{ + Addr: "127.0.0.1:9194", + Token: "", + Pprof: false, + Zpages: false, + }, + HTTP: config.HTTP{ + Addr: "127.0.0.1:9190", + Namespace: "com.owncloud.web", + Root: "/", + CacheTTL: 604800, // 7 days + CORS: config.CORS{ + AllowedOrigins: []string{"*"}, + AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, + AllowedHeaders: []string{"Authorization", "Origin", "Content-Type", "Accept", "X-Requested-With"}, + AllowCredentials: true, + }, + }, + GRPC: config.GRPC{ + Addr: "127.0.0.1:9191", + Namespace: "com.owncloud.api", + }, + StoreType: "metadata", // use metadata or filesystem + DataPath: path.Join(defaults.BaseDataPath(), "settings"), + Asset: config.Asset{ + Path: "", + }, + SetupDefaultAssignments: false, + Metadata: config.Metadata{ + GatewayAddress: "127.0.0.1:9215", // system storage + StorageAddress: "127.0.0.1:9215", + SystemUserIDP: "internal", + }, + } +} + +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.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{} + } + + if cfg.Metadata.SystemUserAPIKey == "" && cfg.Commons != nil && cfg.Commons.SystemUserAPIKey != "" { + cfg.Metadata.SystemUserAPIKey = cfg.Commons.SystemUserAPIKey + } + + if cfg.Metadata.SystemUserID == "" && cfg.Commons != nil && cfg.Commons.SystemUserID != "" { + cfg.Metadata.SystemUserID = cfg.Commons.SystemUserID + } + + if cfg.AdminUserID == "" && cfg.Commons != nil { + cfg.AdminUserID = cfg.Commons.AdminUserID + } +} + +func Sanitize(cfg *config.Config) { + // sanitize config + if cfg.HTTP.Root != "/" { + cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") + } +} diff --git a/extensions/settings/pkg/config/grpc.go b/services/settings/pkg/config/grpc.go similarity index 100% rename from extensions/settings/pkg/config/grpc.go rename to services/settings/pkg/config/grpc.go diff --git a/extensions/settings/pkg/config/http.go b/services/settings/pkg/config/http.go similarity index 100% rename from extensions/settings/pkg/config/http.go rename to services/settings/pkg/config/http.go diff --git a/extensions/settings/pkg/config/log.go b/services/settings/pkg/config/log.go similarity index 100% rename from extensions/settings/pkg/config/log.go rename to services/settings/pkg/config/log.go diff --git a/services/settings/pkg/config/parser/parse.go b/services/settings/pkg/config/parser/parse.go new file mode 100644 index 00000000000..1e9071a78a4 --- /dev/null +++ b/services/settings/pkg/config/parser/parse.go @@ -0,0 +1,49 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/services/settings/pkg/config" + "github.com/owncloud/ocis/v2/services/settings/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + if cfg.TokenManager.JWTSecret == "" { + return shared.MissingJWTTokenError(cfg.Service.Name) + } + + if cfg.Metadata.SystemUserAPIKey == "" { + return shared.MissingSystemUserApiKeyError(cfg.Service.Name) + } + + if cfg.AdminUserID == "" { + return shared.MissingAdminUserID(cfg.Service.Name) + } + + return nil +} diff --git a/extensions/settings/pkg/config/reva.go b/services/settings/pkg/config/reva.go similarity index 100% rename from extensions/settings/pkg/config/reva.go rename to services/settings/pkg/config/reva.go diff --git a/extensions/settings/pkg/config/service.go b/services/settings/pkg/config/service.go similarity index 100% rename from extensions/settings/pkg/config/service.go rename to services/settings/pkg/config/service.go diff --git a/extensions/settings/pkg/config/tracing.go b/services/settings/pkg/config/tracing.go similarity index 100% rename from extensions/settings/pkg/config/tracing.go rename to services/settings/pkg/config/tracing.go diff --git a/services/settings/pkg/logging/logging.go b/services/settings/pkg/logging/logging.go new file mode 100644 index 00000000000..f12ce8916e4 --- /dev/null +++ b/services/settings/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/settings/pkg/config" +) + +// 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/settings/pkg/metrics/metrics.go b/services/settings/pkg/metrics/metrics.go similarity index 100% rename from extensions/settings/pkg/metrics/metrics.go rename to services/settings/pkg/metrics/metrics.go diff --git a/services/settings/pkg/server/debug/option.go b/services/settings/pkg/server/debug/option.go new file mode 100644 index 00000000000..bfafab5a490 --- /dev/null +++ b/services/settings/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/settings/pkg/config" +) + +// 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/services/settings/pkg/server/debug/server.go b/services/settings/pkg/server/debug/server.go new file mode 100644 index 00000000000..e1ed5ff2cbd --- /dev/null +++ b/services/settings/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/settings/pkg/config" +) + +// 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.GetString()), + 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/services/settings/pkg/server/grpc/option.go b/services/settings/pkg/server/grpc/option.go new file mode 100644 index 00000000000..b87b02813dc --- /dev/null +++ b/services/settings/pkg/server/grpc/option.go @@ -0,0 +1,76 @@ +package grpc + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/settings/pkg/config" + "github.com/owncloud/ocis/v2/services/settings/pkg/metrics" + "github.com/urfave/cli/v2" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Name string + Logger log.Logger + Context context.Context + Config *config.Config + Metrics *metrics.Metrics + Flags []cli.Flag +} + +// 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 + } +} + +// Name provides a name for the service. +func Name(val string) Option { + return func(o *Options) { + o.Name = 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 + } +} + +// Metrics provides a function to set the metrics option. +func Metrics(val *metrics.Metrics) Option { + return func(o *Options) { + o.Metrics = val + } +} + +// Flags provides a function to set the flags option. +func Flags(val []cli.Flag) Option { + return func(o *Options) { + o.Flags = append(o.Flags, val...) + } +} diff --git a/services/settings/pkg/server/grpc/server.go b/services/settings/pkg/server/grpc/server.go new file mode 100644 index 00000000000..b8690a3db7e --- /dev/null +++ b/services/settings/pkg/server/grpc/server.go @@ -0,0 +1,78 @@ +package grpc + +import ( + "context" + + permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" + "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" + svc "github.com/owncloud/ocis/v2/services/settings/pkg/service/v0" + "go-micro.dev/v4/api" + "go-micro.dev/v4/server" +) + +// Server initializes a new go-micro service ready to run +func Server(opts ...Option) grpc.Service { + options := newOptions(opts...) + + service := grpc.NewService( + grpc.Logger(options.Logger), + grpc.Name(options.Name), + grpc.Version(version.GetString()), + grpc.Address(options.Config.GRPC.Addr), + grpc.Namespace(options.Config.GRPC.Namespace), + grpc.Context(options.Context), + grpc.Flags(options.Flags...), + ) + + handle := svc.NewService(options.Config, options.Logger) + if err := settingssvc.RegisterBundleServiceHandler(service.Server(), handle); err != nil { + options.Logger.Fatal().Err(err).Msg("could not register Bundle service handler") + } + if err := settingssvc.RegisterValueServiceHandler(service.Server(), handle); err != nil { + options.Logger.Fatal().Err(err).Msg("could not register Value service handler") + } + if err := settingssvc.RegisterRoleServiceHandler(service.Server(), handle); err != nil { + options.Logger.Fatal().Err(err).Msg("could not register Role service handler") + } + if err := settingssvc.RegisterPermissionServiceHandler(service.Server(), handle); err != nil { + options.Logger.Fatal().Err(err).Msg("could not register Permission service handler") + } + + if err := RegisterCS3PermissionsServiceHandler(service.Server(), handle); err != nil { + options.Logger.Fatal().Err(err).Msg("could not register CS3 Permission service handler") + } + + return service +} + +func RegisterCS3PermissionsServiceHandler(s server.Server, hdlr permissions.PermissionsAPIServer, opts ...server.HandlerOption) error { + type permissionsService interface { + CheckPermission(context.Context, *permissions.CheckPermissionRequest, *permissions.CheckPermissionResponse) error + } + type PermissionsAPI struct { + permissionsService + } + h := &permissionsServiceHandler{hdlr} + opts = append(opts, api.WithEndpoint(&api.Endpoint{ + Name: "PermissionsService.Checkpermission", + Path: []string{"/api/v0/permissions/check-permission"}, + Method: []string{"POST"}, + Body: "*", + Handler: "rpc", + })) + return s.Handle(s.NewHandler(&PermissionsAPI{h}, opts...)) +} + +type permissionsServiceHandler struct { + api permissions.PermissionsAPIServer +} + +func (h *permissionsServiceHandler) CheckPermission(ctx context.Context, req *permissions.CheckPermissionRequest, res *permissions.CheckPermissionResponse) error { + r, err := h.api.CheckPermission(ctx, req) + if r != nil { + *res = *r + } + return err +} diff --git a/services/settings/pkg/server/http/option.go b/services/settings/pkg/server/http/option.go new file mode 100644 index 00000000000..e4537ffb12b --- /dev/null +++ b/services/settings/pkg/server/http/option.go @@ -0,0 +1,76 @@ +package http + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/settings/pkg/config" + "github.com/owncloud/ocis/v2/services/settings/pkg/metrics" + "github.com/urfave/cli/v2" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Name string + Logger log.Logger + Context context.Context + Config *config.Config + Metrics *metrics.Metrics + Flags []cli.Flag +} + +// 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 + } +} + +// Name provides a name for the service. +func Name(val string) Option { + return func(o *Options) { + o.Name = 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 + } +} + +// Metrics provides a function to set the metrics option. +func Metrics(val *metrics.Metrics) Option { + return func(o *Options) { + o.Metrics = val + } +} + +// Flags provides a function to set the flags option. +func Flags(val []cli.Flag) Option { + return func(o *Options) { + o.Flags = append(o.Flags, val...) + } +} diff --git a/services/settings/pkg/server/http/server.go b/services/settings/pkg/server/http/server.go new file mode 100644 index 00000000000..ec96b0f4d4b --- /dev/null +++ b/services/settings/pkg/server/http/server.go @@ -0,0 +1,85 @@ +package http + +import ( + "github.com/go-chi/chi/v5" + chimiddleware "github.com/go-chi/chi/v5/middleware" + "github.com/owncloud/ocis/v2/ocis-pkg/account" + "github.com/owncloud/ocis/v2/ocis-pkg/cors" + "github.com/owncloud/ocis/v2/ocis-pkg/middleware" + "github.com/owncloud/ocis/v2/ocis-pkg/service/http" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" + "github.com/owncloud/ocis/v2/services/settings/pkg/assets" + svc "github.com/owncloud/ocis/v2/services/settings/pkg/service/v0" + "go-micro.dev/v4" +) + +// Server initializes the http service and server. +func Server(opts ...Option) http.Service { + options := newOptions(opts...) + + service := http.NewService( + http.Logger(options.Logger), + http.Name(options.Name), + http.Version(version.GetString()), + http.Address(options.Config.HTTP.Addr), + http.Namespace(options.Config.HTTP.Namespace), + http.Context(options.Context), + http.Flags(options.Flags...), + ) + + handle := svc.NewService(options.Config, options.Logger) + + { + handle = svc.NewInstrument(handle, options.Metrics) + handle = svc.NewLogging(handle, options.Logger) + handle = svc.NewTracing(handle) + } + + mux := chi.NewMux() + + mux.Use(chimiddleware.RealIP) + mux.Use(chimiddleware.RequestID) + mux.Use(middleware.NoCache) + mux.Use(middleware.Cors( + cors.Logger(options.Logger), + cors.AllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins), + cors.AllowedMethods(options.Config.HTTP.CORS.AllowedMethods), + cors.AllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders), + cors.AllowCredentials(options.Config.HTTP.CORS.AllowCredentials), + )) + mux.Use(middleware.Secure) + mux.Use(middleware.ExtractAccountUUID( + account.Logger(options.Logger), + account.JWTSecret(options.Config.TokenManager.JWTSecret)), + ) + + mux.Use(middleware.Version( + options.Name, + version.GetString(), + )) + + mux.Use(middleware.Logger( + options.Logger, + )) + + mux.Use(middleware.Static( + options.Config.HTTP.Root, + assets.New( + assets.Logger(options.Logger), + assets.Config(options.Config), + ), + options.Config.HTTP.CacheTTL, + )) + + mux.Route(options.Config.HTTP.Root, func(r chi.Router) { + settingssvc.RegisterBundleServiceWeb(r, handle) + settingssvc.RegisterValueServiceWeb(r, handle) + settingssvc.RegisterRoleServiceWeb(r, handle) + settingssvc.RegisterPermissionServiceWeb(r, handle) + }) + + micro.RegisterHandler(service.Server(), mux) + + return service +} diff --git a/services/settings/pkg/service/v0/instrument.go b/services/settings/pkg/service/v0/instrument.go new file mode 100644 index 00000000000..882a1e1840b --- /dev/null +++ b/services/settings/pkg/service/v0/instrument.go @@ -0,0 +1,13 @@ +package svc + +import ( + "github.com/owncloud/ocis/v2/services/settings/pkg/metrics" +) + +// NewInstrument returns a service that instruments metrics. +func NewInstrument(next Service, metrics *metrics.Metrics) Service { + return Service{ + manager: next.manager, + config: next.config, + } +} diff --git a/extensions/settings/pkg/service/v0/logging.go b/services/settings/pkg/service/v0/logging.go similarity index 100% rename from extensions/settings/pkg/service/v0/logging.go rename to services/settings/pkg/service/v0/logging.go diff --git a/services/settings/pkg/service/v0/option.go b/services/settings/pkg/service/v0/option.go new file mode 100644 index 00000000000..00a3b39410c --- /dev/null +++ b/services/settings/pkg/service/v0/option.go @@ -0,0 +1,39 @@ +package svc + +import ( + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/settings/pkg/config" +) + +// 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 + Config *config.Config + Middleware []func(http.Handler) http.Handler +} + +// Logger provides a function to set the logger option. +func Logger(val log.Logger) Option { + return func(o *Options) { + o.Logger = val + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} + +// Middleware provides a function to set the middleware option. +func Middleware(val ...func(http.Handler) http.Handler) Option { + return func(o *Options) { + o.Middleware = val + } +} diff --git a/extensions/settings/pkg/service/v0/permissions.go b/services/settings/pkg/service/v0/permissions.go similarity index 100% rename from extensions/settings/pkg/service/v0/permissions.go rename to services/settings/pkg/service/v0/permissions.go diff --git a/services/settings/pkg/service/v0/service.go b/services/settings/pkg/service/v0/service.go new file mode 100644 index 00000000000..2c08461aed5 --- /dev/null +++ b/services/settings/pkg/service/v0/service.go @@ -0,0 +1,540 @@ +package svc + +import ( + "context" + "errors" + "fmt" + + permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" + rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + "github.com/cs3org/reva/v2/pkg/rgrpc/status" + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/middleware" + "github.com/owncloud/ocis/v2/ocis-pkg/roles" + settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" + settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" + "github.com/owncloud/ocis/v2/services/settings/pkg/config" + "github.com/owncloud/ocis/v2/services/settings/pkg/settings" + filestore "github.com/owncloud/ocis/v2/services/settings/pkg/store/filesystem" + metastore "github.com/owncloud/ocis/v2/services/settings/pkg/store/metadata" + merrors "go-micro.dev/v4/errors" + "go-micro.dev/v4/metadata" + "google.golang.org/protobuf/types/known/emptypb" +) + +// Service represents a service. +type Service struct { + id string + config *config.Config + logger log.Logger + manager settings.Manager +} + +// NewService returns a service implementation for Service. +func NewService(cfg *config.Config, logger log.Logger) Service { + service := Service{ + id: "ocis-settings", + config: cfg, + logger: logger, + } + + switch cfg.StoreType { + default: + fallthrough + case "metadata": + service.manager = metastore.New(cfg) + case "filesystem": + service.manager = filestore.New(cfg) + // TODO: if we want to further support filesystem store it should use default permissions from store/defaults/defaults.go instead using this duplicate + service.RegisterDefaultRoles() + } + return service +} + +func (g Service) CheckPermission(ctx context.Context, req *permissions.CheckPermissionRequest) (*permissions.CheckPermissionResponse, error) { + spec := req.SubjectRef.Spec + + var accountID string + switch ref := spec.(type) { + case *permissions.SubjectReference_UserId: + accountID = ref.UserId.OpaqueId + case *permissions.SubjectReference_GroupId: + accountID = ref.GroupId.OpaqueId + } + + assignments, err := g.manager.ListRoleAssignments(accountID) + if err != nil { + return &permissions.CheckPermissionResponse{ + Status: status.NewInternal(ctx, err.Error()), + }, nil + } + + roleIDs := make([]string, 0, len(assignments)) + for _, a := range assignments { + roleIDs = append(roleIDs, a.RoleId) + } + + permission, err := g.manager.ReadPermissionByName(req.Permission, roleIDs) + if err != nil { + if !errors.Is(err, settings.ErrPermissionNotFound) { + return &permissions.CheckPermissionResponse{ + Status: status.NewInternal(ctx, err.Error()), + }, nil + } + } + + if permission == nil { + return &permissions.CheckPermissionResponse{ + Status: &rpcv1beta1.Status{ + Code: rpcv1beta1.Code_CODE_PERMISSION_DENIED, + }, + }, nil + } + + return &permissions.CheckPermissionResponse{ + Status: status.NewOK(ctx), + }, nil +} + +// RegisterDefaultRoles composes default roles and saves them. Skipped if the roles already exist. +func (g Service) RegisterDefaultRoles() { + // FIXME: we're writing default roles per service start (i.e. twice at the moment, for http and grpc server). has to happen only once. + for _, role := range generateBundlesDefaultRoles() { + bundleID := role.Extension + "." + role.Id + // check if the role already exists + bundle, _ := g.manager.ReadBundle(role.Id) + if bundle != nil { + g.logger.Debug().Str("bundleID", bundleID).Msg("bundle already exists. skipping.") + continue + } + // create the role + _, err := g.manager.WriteBundle(role) + if err != nil { + g.logger.Error().Err(err).Str("bundleID", bundleID).Msg("failed to register bundle") + } + g.logger.Debug().Str("bundleID", bundleID).Msg("successfully registered bundle") + } + + for _, req := range generatePermissionRequests() { + _, err := g.manager.AddSettingToBundle(req.GetBundleId(), req.GetSetting()) + if err != nil { + g.logger.Error(). + Err(err). + Str("bundleID", req.GetBundleId()). + Interface("setting", req.GetSetting()). + Msg("failed to register permission") + } + } + + if g.config.SetupDefaultAssignments { + for _, req := range g.defaultRoleAssignments() { + if _, err := g.manager.WriteRoleAssignment(req.AccountUuid, req.RoleId); err != nil { + g.logger.Error().Err(err).Msg("failed to register role assignment") + } + } + } +} + +// TODO: check permissions on every request + +// SaveBundle implements the BundleServiceHandler interface +func (g Service) SaveBundle(ctx context.Context, req *settingssvc.SaveBundleRequest, res *settingssvc.SaveBundleResponse) error { + cleanUpResource(ctx, req.Bundle.Resource) + if err := g.checkStaticPermissionsByBundleType(ctx, req.Bundle.Type); err != nil { + return err + } + if validationError := validateSaveBundle(req); validationError != nil { + return merrors.BadRequest(g.id, "%s", validationError) + } + + r, err := g.manager.WriteBundle(req.Bundle) + if err != nil { + return merrors.BadRequest(g.id, "%s", err) + } + res.Bundle = r + return nil +} + +// GetBundle implements the BundleServiceHandler interface +func (g Service) GetBundle(ctx context.Context, req *settingssvc.GetBundleRequest, res *settingssvc.GetBundleResponse) error { + if validationError := validateGetBundle(req); validationError != nil { + return merrors.BadRequest(g.id, "%s", validationError) + } + bundle, err := g.manager.ReadBundle(req.BundleId) + if err != nil { + return merrors.NotFound(g.id, "%s", err) + } + filteredBundle := g.getFilteredBundle(g.getRoleIDs(ctx), bundle) + if len(filteredBundle.Settings) == 0 { + err = fmt.Errorf("could not read bundle: %s", req.BundleId) + return merrors.NotFound(g.id, "%s", err) + } + res.Bundle = filteredBundle + return nil +} + +// ListBundles implements the BundleServiceHandler interface +func (g Service) ListBundles(ctx context.Context, req *settingssvc.ListBundlesRequest, res *settingssvc.ListBundlesResponse) error { + // fetch all bundles + if validationError := validateListBundles(req); validationError != nil { + return merrors.BadRequest(g.id, "%s", validationError) + } + bundles, err := g.manager.ListBundles(settingsmsg.Bundle_TYPE_DEFAULT, req.BundleIds) + if err != nil { + return merrors.NotFound(g.id, "%s", err) + } + roleIDs := g.getRoleIDs(ctx) + + // filter settings in bundles that are allowed according to roles + var filteredBundles []*settingsmsg.Bundle + for _, bundle := range bundles { + filteredBundle := g.getFilteredBundle(roleIDs, bundle) + if len(filteredBundle.Settings) > 0 { + filteredBundles = append(filteredBundles, filteredBundle) + } + } + + res.Bundles = filteredBundles + return nil +} + +func (g Service) getFilteredBundle(roleIDs []string, bundle *settingsmsg.Bundle) *settingsmsg.Bundle { + // check if full bundle is whitelisted + bundleResource := &settingsmsg.Resource{ + Type: settingsmsg.Resource_TYPE_BUNDLE, + Id: bundle.Id, + } + if g.hasPermission( + roleIDs, + bundleResource, + []settingsmsg.Permission_Operation{settingsmsg.Permission_OPERATION_READ, settingsmsg.Permission_OPERATION_READWRITE}, + settingsmsg.Permission_CONSTRAINT_OWN, + ) { + return bundle + } + + // filter settings based on permissions + var filteredSettings []*settingsmsg.Setting + for _, setting := range bundle.Settings { + settingResource := &settingsmsg.Resource{ + Type: settingsmsg.Resource_TYPE_SETTING, + Id: setting.Id, + } + if g.hasPermission( + roleIDs, + settingResource, + []settingsmsg.Permission_Operation{settingsmsg.Permission_OPERATION_READ, settingsmsg.Permission_OPERATION_READWRITE}, + settingsmsg.Permission_CONSTRAINT_OWN, + ) { + filteredSettings = append(filteredSettings, setting) + } + } + bundle.Settings = filteredSettings + return bundle +} + +// AddSettingToBundle implements the BundleServiceHandler interface +func (g Service) AddSettingToBundle(ctx context.Context, req *settingssvc.AddSettingToBundleRequest, res *settingssvc.AddSettingToBundleResponse) error { + cleanUpResource(ctx, req.Setting.Resource) + if err := g.checkStaticPermissionsByBundleID(ctx, req.BundleId); err != nil { + return err + } + if validationError := validateAddSettingToBundle(req); validationError != nil { + return merrors.BadRequest(g.id, "%s", validationError) + } + + r, err := g.manager.AddSettingToBundle(req.BundleId, req.Setting) + if err != nil { + return merrors.BadRequest(g.id, "%s", err) + } + res.Setting = r + return nil +} + +// RemoveSettingFromBundle implements the BundleServiceHandler interface +func (g Service) RemoveSettingFromBundle(ctx context.Context, req *settingssvc.RemoveSettingFromBundleRequest, _ *emptypb.Empty) error { + if err := g.checkStaticPermissionsByBundleID(ctx, req.BundleId); err != nil { + return err + } + if validationError := validateRemoveSettingFromBundle(req); validationError != nil { + return merrors.BadRequest(g.id, "%s", validationError) + } + + if err := g.manager.RemoveSettingFromBundle(req.BundleId, req.SettingId); err != nil { + return merrors.BadRequest(g.id, "%s", err) + } + + return nil +} + +// SaveValue implements the ValueServiceHandler interface +func (g Service) SaveValue(ctx context.Context, req *settingssvc.SaveValueRequest, res *settingssvc.SaveValueResponse) error { + req.Value.AccountUuid = getValidatedAccountUUID(ctx, req.Value.AccountUuid) + cleanUpResource(ctx, req.Value.Resource) + // TODO: we need to check, if the authenticated user has permission to write the value for the specified resource (e.g. global, file with id xy, ...) + if validationError := validateSaveValue(req); validationError != nil { + return merrors.BadRequest(g.id, "%s", validationError) + } + r, err := g.manager.WriteValue(req.Value) + if err != nil { + return merrors.BadRequest(g.id, "%s", err) + } + valueWithIdentifier, err := g.getValueWithIdentifier(r) + if err != nil { + return merrors.NotFound(g.id, "%s", err) + } + res.Value = valueWithIdentifier + return nil +} + +// GetValue implements the ValueServiceHandler interface +func (g Service) GetValue(ctx context.Context, req *settingssvc.GetValueRequest, res *settingssvc.GetValueResponse) error { + if validationError := validateGetValue(req); validationError != nil { + return merrors.BadRequest(g.id, "%s", validationError) + } + r, err := g.manager.ReadValue(req.Id) + if err != nil { + return merrors.NotFound(g.id, "%s", err) + } + valueWithIdentifier, err := g.getValueWithIdentifier(r) + if err != nil { + return merrors.NotFound(g.id, "%s", err) + } + res.Value = valueWithIdentifier + return nil +} + +// GetValueByUniqueIdentifiers implements the ValueService interface +func (g Service) GetValueByUniqueIdentifiers(ctx context.Context, req *settingssvc.GetValueByUniqueIdentifiersRequest, res *settingssvc.GetValueResponse) error { + if validationError := validateGetValueByUniqueIdentifiers(req); validationError != nil { + return merrors.BadRequest(g.id, "%s", validationError) + } + v, err := g.manager.ReadValueByUniqueIdentifiers(req.AccountUuid, req.SettingId) + if err != nil { + return merrors.NotFound(g.id, "%s", err) + } + + if v.BundleId != "" { + valueWithIdentifier, err := g.getValueWithIdentifier(v) + if err != nil { + return merrors.NotFound(g.id, "%s", err) + } + + res.Value = valueWithIdentifier + } + return nil +} + +// ListValues implements the ValueServiceHandler interface +func (g Service) ListValues(ctx context.Context, req *settingssvc.ListValuesRequest, res *settingssvc.ListValuesResponse) error { + req.AccountUuid = getValidatedAccountUUID(ctx, req.AccountUuid) + if validationError := validateListValues(req); validationError != nil { + return merrors.BadRequest(g.id, "%s", validationError) + } + r, err := g.manager.ListValues(req.BundleId, req.AccountUuid) + if err != nil { + return merrors.NotFound(g.id, "%s", err) + } + var result []*settingsmsg.ValueWithIdentifier + for _, value := range r { + valueWithIdentifier, err := g.getValueWithIdentifier(value) + if err == nil { + result = append(result, valueWithIdentifier) + } + } + res.Values = result + return nil +} + +// ListRoles implements the RoleServiceHandler interface +func (g Service) ListRoles(c context.Context, req *settingssvc.ListBundlesRequest, res *settingssvc.ListBundlesResponse) error { + //accountUUID := getValidatedAccountUUID(c, "me") + if validationError := validateListRoles(req); validationError != nil { + return merrors.BadRequest(g.id, "%s", validationError) + } + r, err := g.manager.ListBundles(settingsmsg.Bundle_TYPE_ROLE, req.BundleIds) + if err != nil { + return merrors.NotFound(g.id, "%s", err) + } + // TODO: only allow to list roles when user has account/role/... management permissions + res.Bundles = r + return nil +} + +// ListRoleAssignments implements the RoleServiceHandler interface +func (g Service) ListRoleAssignments(ctx context.Context, req *settingssvc.ListRoleAssignmentsRequest, res *settingssvc.ListRoleAssignmentsResponse) error { + req.AccountUuid = getValidatedAccountUUID(ctx, req.AccountUuid) + if validationError := validateListRoleAssignments(req); validationError != nil { + return merrors.BadRequest(g.id, "%s", validationError) + } + r, err := g.manager.ListRoleAssignments(req.AccountUuid) + if err != nil { + return merrors.NotFound(g.id, "%s", err) + } + res.Assignments = r + return nil +} + +// AssignRoleToUser implements the RoleServiceHandler interface +func (g Service) AssignRoleToUser(ctx context.Context, req *settingssvc.AssignRoleToUserRequest, res *settingssvc.AssignRoleToUserResponse) error { + if err := g.checkStaticPermissionsByBundleType(ctx, settingsmsg.Bundle_TYPE_ROLE); err != nil { + return err + } + + req.AccountUuid = getValidatedAccountUUID(ctx, req.AccountUuid) + if validationError := validateAssignRoleToUser(req); validationError != nil { + return merrors.BadRequest(g.id, "%s", validationError) + } + r, err := g.manager.WriteRoleAssignment(req.AccountUuid, req.RoleId) + if err != nil { + return merrors.BadRequest(g.id, "%s", err) + } + res.Assignment = r + return nil +} + +// RemoveRoleFromUser implements the RoleServiceHandler interface +func (g Service) RemoveRoleFromUser(ctx context.Context, req *settingssvc.RemoveRoleFromUserRequest, _ *emptypb.Empty) error { + if err := g.checkStaticPermissionsByBundleType(ctx, settingsmsg.Bundle_TYPE_ROLE); err != nil { + return err + } + + if validationError := validateRemoveRoleFromUser(req); validationError != nil { + return merrors.BadRequest(g.id, "%s", validationError) + } + if err := g.manager.RemoveRoleAssignment(req.Id); err != nil { + return merrors.BadRequest(g.id, "%s", err) + } + return nil +} + +// ListPermissionsByResource implements the PermissionServiceHandler interface +func (g Service) ListPermissionsByResource(ctx context.Context, req *settingssvc.ListPermissionsByResourceRequest, res *settingssvc.ListPermissionsByResourceResponse) error { + if validationError := validateListPermissionsByResource(req); validationError != nil { + return merrors.BadRequest(g.id, "%s", validationError) + } + permissions, err := g.manager.ListPermissionsByResource(req.Resource, g.getRoleIDs(ctx)) + if err != nil { + return merrors.BadRequest(g.id, "%s", err) + } + res.Permissions = permissions + return nil +} + +// GetPermissionByID implements the PermissionServiceHandler interface +func (g Service) GetPermissionByID(ctx context.Context, req *settingssvc.GetPermissionByIDRequest, res *settingssvc.GetPermissionByIDResponse) error { + if validationError := validateGetPermissionByID(req); validationError != nil { + return merrors.BadRequest(g.id, "%s", validationError) + } + permission, err := g.manager.ReadPermissionByID(req.PermissionId, g.getRoleIDs(ctx)) + if err != nil { + return merrors.BadRequest(g.id, "%s", err) + } + if permission == nil { + return merrors.NotFound(g.id, "%s", fmt.Errorf("permission %s not found in roles", req.PermissionId)) + } + res.Permission = permission + return nil +} + +// cleanUpResource makes sure that the account uuid of the authenticated user is injected if needed. +func cleanUpResource(ctx context.Context, resource *settingsmsg.Resource) { + if resource != nil && resource.Type == settingsmsg.Resource_TYPE_USER { + resource.Id = getValidatedAccountUUID(ctx, resource.Id) + } +} + +// getValidatedAccountUUID converts `me` into an actual account uuid from the context, if possible. +// the result of this function will always be a valid lower-case UUID or an empty string. +func getValidatedAccountUUID(ctx context.Context, accountUUID string) string { + if accountUUID == "me" { + if ownAccountUUID, ok := metadata.Get(ctx, middleware.AccountID); ok { + accountUUID = ownAccountUUID + } + } + if accountUUID == "me" { + // no matter what happens above, an accountUUID of `me` must not be passed on. Clear it instead. + accountUUID = "" + } + return accountUUID +} + +// getRoleIDs extracts the roleIDs of the authenticated user from the context. +func (g Service) getRoleIDs(ctx context.Context) []string { + var ownRoleIDs []string + if ownRoleIDs, ok := roles.ReadRoleIDsFromContext(ctx); ok { + return ownRoleIDs + } + if accountID, ok := metadata.Get(ctx, middleware.AccountID); ok { + assignments, err := g.manager.ListRoleAssignments(accountID) + if err != nil { + g.logger.Info().Err(err).Str("userid", accountID).Msg("failed to get roles for user") + return []string{} + } + + for _, a := range assignments { + ownRoleIDs = append(ownRoleIDs, a.RoleId) + } + return ownRoleIDs + } + g.logger.Info().Msg("failed to get accountID from context") + return []string{} +} + +func (g Service) getValueWithIdentifier(value *settingsmsg.Value) (*settingsmsg.ValueWithIdentifier, error) { + bundle, err := g.manager.ReadBundle(value.BundleId) + if err != nil { + return nil, err + } + setting, err := g.manager.ReadSetting(value.SettingId) + if err != nil { + return nil, err + } + return &settingsmsg.ValueWithIdentifier{ + Identifier: &settingsmsg.Identifier{ + Extension: bundle.Extension, + Bundle: bundle.Name, + Setting: setting.Name, + }, + Value: value, + }, nil +} + +func (g Service) hasStaticPermission(ctx context.Context, permissionID string) bool { + roleIDs, ok := roles.ReadRoleIDsFromContext(ctx) + if !ok { + /** + * FIXME: with this we are skipping permission checks on all requests that are coming in without roleIDs in the + * metadata context. This is a huge security impairment, as that's the case not only for grpc requests but also + * for unauthenticated http requests and http requests coming in without hitting the ocis-proxy first. + */ + // TODO add system role for internal requests. + // - at least the proxy needs to look up account info + // - glauth needs to make bind requests + // tracked as OCIS-454 + return true + } + p, err := g.manager.ReadPermissionByID(permissionID, roleIDs) + return err == nil && p != nil +} + +func (g Service) checkStaticPermissionsByBundleID(ctx context.Context, bundleID string) error { + bundle, err := g.manager.ReadBundle(bundleID) + if err != nil { + return merrors.NotFound(g.id, "bundle not found: %s", err) + } + return g.checkStaticPermissionsByBundleType(ctx, bundle.Type) +} + +func (g Service) checkStaticPermissionsByBundleType(ctx context.Context, bundleType settingsmsg.Bundle_Type) error { + if bundleType == settingsmsg.Bundle_TYPE_ROLE { + if !g.hasStaticPermission(ctx, RoleManagementPermissionID) { + return merrors.Forbidden(g.id, "user has no role management permission") + } + return nil + } + if !g.hasStaticPermission(ctx, SettingsManagementPermissionID) { + return merrors.Forbidden(g.id, "user has no settings management permission") + } + return nil +} diff --git a/extensions/settings/pkg/service/v0/service_test.go b/services/settings/pkg/service/v0/service_test.go similarity index 100% rename from extensions/settings/pkg/service/v0/service_test.go rename to services/settings/pkg/service/v0/service_test.go diff --git a/extensions/settings/pkg/service/v0/settings.go b/services/settings/pkg/service/v0/settings.go similarity index 100% rename from extensions/settings/pkg/service/v0/settings.go rename to services/settings/pkg/service/v0/settings.go diff --git a/extensions/settings/pkg/service/v0/tracing.go b/services/settings/pkg/service/v0/tracing.go similarity index 100% rename from extensions/settings/pkg/service/v0/tracing.go rename to services/settings/pkg/service/v0/tracing.go diff --git a/extensions/settings/pkg/service/v0/validator.go b/services/settings/pkg/service/v0/validator.go similarity index 100% rename from extensions/settings/pkg/service/v0/validator.go rename to services/settings/pkg/service/v0/validator.go diff --git a/extensions/settings/pkg/settings/settings.go b/services/settings/pkg/settings/settings.go similarity index 97% rename from extensions/settings/pkg/settings/settings.go rename to services/settings/pkg/settings/settings.go index 5037fa1b27f..19658ed0e43 100644 --- a/extensions/settings/pkg/settings/settings.go +++ b/services/settings/pkg/settings/settings.go @@ -3,8 +3,8 @@ package settings import ( "errors" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/config" settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" + "github.com/owncloud/ocis/v2/services/settings/pkg/config" ) var ( diff --git a/extensions/settings/pkg/store/defaults/defaults.go b/services/settings/pkg/store/defaults/defaults.go similarity index 99% rename from extensions/settings/pkg/store/defaults/defaults.go rename to services/settings/pkg/store/defaults/defaults.go index e24c632550f..91173c657db 100644 --- a/extensions/settings/pkg/store/defaults/defaults.go +++ b/services/settings/pkg/store/defaults/defaults.go @@ -1,8 +1,8 @@ package defaults import ( - "github.com/owncloud/ocis/v2/extensions/settings/pkg/config" settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" + "github.com/owncloud/ocis/v2/services/settings/pkg/config" ) const ( diff --git a/extensions/settings/pkg/store/errortypes/errortypes.go b/services/settings/pkg/store/errortypes/errortypes.go similarity index 100% rename from extensions/settings/pkg/store/errortypes/errortypes.go rename to services/settings/pkg/store/errortypes/errortypes.go diff --git a/extensions/settings/pkg/store/filesystem/assignments.go b/services/settings/pkg/store/filesystem/assignments.go similarity index 100% rename from extensions/settings/pkg/store/filesystem/assignments.go rename to services/settings/pkg/store/filesystem/assignments.go diff --git a/extensions/settings/pkg/store/filesystem/assignments_test.go b/services/settings/pkg/store/filesystem/assignments_test.go similarity index 100% rename from extensions/settings/pkg/store/filesystem/assignments_test.go rename to services/settings/pkg/store/filesystem/assignments_test.go diff --git a/services/settings/pkg/store/filesystem/bundles.go b/services/settings/pkg/store/filesystem/bundles.go new file mode 100644 index 00000000000..50746fe1da9 --- /dev/null +++ b/services/settings/pkg/store/filesystem/bundles.go @@ -0,0 +1,180 @@ +// Package store implements the go-micro store interface +package store + +import ( + "fmt" + "io/ioutil" + "path/filepath" + "sync" + + "github.com/gofrs/uuid" + settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" + "github.com/owncloud/ocis/v2/services/settings/pkg/store/errortypes" +) + +var m = &sync.RWMutex{} + +// ListBundles returns all bundles in the dataPath folder that match the given type. +func (s Store) ListBundles(bundleType settingsmsg.Bundle_Type, bundleIDs []string) ([]*settingsmsg.Bundle, error) { + // FIXME: list requests should be ran against a cache, not FS + m.RLock() + defer m.RUnlock() + + bundlesFolder := s.buildFolderPathForBundles(false) + bundleFiles, err := ioutil.ReadDir(bundlesFolder) + if err != nil { + return []*settingsmsg.Bundle{}, nil + } + + records := make([]*settingsmsg.Bundle, 0, len(bundleFiles)) + for _, bundleFile := range bundleFiles { + record := settingsmsg.Bundle{} + err = s.parseRecordFromFile(&record, filepath.Join(bundlesFolder, bundleFile.Name())) + if err != nil { + s.Logger.Warn().Msgf("error reading %v", bundleFile) + continue + } + if record.Type != bundleType { + continue + } + if len(bundleIDs) > 0 && !containsStr(record.Id, bundleIDs) { + continue + } + records = append(records, &record) + } + + return records, nil +} + +// containsStr checks if the strs slice contains str +func containsStr(str string, strs []string) bool { + for _, s := range strs { + if s == str { + return true + } + } + return false +} + +// ReadBundle tries to find a bundle by the given id within the dataPath. +func (s Store) ReadBundle(bundleID string) (*settingsmsg.Bundle, error) { + m.RLock() + defer m.RUnlock() + + filePath := s.buildFilePathForBundle(bundleID, false) + record := settingsmsg.Bundle{} + if err := s.parseRecordFromFile(&record, filePath); err != nil { + return nil, err + } + + s.Logger.Debug().Msgf("read contents from file: %v", filePath) + return &record, nil +} + +// ReadSetting tries to find a setting by the given id within the dataPath. +func (s Store) ReadSetting(settingID string) (*settingsmsg.Setting, error) { + m.RLock() + defer m.RUnlock() + + bundles, err := s.ListBundles(settingsmsg.Bundle_TYPE_DEFAULT, []string{}) + if err != nil { + return nil, err + } + for _, bundle := range bundles { + for _, setting := range bundle.Settings { + if setting.Id == settingID { + return setting, nil + } + } + } + return nil, fmt.Errorf("could not read setting: %v", settingID) +} + +// WriteBundle writes the given record into a file within the dataPath. +func (s Store) WriteBundle(record *settingsmsg.Bundle) (*settingsmsg.Bundle, error) { + // FIXME: locking should happen on the file here, not globally. + m.Lock() + defer m.Unlock() + if record.Id == "" { + record.Id = uuid.Must(uuid.NewV4()).String() + } + filePath := s.buildFilePathForBundle(record.Id, true) + if err := s.writeRecordToFile(record, filePath); err != nil { + return nil, err + } + + s.Logger.Debug().Msgf("request contents written to file: %v", filePath) + return record, nil +} + +// AddSettingToBundle adds the given setting to the bundle with the given bundleID. +func (s Store) AddSettingToBundle(bundleID string, setting *settingsmsg.Setting) (*settingsmsg.Setting, error) { + bundle, err := s.ReadBundle(bundleID) + if err != nil { + if _, notFound := err.(errortypes.BundleNotFound); !notFound { + return nil, err + } + bundle = new(settingsmsg.Bundle) + bundle.Id = bundleID + bundle.Type = settingsmsg.Bundle_TYPE_DEFAULT + } + if setting.Id == "" { + setting.Id = uuid.Must(uuid.NewV4()).String() + } + setSetting(bundle, setting) + _, err = s.WriteBundle(bundle) + if err != nil { + return nil, err + } + return setting, nil +} + +// RemoveSettingFromBundle removes the setting from the bundle with the given ids. +func (s Store) RemoveSettingFromBundle(bundleID string, settingID string) error { + bundle, err := s.ReadBundle(bundleID) + if err != nil { + return nil + } + if ok := removeSetting(bundle, settingID); ok { + if _, err := s.WriteBundle(bundle); err != nil { + return err + } + } + return nil +} + +// indexOfSetting finds the index of the given setting within the given bundle. +// returns -1 if the setting was not found. +func indexOfSetting(bundle *settingsmsg.Bundle, settingID string) int { + for index := range bundle.Settings { + s := bundle.Settings[index] + if s.Id == settingID { + return index + } + } + return -1 +} + +// setSetting will append or overwrite the given setting within the given bundle +func setSetting(bundle *settingsmsg.Bundle, setting *settingsmsg.Setting) { + m.Lock() + defer m.Unlock() + index := indexOfSetting(bundle, setting.Id) + if index == -1 { + bundle.Settings = append(bundle.Settings, setting) + } else { + bundle.Settings[index] = setting + } +} + +// removeSetting will remove the given setting from the given bundle +func removeSetting(bundle *settingsmsg.Bundle, settingID string) bool { + m.Lock() + defer m.Unlock() + index := indexOfSetting(bundle, settingID) + if index == -1 { + return false + } + bundle.Settings = append(bundle.Settings[:index], bundle.Settings[index+1:]...) + return true +} diff --git a/extensions/settings/pkg/store/filesystem/bundles_test.go b/services/settings/pkg/store/filesystem/bundles_test.go similarity index 100% rename from extensions/settings/pkg/store/filesystem/bundles_test.go rename to services/settings/pkg/store/filesystem/bundles_test.go diff --git a/extensions/settings/pkg/store/filesystem/io.go b/services/settings/pkg/store/filesystem/io.go similarity index 93% rename from extensions/settings/pkg/store/filesystem/io.go rename to services/settings/pkg/store/filesystem/io.go index 2e59192fda6..38b5f5a6da0 100644 --- a/extensions/settings/pkg/store/filesystem/io.go +++ b/services/settings/pkg/store/filesystem/io.go @@ -4,7 +4,7 @@ import ( "io/ioutil" "os" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/store/errortypes" + "github.com/owncloud/ocis/v2/services/settings/pkg/store/errortypes" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" ) diff --git a/extensions/settings/pkg/store/filesystem/paths.go b/services/settings/pkg/store/filesystem/paths.go similarity index 100% rename from extensions/settings/pkg/store/filesystem/paths.go rename to services/settings/pkg/store/filesystem/paths.go diff --git a/services/settings/pkg/store/filesystem/permissions.go b/services/settings/pkg/store/filesystem/permissions.go new file mode 100644 index 00000000000..a01ba654680 --- /dev/null +++ b/services/settings/pkg/store/filesystem/permissions.go @@ -0,0 +1,72 @@ +package store + +import ( + settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" + "github.com/owncloud/ocis/v2/services/settings/pkg/settings" + "github.com/owncloud/ocis/v2/services/settings/pkg/util" +) + +// ListPermissionsByResource collects all permissions from the provided roleIDs that match the requested resource +func (s Store) ListPermissionsByResource(resource *settingsmsg.Resource, roleIDs []string) ([]*settingsmsg.Permission, error) { + records := make([]*settingsmsg.Permission, 0) + for _, roleID := range roleIDs { + role, err := s.ReadBundle(roleID) + if err != nil { + s.Logger.Debug().Str("roleID", roleID).Msg("role not found, skipping") + continue + } + records = append(records, extractPermissionsByResource(resource, role)...) + } + return records, nil +} + +// ReadPermissionByID finds the permission in the roles, specified by the provided roleIDs +func (s Store) ReadPermissionByID(permissionID string, roleIDs []string) (*settingsmsg.Permission, error) { + for _, roleID := range roleIDs { + role, err := s.ReadBundle(roleID) + if err != nil { + s.Logger.Debug().Str("roleID", roleID).Msg("role not found, skipping") + continue + } + for _, permission := range role.Settings { + if permission.Id == permissionID { + if value, ok := permission.Value.(*settingsmsg.Setting_PermissionValue); ok { + return value.PermissionValue, nil + } + } + } + } + return nil, nil +} + +// ReadPermissionByName finds the permission in the roles, specified by the provided roleIDs +func (s Store) ReadPermissionByName(name string, roleIDs []string) (*settingsmsg.Permission, error) { + for _, roleID := range roleIDs { + role, err := s.ReadBundle(roleID) + if err != nil { + s.Logger.Debug().Str("roleID", roleID).Msg("role not found, skipping") + continue + } + for _, permission := range role.Settings { + if permission.Name == name { + if value, ok := permission.Value.(*settingsmsg.Setting_PermissionValue); ok { + return value.PermissionValue, nil + } + } + } + } + return nil, settings.ErrPermissionNotFound +} + +// extractPermissionsByResource collects all permissions from the provided role that match the requested resource +func extractPermissionsByResource(resource *settingsmsg.Resource, role *settingsmsg.Bundle) []*settingsmsg.Permission { + permissions := make([]*settingsmsg.Permission, 0) + for _, setting := range role.Settings { + if value, ok := setting.Value.(*settingsmsg.Setting_PermissionValue); ok { + if util.IsResourceMatched(setting.Resource, resource) { + permissions = append(permissions, value.PermissionValue) + } + } + } + return permissions +} diff --git a/extensions/settings/pkg/store/filesystem/staticcheck.conf b/services/settings/pkg/store/filesystem/staticcheck.conf similarity index 100% rename from extensions/settings/pkg/store/filesystem/staticcheck.conf rename to services/settings/pkg/store/filesystem/staticcheck.conf diff --git a/services/settings/pkg/store/filesystem/store.go b/services/settings/pkg/store/filesystem/store.go new file mode 100644 index 00000000000..970e3e73ffc --- /dev/null +++ b/services/settings/pkg/store/filesystem/store.go @@ -0,0 +1,50 @@ +// Package store implements the go-micro store interface +package store + +import ( + "os" + + olog "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/settings/pkg/config" + "github.com/owncloud/ocis/v2/services/settings/pkg/settings" +) + +var ( + // Name is the default name for the settings store + Name = "ocis-settings" + managerName = "filesystem" +) + +// Store interacts with the filesystem to manage settings information +type Store struct { + dataPath string + Logger olog.Logger +} + +// New creates a new store +func New(cfg *config.Config) settings.Manager { + s := Store{ + //Logger: olog.NewLogger( + // olog.Color(cfg.Log.Color), + // olog.Pretty(cfg.Log.Pretty), + // olog.Level(cfg.Log.Level), + // olog.File(cfg.Log.File), + //), + } + + if _, err := os.Stat(cfg.DataPath); err != nil { + s.Logger.Info().Msgf("creating container on %v", cfg.DataPath) + err = os.MkdirAll(cfg.DataPath, 0700) + + if err != nil { + s.Logger.Err(err).Msgf("providing container on %v", cfg.DataPath) + } + } + + s.dataPath = cfg.DataPath + return &s +} + +func init() { + settings.Registry[managerName] = New +} diff --git a/extensions/settings/pkg/store/filesystem/store_test.go b/services/settings/pkg/store/filesystem/store_test.go similarity index 100% rename from extensions/settings/pkg/store/filesystem/store_test.go rename to services/settings/pkg/store/filesystem/store_test.go diff --git a/extensions/settings/pkg/store/filesystem/values.go b/services/settings/pkg/store/filesystem/values.go similarity index 100% rename from extensions/settings/pkg/store/filesystem/values.go rename to services/settings/pkg/store/filesystem/values.go diff --git a/extensions/settings/pkg/store/filesystem/values_test.go b/services/settings/pkg/store/filesystem/values_test.go similarity index 100% rename from extensions/settings/pkg/store/filesystem/values_test.go rename to services/settings/pkg/store/filesystem/values_test.go diff --git a/extensions/settings/pkg/store/metadata/assignments.go b/services/settings/pkg/store/metadata/assignments.go similarity index 100% rename from extensions/settings/pkg/store/metadata/assignments.go rename to services/settings/pkg/store/metadata/assignments.go diff --git a/extensions/settings/pkg/store/metadata/assignments_test.go b/services/settings/pkg/store/metadata/assignments_test.go similarity index 98% rename from extensions/settings/pkg/store/metadata/assignments_test.go rename to services/settings/pkg/store/metadata/assignments_test.go index 19165fac8da..18d246a46a4 100644 --- a/extensions/settings/pkg/store/metadata/assignments_test.go +++ b/services/settings/pkg/store/metadata/assignments_test.go @@ -6,10 +6,10 @@ import ( "testing" "github.com/gofrs/uuid" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/config/defaults" olog "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/ocis-pkg/shared" settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" + "github.com/owncloud/ocis/v2/services/settings/pkg/config/defaults" "github.com/stretchr/testify/require" ) diff --git a/services/settings/pkg/store/metadata/bundles.go b/services/settings/pkg/store/metadata/bundles.go new file mode 100644 index 00000000000..36cdd7c96df --- /dev/null +++ b/services/settings/pkg/store/metadata/bundles.go @@ -0,0 +1,146 @@ +// Package store implements the go-micro store interface +package store + +import ( + "context" + "encoding/json" + "errors" + "fmt" + + "github.com/gofrs/uuid" + settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" + "github.com/owncloud/ocis/v2/services/settings/pkg/store/defaults" +) + +// ListBundles returns all bundles in the dataPath folder that match the given type. +func (s *Store) ListBundles(bundleType settingsmsg.Bundle_Type, bundleIDs []string) ([]*settingsmsg.Bundle, error) { + // TODO: this is needed for initialization - we need to find a better way to fix this + if s.mdc == nil && len(bundleIDs) == 1 { + return defaultBundle(bundleType, bundleIDs[0]), nil + } + s.Init() + ctx := context.TODO() + + if len(bundleIDs) == 0 { + bIDs, err := s.mdc.ReadDir(ctx, bundleFolderLocation) + if err != nil { + return nil, err + } + + bundleIDs = bIDs + } + var bundles []*settingsmsg.Bundle + for _, id := range bundleIDs { + b, err := s.mdc.SimpleDownload(ctx, bundlePath(id)) + if err != nil { + return nil, err + } + + bundle := &settingsmsg.Bundle{} + err = json.Unmarshal(b, bundle) + if err != nil { + return nil, err + } + + if bundle.Type == bundleType { + bundles = append(bundles, bundle) + } + + } + return bundles, nil +} + +// ReadBundle tries to find a bundle by the given id from the metadata service +func (s *Store) ReadBundle(bundleID string) (*settingsmsg.Bundle, error) { + if s.mdc == nil { + return defaultBundle(settingsmsg.Bundle_TYPE_ROLE, bundleID)[0], nil + } + s.Init() + ctx := context.TODO() + b, err := s.mdc.SimpleDownload(ctx, bundlePath(bundleID)) + if err != nil { + return nil, err + } + + bundle := &settingsmsg.Bundle{} + return bundle, json.Unmarshal(b, bundle) +} + +// ReadSetting tries to find a setting by the given id from the metadata service +func (s *Store) ReadSetting(settingID string) (*settingsmsg.Setting, error) { + s.Init() + ctx := context.TODO() + + ids, err := s.mdc.ReadDir(ctx, bundleFolderLocation) + if err != nil { + return nil, err + } + + // TODO: avoid spamming metadata service + for _, id := range ids { + b, err := s.ReadBundle(id) + if err != nil { + return nil, err + } + + for _, setting := range b.Settings { + if setting.Id == settingID { + return setting, nil + } + } + + } + return nil, fmt.Errorf("setting '%s' not found", settingID) +} + +// WriteBundle sends the givens record to the metadataclient. returns `record` for legacy reasons +func (s *Store) WriteBundle(record *settingsmsg.Bundle) (*settingsmsg.Bundle, error) { + s.Init() + ctx := context.TODO() + + b, err := json.Marshal(record) + if err != nil { + return nil, err + } + return record, s.mdc.SimpleUpload(ctx, bundlePath(record.Id), b) +} + +// AddSettingToBundle adds the given setting to the bundle with the given bundleID. +func (s *Store) AddSettingToBundle(bundleID string, setting *settingsmsg.Setting) (*settingsmsg.Setting, error) { + s.Init() + b, err := s.ReadBundle(bundleID) + if err != nil { + // TODO: How to differentiate 'not found'? + b = new(settingsmsg.Bundle) + b.Id = bundleID + b.Type = settingsmsg.Bundle_TYPE_DEFAULT + } + + if setting.Id == "" { + setting.Id = uuid.Must(uuid.NewV4()).String() + } + + b.Settings = append(b.Settings, setting) + _, err = s.WriteBundle(b) + return setting, err +} + +// RemoveSettingFromBundle removes the setting from the bundle with the given ids. +func (s *Store) RemoveSettingFromBundle(bundleID string, settingID string) error { + fmt.Println("RemoveSettingFromBundle not implemented") + return errors.New("not implemented") +} + +func bundlePath(id string) string { + return fmt.Sprintf("%s/%s", bundleFolderLocation, id) +} + +func defaultBundle(bundleType settingsmsg.Bundle_Type, bundleID string) []*settingsmsg.Bundle { + var bundles []*settingsmsg.Bundle + for _, b := range defaults.GenerateBundlesDefaultRoles() { + if b.Type == bundleType && b.Id == bundleID { + bundles = append(bundles, b) + } + } + return bundles +} diff --git a/extensions/settings/pkg/store/metadata/bundles_test.go b/services/settings/pkg/store/metadata/bundles_test.go similarity index 100% rename from extensions/settings/pkg/store/metadata/bundles_test.go rename to services/settings/pkg/store/metadata/bundles_test.go diff --git a/extensions/settings/pkg/store/metadata/cache.go b/services/settings/pkg/store/metadata/cache.go similarity index 100% rename from extensions/settings/pkg/store/metadata/cache.go rename to services/settings/pkg/store/metadata/cache.go diff --git a/services/settings/pkg/store/metadata/permissions.go b/services/settings/pkg/store/metadata/permissions.go new file mode 100644 index 00000000000..9756db8d62f --- /dev/null +++ b/services/settings/pkg/store/metadata/permissions.go @@ -0,0 +1,72 @@ +package store + +import ( + settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" + "github.com/owncloud/ocis/v2/services/settings/pkg/settings" + "github.com/owncloud/ocis/v2/services/settings/pkg/util" +) + +// ListPermissionsByResource collects all permissions from the provided roleIDs that match the requested resource +func (s *Store) ListPermissionsByResource(resource *settingsmsg.Resource, roleIDs []string) ([]*settingsmsg.Permission, error) { + records := make([]*settingsmsg.Permission, 0) + for _, roleID := range roleIDs { + role, err := s.ReadBundle(roleID) + if err != nil { + s.Logger.Debug().Str("roleID", roleID).Msg("role not found, skipping") + continue + } + records = append(records, extractPermissionsByResource(resource, role)...) + } + return records, nil +} + +// ReadPermissionByID finds the permission in the roles, specified by the provided roleIDs +func (s *Store) ReadPermissionByID(permissionID string, roleIDs []string) (*settingsmsg.Permission, error) { + for _, roleID := range roleIDs { + role, err := s.ReadBundle(roleID) + if err != nil { + s.Logger.Debug().Str("roleID", roleID).Msg("role not found, skipping") + continue + } + for _, permission := range role.Settings { + if permission.Id == permissionID { + if value, ok := permission.Value.(*settingsmsg.Setting_PermissionValue); ok { + return value.PermissionValue, nil + } + } + } + } + return nil, nil +} + +// ReadPermissionByName finds the permission in the roles, specified by the provided roleIDs +func (s *Store) ReadPermissionByName(name string, roleIDs []string) (*settingsmsg.Permission, error) { + for _, roleID := range roleIDs { + role, err := s.ReadBundle(roleID) + if err != nil { + s.Logger.Debug().Str("roleID", roleID).Msg("role not found, skipping") + continue + } + for _, permission := range role.Settings { + if permission.Name == name { + if value, ok := permission.Value.(*settingsmsg.Setting_PermissionValue); ok { + return value.PermissionValue, nil + } + } + } + } + return nil, settings.ErrPermissionNotFound +} + +// extractPermissionsByResource collects all permissions from the provided role that match the requested resource +func extractPermissionsByResource(resource *settingsmsg.Resource, role *settingsmsg.Bundle) []*settingsmsg.Permission { + permissions := make([]*settingsmsg.Permission, 0) + for _, setting := range role.Settings { + if value, ok := setting.Value.(*settingsmsg.Setting_PermissionValue); ok { + if util.IsResourceMatched(setting.Resource, resource) { + permissions = append(permissions, value.PermissionValue) + } + } + } + return permissions +} diff --git a/extensions/settings/pkg/store/metadata/permissions_test.go b/services/settings/pkg/store/metadata/permissions_test.go similarity index 100% rename from extensions/settings/pkg/store/metadata/permissions_test.go rename to services/settings/pkg/store/metadata/permissions_test.go diff --git a/services/settings/pkg/store/metadata/store.go b/services/settings/pkg/store/metadata/store.go new file mode 100644 index 00000000000..654c3bba4c4 --- /dev/null +++ b/services/settings/pkg/store/metadata/store.go @@ -0,0 +1,155 @@ +// Package store implements the go-micro store interface +package store + +import ( + "context" + "encoding/json" + "log" + "sync" + + "github.com/cs3org/reva/v2/pkg/storage/utils/metadata" + "github.com/gofrs/uuid" + olog "github.com/owncloud/ocis/v2/ocis-pkg/log" + settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" + "github.com/owncloud/ocis/v2/services/settings/pkg/config" + "github.com/owncloud/ocis/v2/services/settings/pkg/settings" + "github.com/owncloud/ocis/v2/services/settings/pkg/store/defaults" +) + +var ( + // Name is the default name for the settings store + Name = "ocis-settings" + managerName = "metadata" + settingsSpaceID = "f1bdd61a-da7c-49fc-8203-0558109d1b4f" // uuid.Must(uuid.NewV4()).String() + rootFolderLocation = "settings" + bundleFolderLocation = "settings/bundles" + accountsFolderLocation = "settings/accounts" + valuesFolderLocation = "settings/values" +) + +// MetadataClient is the interface to talk to metadata service +type MetadataClient interface { + SimpleDownload(ctx context.Context, id string) ([]byte, error) + SimpleUpload(ctx context.Context, id string, content []byte) error + Delete(ctx context.Context, id string) error + ReadDir(ctx context.Context, id string) ([]string, error) + MakeDirIfNotExist(ctx context.Context, id string) error + Init(ctx context.Context, id string) error +} + +// Store interacts with the filesystem to manage settings information +type Store struct { + Logger olog.Logger + + mdc MetadataClient + cfg *config.Config + + l *sync.Mutex +} + +// Init initialize the store once, later calls are noops +func (s *Store) Init() { + if s.mdc != nil { + return + } + + s.l.Lock() + defer s.l.Unlock() + + if s.mdc != nil { + return + } + + mdc := &CachedMDC{next: NewMetadataClient(s.cfg.Metadata)} + if err := s.initMetadataClient(mdc); err != nil { + s.Logger.Error().Err(err).Msg("error initializing metadata client") + } +} + +// New creates a new store +func New(cfg *config.Config) settings.Manager { + s := Store{ + Logger: olog.NewLogger( + olog.Color(cfg.Log.Color), + olog.Pretty(cfg.Log.Pretty), + olog.Level(cfg.Log.Level), + olog.File(cfg.Log.File), + ), + cfg: cfg, + l: &sync.Mutex{}, + } + + return &s +} + +// NewMetadataClient returns the MetadataClient +func NewMetadataClient(cfg config.Metadata) MetadataClient { + mdc, err := metadata.NewCS3Storage(cfg.GatewayAddress, cfg.StorageAddress, cfg.SystemUserID, cfg.SystemUserIDP, cfg.SystemUserAPIKey) + if err != nil { + log.Fatal("error connecting to mdc:", err) + } + return mdc + +} + +// we need to lazy initialize the MetadataClient because metadata service might not be ready +func (s *Store) initMetadataClient(mdc MetadataClient) error { + ctx := context.TODO() + err := mdc.Init(ctx, settingsSpaceID) + if err != nil { + return err + } + + for _, p := range []string{ + rootFolderLocation, + accountsFolderLocation, + bundleFolderLocation, + valuesFolderLocation, + } { + err = mdc.MakeDirIfNotExist(ctx, p) + if err != nil { + return err + } + } + + for _, p := range defaults.GenerateBundlesDefaultRoles() { + b, err := json.Marshal(p) + if err != nil { + return err + } + err = mdc.SimpleUpload(ctx, bundlePath(p.Id), b) + if err != nil { + return err + } + } + + for _, p := range defaults.DefaultRoleAssignments(s.cfg) { + accountUUID := p.AccountUuid + roleID := p.RoleId + err = mdc.MakeDirIfNotExist(ctx, accountPath(accountUUID)) + if err != nil { + return err + } + + ass := &settingsmsg.UserRoleAssignment{ + Id: uuid.Must(uuid.NewV4()).String(), + AccountUuid: accountUUID, + RoleId: roleID, + } + b, err := json.Marshal(ass) + if err != nil { + return err + } + err = mdc.SimpleUpload(ctx, assignmentPath(accountUUID, ass.Id), b) + if err != nil { + return err + } + } + + s.mdc = mdc + return nil +} + +func init() { + settings.Registry[managerName] = New +} diff --git a/extensions/settings/pkg/store/metadata/store_test.go b/services/settings/pkg/store/metadata/store_test.go similarity index 97% rename from extensions/settings/pkg/store/metadata/store_test.go rename to services/settings/pkg/store/metadata/store_test.go index 67956298927..bb1c75ff892 100644 --- a/extensions/settings/pkg/store/metadata/store_test.go +++ b/services/settings/pkg/store/metadata/store_test.go @@ -4,7 +4,7 @@ import ( "context" "strings" - "github.com/owncloud/ocis/v2/extensions/settings/pkg/config/defaults" + "github.com/owncloud/ocis/v2/services/settings/pkg/config/defaults" ) const ( diff --git a/extensions/settings/pkg/store/metadata/values.go b/services/settings/pkg/store/metadata/values.go similarity index 100% rename from extensions/settings/pkg/store/metadata/values.go rename to services/settings/pkg/store/metadata/values.go diff --git a/extensions/settings/pkg/store/metadata/values_test.go b/services/settings/pkg/store/metadata/values_test.go similarity index 100% rename from extensions/settings/pkg/store/metadata/values_test.go rename to services/settings/pkg/store/metadata/values_test.go diff --git a/services/settings/pkg/store/registry.go b/services/settings/pkg/store/registry.go new file mode 100644 index 00000000000..7c777159826 --- /dev/null +++ b/services/settings/pkg/store/registry.go @@ -0,0 +1,7 @@ +package store + +import ( + // init filesystem store + _ "github.com/owncloud/ocis/v2/services/settings/pkg/store/filesystem" + _ "github.com/owncloud/ocis/v2/services/settings/pkg/store/metadata" +) diff --git a/services/settings/pkg/tracing/tracing.go b/services/settings/pkg/tracing/tracing.go new file mode 100644 index 00000000000..46a5bbd813e --- /dev/null +++ b/services/settings/pkg/tracing/tracing.go @@ -0,0 +1,23 @@ +package tracing + +import ( + pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/settings/pkg/config" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the settings service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config) error { + var err error + if cfg.Tracing.Enabled { + if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { + return err + } + } + + return nil +} diff --git a/extensions/settings/pkg/util/resource_helper.go b/services/settings/pkg/util/resource_helper.go similarity index 100% rename from extensions/settings/pkg/util/resource_helper.go rename to services/settings/pkg/util/resource_helper.go diff --git a/extensions/settings/pkg/util/resource_helper_test.go b/services/settings/pkg/util/resource_helper_test.go similarity index 100% rename from extensions/settings/pkg/util/resource_helper_test.go rename to services/settings/pkg/util/resource_helper_test.go diff --git a/extensions/settings/reflex.conf b/services/settings/reflex.conf similarity index 100% rename from extensions/settings/reflex.conf rename to services/settings/reflex.conf diff --git a/extensions/settings/rollup.config.js b/services/settings/rollup.config.js similarity index 100% rename from extensions/settings/rollup.config.js rename to services/settings/rollup.config.js diff --git a/extensions/settings/settings.go b/services/settings/settings.go similarity index 100% rename from extensions/settings/settings.go rename to services/settings/settings.go diff --git a/extensions/settings/ui/app.js b/services/settings/ui/app.js similarity index 100% rename from extensions/settings/ui/app.js rename to services/settings/ui/app.js diff --git a/extensions/settings/ui/client/settings/index.js b/services/settings/ui/client/settings/index.js similarity index 100% rename from extensions/settings/ui/client/settings/index.js rename to services/settings/ui/client/settings/index.js diff --git a/extensions/settings/ui/components/SettingsApp.vue b/services/settings/ui/components/SettingsApp.vue similarity index 100% rename from extensions/settings/ui/components/SettingsApp.vue rename to services/settings/ui/components/SettingsApp.vue diff --git a/extensions/settings/ui/components/SettingsBundle.vue b/services/settings/ui/components/SettingsBundle.vue similarity index 100% rename from extensions/settings/ui/components/SettingsBundle.vue rename to services/settings/ui/components/SettingsBundle.vue diff --git a/extensions/settings/ui/components/settings/SettingBoolean.vue b/services/settings/ui/components/settings/SettingBoolean.vue similarity index 100% rename from extensions/settings/ui/components/settings/SettingBoolean.vue rename to services/settings/ui/components/settings/SettingBoolean.vue diff --git a/extensions/settings/ui/components/settings/SettingMultiChoice.vue b/services/settings/ui/components/settings/SettingMultiChoice.vue similarity index 100% rename from extensions/settings/ui/components/settings/SettingMultiChoice.vue rename to services/settings/ui/components/settings/SettingMultiChoice.vue diff --git a/extensions/settings/ui/components/settings/SettingNumber.vue b/services/settings/ui/components/settings/SettingNumber.vue similarity index 100% rename from extensions/settings/ui/components/settings/SettingNumber.vue rename to services/settings/ui/components/settings/SettingNumber.vue diff --git a/extensions/settings/ui/components/settings/SettingSingleChoice.vue b/services/settings/ui/components/settings/SettingSingleChoice.vue similarity index 100% rename from extensions/settings/ui/components/settings/SettingSingleChoice.vue rename to services/settings/ui/components/settings/SettingSingleChoice.vue diff --git a/extensions/settings/ui/components/settings/SettingString.vue b/services/settings/ui/components/settings/SettingString.vue similarity index 100% rename from extensions/settings/ui/components/settings/SettingString.vue rename to services/settings/ui/components/settings/SettingString.vue diff --git a/extensions/settings/ui/components/settings/SettingUnknown.vue b/services/settings/ui/components/settings/SettingUnknown.vue similarity index 100% rename from extensions/settings/ui/components/settings/SettingUnknown.vue rename to services/settings/ui/components/settings/SettingUnknown.vue diff --git a/extensions/settings/ui/dictionary.js b/services/settings/ui/dictionary.js similarity index 100% rename from extensions/settings/ui/dictionary.js rename to services/settings/ui/dictionary.js diff --git a/extensions/settings/ui/store/index.js b/services/settings/ui/store/index.js similarity index 100% rename from extensions/settings/ui/store/index.js rename to services/settings/ui/store/index.js diff --git a/extensions/settings/ui/tests/acceptance/features/settings.feature b/services/settings/ui/tests/acceptance/features/settings.feature similarity index 100% rename from extensions/settings/ui/tests/acceptance/features/settings.feature rename to services/settings/ui/tests/acceptance/features/settings.feature diff --git a/extensions/settings/ui/tests/acceptance/helpers/language.js b/services/settings/ui/tests/acceptance/helpers/language.js similarity index 100% rename from extensions/settings/ui/tests/acceptance/helpers/language.js rename to services/settings/ui/tests/acceptance/helpers/language.js diff --git a/extensions/settings/ui/tests/acceptance/pageobjects/filesPageSettingsContext.js b/services/settings/ui/tests/acceptance/pageobjects/filesPageSettingsContext.js similarity index 100% rename from extensions/settings/ui/tests/acceptance/pageobjects/filesPageSettingsContext.js rename to services/settings/ui/tests/acceptance/pageobjects/filesPageSettingsContext.js diff --git a/extensions/settings/ui/tests/acceptance/pageobjects/settingsPage.js b/services/settings/ui/tests/acceptance/pageobjects/settingsPage.js similarity index 100% rename from extensions/settings/ui/tests/acceptance/pageobjects/settingsPage.js rename to services/settings/ui/tests/acceptance/pageobjects/settingsPage.js diff --git a/extensions/settings/ui/tests/acceptance/stepDefinitions/settingsContext.js b/services/settings/ui/tests/acceptance/stepDefinitions/settingsContext.js similarity index 100% rename from extensions/settings/ui/tests/acceptance/stepDefinitions/settingsContext.js rename to services/settings/ui/tests/acceptance/stepDefinitions/settingsContext.js diff --git a/extensions/settings/ui/tests/run-acceptance-test.sh b/services/settings/ui/tests/run-acceptance-test.sh similarity index 100% rename from extensions/settings/ui/tests/run-acceptance-test.sh rename to services/settings/ui/tests/run-acceptance-test.sh diff --git a/extensions/settings/yarn.lock b/services/settings/yarn.lock similarity index 100% rename from extensions/settings/yarn.lock rename to services/settings/yarn.lock diff --git a/extensions/sharing/Makefile b/services/sharing/Makefile similarity index 100% rename from extensions/sharing/Makefile rename to services/sharing/Makefile diff --git a/services/sharing/cmd/sharing/main.go b/services/sharing/cmd/sharing/main.go new file mode 100644 index 00000000000..54e0996e3fd --- /dev/null +++ b/services/sharing/cmd/sharing/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/sharing/pkg/command" + "github.com/owncloud/ocis/v2/services/sharing/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/services/sharing/pkg/command/health.go b/services/sharing/pkg/command/health.go new file mode 100644 index 00000000000..1c2bb37ed45 --- /dev/null +++ b/services/sharing/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/sharing/pkg/config" + "github.com/owncloud/ocis/v2/services/sharing/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/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/services/sharing/pkg/command/root.go b/services/sharing/pkg/command/root.go new file mode 100644 index 00000000000..fae443c937e --- /dev/null +++ b/services/sharing/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/sharing/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/services/sharing/pkg/command/server.go b/services/sharing/pkg/command/server.go new file mode 100644 index 00000000000..97737f8afea --- /dev/null +++ b/services/sharing/pkg/command/server.go @@ -0,0 +1,121 @@ +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/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/sync" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/sharing/pkg/config" + "github.com/owncloud/ocis/v2/services/sharing/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/sharing/pkg/logging" + "github.com/owncloud/ocis/v2/services/sharing/pkg/revaconfig" + "github.com/owncloud/ocis/v2/services/sharing/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/sharing/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) + os.Exit(1) + } + 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.GetString(), + 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/services/sharing/pkg/command/version.go b/services/sharing/pkg/command/version.go new file mode 100644 index 00000000000..368f0710d22 --- /dev/null +++ b/services/sharing/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/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.GetString()) + 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/services/sharing/pkg/config/config.go similarity index 100% rename from extensions/sharing/pkg/config/config.go rename to services/sharing/pkg/config/config.go diff --git a/services/sharing/pkg/config/defaults/defaultconfig.go b/services/sharing/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..890320cc95a --- /dev/null +++ b/services/sharing/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,129 @@ +package defaults + +import ( + "path/filepath" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" + "github.com/owncloud/ocis/v2/services/sharing/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:9151", + Token: "", + Pprof: false, + Zpages: false, + }, + GRPC: config.GRPCConfig{ + Addr: "127.0.0.1:9150", + Namespace: "com.owncloud.api", + Protocol: "tcp", + }, + Service: config.Service{ + Name: "sharing", + }, + Reva: &config.Reva{ + Address: "127.0.0.1:9142", + }, + UserSharingDriver: "cs3", + UserSharingDrivers: config.UserSharingDrivers{ + JSON: config.UserSharingJSONDriver{ + File: filepath.Join(defaults.BaseDataPath(), "storage", "shares.json"), + }, + CS3: config.UserSharingCS3Driver{ + ProviderAddr: "127.0.0.1:9215", // system storage + SystemUserIDP: "internal", + }, + OwnCloudSQL: config.UserSharingOwnCloudSQLDriver{ + DBUsername: "owncloud", + DBHost: "mysql", + DBPort: 3306, + DBName: "owncloud", + }, + }, + PublicSharingDriver: "cs3", + PublicSharingDrivers: config.PublicSharingDrivers{ + JSON: config.PublicSharingJSONDriver{ + File: filepath.Join(defaults.BaseDataPath(), "storage", "publicshares.json"), + }, + CS3: config.PublicSharingCS3Driver{ + ProviderAddr: "127.0.0.1:9215", // system storage + SystemUserIDP: "internal", + }, + // TODO implement and add owncloudsql publicshare driver + }, + Events: config.Events{ + Addr: "127.0.0.1:9233", + ClusterID: "ocis-cluster", + }, + } +} + +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{} + } + + if cfg.UserSharingDrivers.CS3.SystemUserAPIKey == "" && cfg.Commons != nil && cfg.Commons.SystemUserAPIKey != "" { + cfg.UserSharingDrivers.CS3.SystemUserAPIKey = cfg.Commons.SystemUserAPIKey + } + + if cfg.UserSharingDrivers.CS3.SystemUserID == "" && cfg.Commons != nil && cfg.Commons.SystemUserID != "" { + cfg.UserSharingDrivers.CS3.SystemUserID = cfg.Commons.SystemUserID + } + + if cfg.PublicSharingDrivers.CS3.SystemUserAPIKey == "" && cfg.Commons != nil && cfg.Commons.SystemUserAPIKey != "" { + cfg.PublicSharingDrivers.CS3.SystemUserAPIKey = cfg.Commons.SystemUserAPIKey + } + + if cfg.PublicSharingDrivers.CS3.SystemUserID == "" && cfg.Commons != nil && cfg.Commons.SystemUserID != "" { + cfg.PublicSharingDrivers.CS3.SystemUserID = cfg.Commons.SystemUserID + } +} + +func Sanitize(cfg *config.Config) { + // nothing to sanitize here atm +} diff --git a/services/sharing/pkg/config/parser/parse.go b/services/sharing/pkg/config/parser/parse.go new file mode 100644 index 00000000000..32cc5624c78 --- /dev/null +++ b/services/sharing/pkg/config/parser/parse.go @@ -0,0 +1,58 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/services/sharing/pkg/config" + "github.com/owncloud/ocis/v2/services/sharing/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + if cfg.TokenManager.JWTSecret == "" { + return shared.MissingJWTTokenError(cfg.Service.Name) + } + + if cfg.PublicSharingDriver == "cs3" && cfg.PublicSharingDrivers.CS3.SystemUserAPIKey == "" { + return shared.MissingSystemUserApiKeyError(cfg.Service.Name) + } + + if cfg.PublicSharingDriver == "cs3" && cfg.PublicSharingDrivers.CS3.SystemUserID == "" { + return shared.MissingSystemUserID(cfg.Service.Name) + } + + if cfg.UserSharingDriver == "cs3" && cfg.UserSharingDrivers.CS3.SystemUserAPIKey == "" { + return shared.MissingSystemUserApiKeyError(cfg.Service.Name) + } + + if cfg.UserSharingDriver == "cs3" && cfg.UserSharingDrivers.CS3.SystemUserID == "" { + return shared.MissingSystemUserID(cfg.Service.Name) + } + + return nil +} diff --git a/extensions/sharing/pkg/config/reva.go b/services/sharing/pkg/config/reva.go similarity index 100% rename from extensions/sharing/pkg/config/reva.go rename to services/sharing/pkg/config/reva.go diff --git a/services/sharing/pkg/logging/logging.go b/services/sharing/pkg/logging/logging.go new file mode 100644 index 00000000000..55fa28366f4 --- /dev/null +++ b/services/sharing/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/sharing/pkg/config" +) + +// 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/services/sharing/pkg/revaconfig/config.go b/services/sharing/pkg/revaconfig/config.go new file mode 100644 index 00000000000..5b5634f1fb8 --- /dev/null +++ b/services/sharing/pkg/revaconfig/config.go @@ -0,0 +1,99 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/v2/services/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{}{ + "gateway_addr": cfg.Reva.Address, + "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{}{ + "gateway_addr": cfg.UserSharingDrivers.CS3.ProviderAddr, + "provider_addr": cfg.UserSharingDrivers.CS3.ProviderAddr, + "service_user_id": cfg.UserSharingDrivers.CS3.SystemUserID, + "service_user_idp": cfg.UserSharingDrivers.CS3.SystemUserIDP, + "machine_auth_apikey": cfg.UserSharingDrivers.CS3.SystemUserAPIKey, + }, + }, + }, + "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{}{ + "gateway_addr": cfg.PublicSharingDrivers.CS3.ProviderAddr, + "provider_addr": cfg.PublicSharingDrivers.CS3.ProviderAddr, + "service_user_id": cfg.PublicSharingDrivers.CS3.SystemUserID, + "service_user_idp": cfg.PublicSharingDrivers.CS3.SystemUserIDP, + "machine_auth_apikey": cfg.PublicSharingDrivers.CS3.SystemUserAPIKey, + }, + }, + }, + }, + "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/services/sharing/pkg/server/debug/option.go b/services/sharing/pkg/server/debug/option.go new file mode 100644 index 00000000000..cfae7d88d42 --- /dev/null +++ b/services/sharing/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/sharing/pkg/config" +) + +// 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/services/sharing/pkg/server/debug/server.go b/services/sharing/pkg/server/debug/server.go new file mode 100644 index 00000000000..7c4ffd84692 --- /dev/null +++ b/services/sharing/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/sharing/pkg/config" +) + +// 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.GetString()), + 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/services/sharing/pkg/tracing/tracing.go b/services/sharing/pkg/tracing/tracing.go new file mode 100644 index 00000000000..b9eb3cfd0f8 --- /dev/null +++ b/services/sharing/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/sharing/pkg/config" + "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/services/storage-publiclink/Makefile similarity index 100% rename from extensions/storage-publiclink/Makefile rename to services/storage-publiclink/Makefile diff --git a/services/storage-publiclink/cmd/storage-publiclink/main.go b/services/storage-publiclink/cmd/storage-publiclink/main.go new file mode 100644 index 00000000000..6d2415a0070 --- /dev/null +++ b/services/storage-publiclink/cmd/storage-publiclink/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/storage-publiclink/pkg/command" + "github.com/owncloud/ocis/v2/services/storage-publiclink/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/services/storage-publiclink/pkg/command/health.go b/services/storage-publiclink/pkg/command/health.go new file mode 100644 index 00000000000..b2f3889f507 --- /dev/null +++ b/services/storage-publiclink/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/storage-publiclink/pkg/config" + "github.com/owncloud/ocis/v2/services/storage-publiclink/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/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/services/storage-publiclink/pkg/command/root.go b/services/storage-publiclink/pkg/command/root.go new file mode 100644 index 00000000000..831cb046ece --- /dev/null +++ b/services/storage-publiclink/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/storage-publiclink/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/services/storage-publiclink/pkg/command/server.go b/services/storage-publiclink/pkg/command/server.go new file mode 100644 index 00000000000..66dd9c9d8ee --- /dev/null +++ b/services/storage-publiclink/pkg/command/server.go @@ -0,0 +1,108 @@ +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/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/sync" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/storage-publiclink/pkg/config" + "github.com/owncloud/ocis/v2/services/storage-publiclink/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/storage-publiclink/pkg/logging" + "github.com/owncloud/ocis/v2/services/storage-publiclink/pkg/revaconfig" + "github.com/owncloud/ocis/v2/services/storage-publiclink/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/storage-publiclink/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) + os.Exit(1) + } + 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.GetString(), + 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/services/storage-publiclink/pkg/command/version.go b/services/storage-publiclink/pkg/command/version.go new file mode 100644 index 00000000000..31e23c9f386 --- /dev/null +++ b/services/storage-publiclink/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/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.GetString()) + 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/services/storage-publiclink/pkg/config/config.go similarity index 100% rename from extensions/storage-publiclink/pkg/config/config.go rename to services/storage-publiclink/pkg/config/config.go diff --git a/services/storage-publiclink/pkg/config/defaults/defaultconfig.go b/services/storage-publiclink/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..fd94933ef7c --- /dev/null +++ b/services/storage-publiclink/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,82 @@ +package defaults + +import ( + "github.com/owncloud/ocis/v2/services/storage-publiclink/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:9179", + Token: "", + Pprof: false, + Zpages: false, + }, + GRPC: config.GRPCConfig{ + Addr: "127.0.0.1:9178", + Namespace: "com.owncloud.api", + Protocol: "tcp", + }, + Service: config.Service{ + Name: "storage-publiclink", + }, + Reva: &config.Reva{ + Address: "127.0.0.1:9142", + }, + StorageProvider: config.StorageProvider{ + 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.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/services/storage-publiclink/pkg/config/parser/parse.go b/services/storage-publiclink/pkg/config/parser/parse.go new file mode 100644 index 00000000000..b081f858be5 --- /dev/null +++ b/services/storage-publiclink/pkg/config/parser/parse.go @@ -0,0 +1,42 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/services/storage-publiclink/pkg/config" + "github.com/owncloud/ocis/v2/services/storage-publiclink/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + if cfg.TokenManager.JWTSecret == "" { + return shared.MissingJWTTokenError(cfg.Service.Name) + } + + return nil +} diff --git a/extensions/storage-publiclink/pkg/config/reva.go b/services/storage-publiclink/pkg/config/reva.go similarity index 100% rename from extensions/storage-publiclink/pkg/config/reva.go rename to services/storage-publiclink/pkg/config/reva.go diff --git a/services/storage-publiclink/pkg/logging/logging.go b/services/storage-publiclink/pkg/logging/logging.go new file mode 100644 index 00000000000..c5a037c8364 --- /dev/null +++ b/services/storage-publiclink/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/storage-publiclink/pkg/config" +) + +// 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/services/storage-publiclink/pkg/revaconfig/config.go b/services/storage-publiclink/pkg/revaconfig/config.go new file mode 100644 index 00000000000..2c2cfd9c00d --- /dev/null +++ b/services/storage-publiclink/pkg/revaconfig/config.go @@ -0,0 +1,44 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/v2/services/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/services/storage-publiclink/pkg/server/debug/option.go b/services/storage-publiclink/pkg/server/debug/option.go new file mode 100644 index 00000000000..4d3645a629d --- /dev/null +++ b/services/storage-publiclink/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/storage-publiclink/pkg/config" +) + +// 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/services/storage-publiclink/pkg/server/debug/server.go b/services/storage-publiclink/pkg/server/debug/server.go new file mode 100644 index 00000000000..61b501b58a9 --- /dev/null +++ b/services/storage-publiclink/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/storage-publiclink/pkg/config" +) + +// 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.GetString()), + 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/services/storage-publiclink/pkg/tracing/tracing.go b/services/storage-publiclink/pkg/tracing/tracing.go new file mode 100644 index 00000000000..56551e597d5 --- /dev/null +++ b/services/storage-publiclink/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/storage-publiclink/pkg/config" + "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/services/storage-shares/Makefile similarity index 100% rename from extensions/storage-shares/Makefile rename to services/storage-shares/Makefile diff --git a/services/storage-shares/cmd/storage-shares/main.go b/services/storage-shares/cmd/storage-shares/main.go new file mode 100644 index 00000000000..8a8f9336beb --- /dev/null +++ b/services/storage-shares/cmd/storage-shares/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/storage-shares/pkg/command" + "github.com/owncloud/ocis/v2/services/storage-shares/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/services/storage-shares/pkg/command/health.go b/services/storage-shares/pkg/command/health.go new file mode 100644 index 00000000000..90e68c8ba17 --- /dev/null +++ b/services/storage-shares/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/storage-shares/pkg/config" + "github.com/owncloud/ocis/v2/services/storage-shares/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/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/services/storage-shares/pkg/command/root.go b/services/storage-shares/pkg/command/root.go new file mode 100644 index 00000000000..5d8db7f5ec3 --- /dev/null +++ b/services/storage-shares/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/storage-shares/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/services/storage-shares/pkg/command/server.go b/services/storage-shares/pkg/command/server.go new file mode 100644 index 00000000000..e541979971c --- /dev/null +++ b/services/storage-shares/pkg/command/server.go @@ -0,0 +1,108 @@ +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/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/sync" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/storage-shares/pkg/config" + "github.com/owncloud/ocis/v2/services/storage-shares/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/storage-shares/pkg/logging" + "github.com/owncloud/ocis/v2/services/storage-shares/pkg/revaconfig" + "github.com/owncloud/ocis/v2/services/storage-shares/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/storage-shares/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) + os.Exit(1) + } + 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.GetString(), + 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/services/storage-shares/pkg/command/version.go b/services/storage-shares/pkg/command/version.go new file mode 100644 index 00000000000..df3e40b2fc6 --- /dev/null +++ b/services/storage-shares/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/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.GetString()) + 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/services/storage-shares/pkg/config/config.go similarity index 100% rename from extensions/storage-shares/pkg/config/config.go rename to services/storage-shares/pkg/config/config.go diff --git a/services/storage-shares/pkg/config/defaults/defaultconfig.go b/services/storage-shares/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..b9664276f53 --- /dev/null +++ b/services/storage-shares/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,82 @@ +package defaults + +import ( + "github.com/owncloud/ocis/v2/services/storage-shares/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:9156", + Token: "", + Pprof: false, + Zpages: false, + }, + GRPC: config.GRPCConfig{ + Addr: "127.0.0.1:9154", + Namespace: "com.owncloud.api", + Protocol: "tcp", + }, + Service: config.Service{ + Name: "storage-shares", + }, + Reva: &config.Reva{ + Address: "127.0.0.1:9142", + }, + MountID: "7639e57c-4433-4a12-8201-722fd0009154", + ReadOnly: false, + SharesProviderEndpoint: "localhost:9150", + } +} + +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/services/storage-shares/pkg/config/parser/parse.go b/services/storage-shares/pkg/config/parser/parse.go new file mode 100644 index 00000000000..6c7b5c37162 --- /dev/null +++ b/services/storage-shares/pkg/config/parser/parse.go @@ -0,0 +1,42 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/services/storage-shares/pkg/config" + "github.com/owncloud/ocis/v2/services/storage-shares/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + if cfg.TokenManager.JWTSecret == "" { + return shared.MissingJWTTokenError(cfg.Service.Name) + } + + return nil +} diff --git a/extensions/storage-shares/pkg/config/reva.go b/services/storage-shares/pkg/config/reva.go similarity index 100% rename from extensions/storage-shares/pkg/config/reva.go rename to services/storage-shares/pkg/config/reva.go diff --git a/services/storage-shares/pkg/logging/logging.go b/services/storage-shares/pkg/logging/logging.go new file mode 100644 index 00000000000..f7f4151ca16 --- /dev/null +++ b/services/storage-shares/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/storage-shares/pkg/config" +) + +// 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/services/storage-shares/pkg/revaconfig/config.go b/services/storage-shares/pkg/revaconfig/config.go new file mode 100644 index 00000000000..d6d29ba0ef5 --- /dev/null +++ b/services/storage-shares/pkg/revaconfig/config.go @@ -0,0 +1,39 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/v2/services/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, + "mount_id": cfg.MountID, + }, + }, + }, + } + if cfg.ReadOnly { + gcfg := rcfg["grpc"].(map[string]interface{}) + gcfg["interceptors"] = map[string]interface{}{ + "readonly": map[string]interface{}{}, + } + } + return rcfg +} diff --git a/services/storage-shares/pkg/server/debug/option.go b/services/storage-shares/pkg/server/debug/option.go new file mode 100644 index 00000000000..3363308d193 --- /dev/null +++ b/services/storage-shares/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/storage-shares/pkg/config" +) + +// 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/services/storage-shares/pkg/server/debug/server.go b/services/storage-shares/pkg/server/debug/server.go new file mode 100644 index 00000000000..159b70fdb10 --- /dev/null +++ b/services/storage-shares/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/storage-shares/pkg/config" +) + +// 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.GetString()), + 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/services/storage-shares/pkg/tracing/tracing.go b/services/storage-shares/pkg/tracing/tracing.go new file mode 100644 index 00000000000..80077224d9f --- /dev/null +++ b/services/storage-shares/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/storage-shares/pkg/config" + "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-system/Makefile b/services/storage-system/Makefile similarity index 100% rename from extensions/storage-system/Makefile rename to services/storage-system/Makefile diff --git a/services/storage-system/cmd/storage-system/main.go b/services/storage-system/cmd/storage-system/main.go new file mode 100644 index 00000000000..d9242ea0571 --- /dev/null +++ b/services/storage-system/cmd/storage-system/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/storage-system/pkg/command" + "github.com/owncloud/ocis/v2/services/storage-system/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/services/storage-system/pkg/command/health.go b/services/storage-system/pkg/command/health.go new file mode 100644 index 00000000000..8b5d5e116a9 --- /dev/null +++ b/services/storage-system/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/storage-system/pkg/config" + "github.com/owncloud/ocis/v2/services/storage-system/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/storage-system/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/services/storage-system/pkg/command/root.go b/services/storage-system/pkg/command/root.go new file mode 100644 index 00000000000..8e9847e6068 --- /dev/null +++ b/services/storage-system/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/storage-system/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-system command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "storage-system", + Usage: "Provide system 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-system command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new storage-system.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.StorageSystem.Commons = cfg.Commons + return SutureService{ + cfg: cfg.StorageSystem, + } +} + +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/services/storage-system/pkg/command/server.go b/services/storage-system/pkg/command/server.go new file mode 100644 index 00000000000..b06a3ad908b --- /dev/null +++ b/services/storage-system/pkg/command/server.go @@ -0,0 +1,119 @@ +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/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/sync" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/storage-system/pkg/config" + "github.com/owncloud/ocis/v2/services/storage-system/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/storage-system/pkg/logging" + "github.com/owncloud/ocis/v2/services/storage-system/pkg/revaconfig" + "github.com/owncloud/ocis/v2/services/storage-system/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/storage-system/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) + os.Exit(1) + } + 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.StorageSystemFromStruct(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.GetString(), + 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.GetString(), + 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/services/storage-system/pkg/command/version.go b/services/storage-system/pkg/command/version.go new file mode 100644 index 00000000000..43898d14907 --- /dev/null +++ b/services/storage-system/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/storage-system/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.GetString()) + 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-system/pkg/config/config.go b/services/storage-system/pkg/config/config.go similarity index 100% rename from extensions/storage-system/pkg/config/config.go rename to services/storage-system/pkg/config/config.go diff --git a/services/storage-system/pkg/config/defaults/defaultconfig.go b/services/storage-system/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..48f0bf6bffe --- /dev/null +++ b/services/storage-system/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,104 @@ +package defaults + +import ( + "path/filepath" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" + "github.com/owncloud/ocis/v2/services/storage-system/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:9217", + Token: "", + Pprof: false, + Zpages: false, + }, + GRPC: config.GRPCConfig{ + Addr: "127.0.0.1:9215", + Namespace: "com.owncloud.api", + Protocol: "tcp", + }, + HTTP: config.HTTPConfig{ + Addr: "127.0.0.1:9216", + Namespace: "com.owncloud.web", + Protocol: "tcp", + }, + Service: config.Service{ + Name: "storage-system", + }, + Reva: &config.Reva{ + Address: "127.0.0.1:9142", + }, + TempFolder: filepath.Join(defaults.BaseDataPath(), "tmp", "metadata"), + DataServerURL: "http://localhost:9216/data", + Driver: "ocis", + Drivers: config.Drivers{ + OCIS: config.OCISDriver{ + Root: filepath.Join(defaults.BaseDataPath(), "storage", "metadata"), + }, + }, + } +} + +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{} + } + + if cfg.SystemUserAPIKey == "" && cfg.Commons != nil && cfg.Commons.SystemUserAPIKey != "" { + cfg.SystemUserAPIKey = cfg.Commons.SystemUserAPIKey + } + + if cfg.SystemUserID == "" && cfg.Commons != nil && cfg.Commons.SystemUserID != "" { + cfg.SystemUserID = cfg.Commons.SystemUserID + } + +} + +func Sanitize(cfg *config.Config) { + // nothing to sanitize here atm +} diff --git a/services/storage-system/pkg/config/parser/parse.go b/services/storage-system/pkg/config/parser/parse.go new file mode 100644 index 00000000000..ce706ed670f --- /dev/null +++ b/services/storage-system/pkg/config/parser/parse.go @@ -0,0 +1,49 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/services/storage-system/pkg/config" + "github.com/owncloud/ocis/v2/services/storage-system/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + if cfg.TokenManager.JWTSecret == "" { + return shared.MissingJWTTokenError(cfg.Service.Name) + } + + if cfg.SystemUserAPIKey == "" { + return shared.MissingSystemUserApiKeyError(cfg.Service.Name) + } + + if cfg.SystemUserID == "" { + return shared.MissingSystemUserID(cfg.Service.Name) + } + return nil +} diff --git a/extensions/storage-system/pkg/config/reva.go b/services/storage-system/pkg/config/reva.go similarity index 100% rename from extensions/storage-system/pkg/config/reva.go rename to services/storage-system/pkg/config/reva.go diff --git a/services/storage-system/pkg/logging/logging.go b/services/storage-system/pkg/logging/logging.go new file mode 100644 index 00000000000..f2419085eb2 --- /dev/null +++ b/services/storage-system/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/storage-system/pkg/config" +) + +// 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/services/storage-system/pkg/revaconfig/config.go b/services/storage-system/pkg/revaconfig/config.go new file mode 100644 index 00000000000..bbe33e61518 --- /dev/null +++ b/services/storage-system/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/v2/services/storage-system/pkg/config" +) + +// StorageSystemFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func StorageSystemFromStruct(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.SystemUserID, + "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.SystemUserAPIKey, + "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/services/storage-system/pkg/server/debug/option.go b/services/storage-system/pkg/server/debug/option.go new file mode 100644 index 00000000000..e0b16437756 --- /dev/null +++ b/services/storage-system/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/storage-system/pkg/config" +) + +// 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/services/storage-system/pkg/server/debug/server.go b/services/storage-system/pkg/server/debug/server.go new file mode 100644 index 00000000000..0c83d774229 --- /dev/null +++ b/services/storage-system/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/storage-system/pkg/config" +) + +// 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.GetString()), + 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/services/storage-system/pkg/tracing/tracing.go b/services/storage-system/pkg/tracing/tracing.go new file mode 100644 index 00000000000..531ef376ab2 --- /dev/null +++ b/services/storage-system/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/storage-system/pkg/config" + "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/services/storage-users/Makefile similarity index 100% rename from extensions/storage-users/Makefile rename to services/storage-users/Makefile diff --git a/services/storage-users/cmd/storage-users/main.go b/services/storage-users/cmd/storage-users/main.go new file mode 100644 index 00000000000..c3c7bc7e3f0 --- /dev/null +++ b/services/storage-users/cmd/storage-users/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/storage-users/pkg/command" + "github.com/owncloud/ocis/v2/services/storage-users/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/services/storage-users/pkg/command/health.go b/services/storage-users/pkg/command/health.go new file mode 100644 index 00000000000..9dcb45556a8 --- /dev/null +++ b/services/storage-users/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/storage-users/pkg/config" + "github.com/owncloud/ocis/v2/services/storage-users/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/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/services/storage-users/pkg/command/root.go b/services/storage-users/pkg/command/root.go new file mode 100644 index 00000000000..ae9dd94c0e9 --- /dev/null +++ b/services/storage-users/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/storage-users/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/services/storage-users/pkg/command/server.go b/services/storage-users/pkg/command/server.go new file mode 100644 index 00000000000..7cf8b60a998 --- /dev/null +++ b/services/storage-users/pkg/command/server.go @@ -0,0 +1,108 @@ +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/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/sync" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/storage-users/pkg/config" + "github.com/owncloud/ocis/v2/services/storage-users/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/storage-users/pkg/logging" + "github.com/owncloud/ocis/v2/services/storage-users/pkg/revaconfig" + "github.com/owncloud/ocis/v2/services/storage-users/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/storage-users/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) + os.Exit(1) + } + 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.GetString(), + 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/services/storage-users/pkg/command/version.go b/services/storage-users/pkg/command/version.go new file mode 100644 index 00000000000..153d72e5516 --- /dev/null +++ b/services/storage-users/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/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.GetString()) + 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/services/storage-users/pkg/config/config.go similarity index 100% rename from extensions/storage-users/pkg/config/config.go rename to services/storage-users/pkg/config/config.go diff --git a/services/storage-users/pkg/config/defaults/defaultconfig.go b/services/storage-users/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..ee7dd2d0842 --- /dev/null +++ b/services/storage-users/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,127 @@ +package defaults + +import ( + "path/filepath" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" + "github.com/owncloud/ocis/v2/services/storage-users/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:9159", + Token: "", + Pprof: false, + Zpages: false, + }, + GRPC: config.GRPCConfig{ + Addr: "127.0.0.1:9157", + Namespace: "com.owncloud.api", + Protocol: "tcp", + }, + HTTP: config.HTTPConfig{ + Addr: "127.0.0.1:9158", + Namespace: "com.owncloud.web", + Protocol: "tcp", + Prefix: "data", + }, + Service: config.Service{ + Name: "storage-users", + }, + Reva: &config.Reva{ + Address: "127.0.0.1:9142", + }, + TempFolder: filepath.Join(defaults.BaseDataPath(), "tmp", "users"), + DataServerURL: "http://localhost:9158/data", + MountID: "1284d238-aa92-42ce-bdc4-0b0000009157", + Driver: "ocis", + Drivers: config.Drivers{ + OwnCloudSQL: config.OwnCloudSQLDriver{ + Root: filepath.Join(defaults.BaseDataPath(), "storage", "owncloud"), + ShareFolder: "/Shares", + UserLayout: "{{.Username}}", + UploadInfoDir: filepath.Join(defaults.BaseDataPath(), "storage", "uploadinfo"), + DBUsername: "owncloud", + DBPassword: "owncloud", + DBHost: "", + DBPort: 3306, + DBName: "owncloud", + UsersProviderEndpoint: "localhost:9144", + }, + S3NG: config.S3NGDriver{ + Root: filepath.Join(defaults.BaseDataPath(), "storage", "users"), + ShareFolder: "/Shares", + UserLayout: "{{.Id.OpaqueId}}", + Region: "default", + PersonalSpaceAliasTemplate: "{{.SpaceType}}/{{.User.Username | lower}}", + GeneralSpaceAliasTemplate: "{{.SpaceType}}/{{.SpaceName | replace \" \" \"-\" | lower}}", + PermissionsEndpoint: "127.0.0.1:9191", + }, + OCIS: config.OCISDriver{ + Root: filepath.Join(defaults.BaseDataPath(), "storage", "users"), + ShareFolder: "/Shares", + UserLayout: "{{.Id.OpaqueId}}", + PersonalSpaceAliasTemplate: "{{.SpaceType}}/{{.User.Username | lower}}", + GeneralSpaceAliasTemplate: "{{.SpaceType}}/{{.SpaceName | replace \" \" \"-\" | lower}}", + PermissionsEndpoint: "127.0.0.1:9191", + }, + }, + Events: config.Events{ + Addr: "127.0.0.1:9233", + ClusterID: "ocis-cluster", + }, + } +} + +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/services/storage-users/pkg/config/parser/parse.go b/services/storage-users/pkg/config/parser/parse.go new file mode 100644 index 00000000000..049a504ab90 --- /dev/null +++ b/services/storage-users/pkg/config/parser/parse.go @@ -0,0 +1,42 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/services/storage-users/pkg/config" + "github.com/owncloud/ocis/v2/services/storage-users/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + if cfg.TokenManager.JWTSecret == "" { + return shared.MissingJWTTokenError(cfg.Service.Name) + } + + return nil +} diff --git a/extensions/storage-users/pkg/config/reva.go b/services/storage-users/pkg/config/reva.go similarity index 100% rename from extensions/storage-users/pkg/config/reva.go rename to services/storage-users/pkg/config/reva.go diff --git a/services/storage-users/pkg/logging/logging.go b/services/storage-users/pkg/logging/logging.go new file mode 100644 index 00000000000..e0576506bbf --- /dev/null +++ b/services/storage-users/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/storage-users/pkg/config" +) + +// 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/services/storage-users/pkg/revaconfig/config.go b/services/storage-users/pkg/revaconfig/config.go new file mode 100644 index 00000000000..fb3efbeafd1 --- /dev/null +++ b/services/storage-users/pkg/revaconfig/config.go @@ -0,0 +1,69 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/v2/services/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, + "nats_address": cfg.Events.Addr, + "nats_clusterID": cfg.Events.ClusterID, + }, + }, + }, + } + 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/revaconfig/user.go b/services/storage-users/pkg/revaconfig/user.go similarity index 98% rename from extensions/storage-users/pkg/revaconfig/user.go rename to services/storage-users/pkg/revaconfig/user.go index 139c47027f5..ca06607772f 100644 --- a/extensions/storage-users/pkg/revaconfig/user.go +++ b/services/storage-users/pkg/revaconfig/user.go @@ -1,6 +1,6 @@ package revaconfig -import "github.com/owncloud/ocis/v2/extensions/storage-users/pkg/config" +import "github.com/owncloud/ocis/v2/services/storage-users/pkg/config" func UserDrivers(cfg *config.Config) map[string]interface{} { return map[string]interface{}{ diff --git a/services/storage-users/pkg/server/debug/option.go b/services/storage-users/pkg/server/debug/option.go new file mode 100644 index 00000000000..1ef04bc7388 --- /dev/null +++ b/services/storage-users/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/storage-users/pkg/config" +) + +// 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/services/storage-users/pkg/server/debug/server.go b/services/storage-users/pkg/server/debug/server.go new file mode 100644 index 00000000000..bad45bf058e --- /dev/null +++ b/services/storage-users/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/storage-users/pkg/config" +) + +// 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.GetString()), + 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/services/storage-users/pkg/tracing/tracing.go b/services/storage-users/pkg/tracing/tracing.go new file mode 100644 index 00000000000..8a9fca7123f --- /dev/null +++ b/services/storage-users/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/storage-users/pkg/config" + "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/store/.golangci.yaml b/services/store/.golangci.yaml similarity index 100% rename from extensions/store/.golangci.yaml rename to services/store/.golangci.yaml diff --git a/extensions/store/Makefile b/services/store/Makefile similarity index 100% rename from extensions/store/Makefile rename to services/store/Makefile diff --git a/services/store/cmd/store/main.go b/services/store/cmd/store/main.go new file mode 100644 index 00000000000..ecf42e81938 --- /dev/null +++ b/services/store/cmd/store/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/store/pkg/command" + "github.com/owncloud/ocis/v2/services/store/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/store/docker/Dockerfile.linux.amd64 b/services/store/docker/Dockerfile.linux.amd64 similarity index 100% rename from extensions/store/docker/Dockerfile.linux.amd64 rename to services/store/docker/Dockerfile.linux.amd64 diff --git a/extensions/store/docker/Dockerfile.linux.arm b/services/store/docker/Dockerfile.linux.arm similarity index 100% rename from extensions/store/docker/Dockerfile.linux.arm rename to services/store/docker/Dockerfile.linux.arm diff --git a/extensions/store/docker/Dockerfile.linux.arm64 b/services/store/docker/Dockerfile.linux.arm64 similarity index 100% rename from extensions/store/docker/Dockerfile.linux.arm64 rename to services/store/docker/Dockerfile.linux.arm64 diff --git a/extensions/store/docker/manifest.tmpl b/services/store/docker/manifest.tmpl similarity index 100% rename from extensions/store/docker/manifest.tmpl rename to services/store/docker/manifest.tmpl diff --git a/services/store/pkg/command/health.go b/services/store/pkg/command/health.go new file mode 100644 index 00000000000..e08ec2c782a --- /dev/null +++ b/services/store/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/store/pkg/config" + "github.com/owncloud/ocis/v2/services/store/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/store/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/services/store/pkg/command/root.go b/services/store/pkg/command/root.go new file mode 100644 index 00000000000..5396b1103e1 --- /dev/null +++ b/services/store/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/store/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-store command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "store", + Usage: "Service to store values for ocis services", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the store command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new store.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.Store.Commons = cfg.Commons + return SutureService{ + cfg: cfg.Store, + } +} + +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/services/store/pkg/command/server.go b/services/store/pkg/command/server.go new file mode 100644 index 00000000000..8b08d7a4ebc --- /dev/null +++ b/services/store/pkg/command/server.go @@ -0,0 +1,95 @@ +package command + +import ( + "context" + "fmt" + "os" + + "github.com/oklog/run" + + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/store/pkg/config" + "github.com/owncloud/ocis/v2/services/store/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/store/pkg/logging" + "github.com/owncloud/ocis/v2/services/store/pkg/metrics" + "github.com/owncloud/ocis/v2/services/store/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/store/pkg/server/grpc" + "github.com/owncloud/ocis/v2/services/store/pkg/tracing" + "github.com/urfave/cli/v2" +) + +// Server is the entrypoint 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) + os.Exit(1) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg) + if err != nil { + return err + } + + var ( + gr = run.Group{} + ctx, cancel = func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() + metrics = metrics.New() + ) + + defer cancel() + + metrics.BuildInfo.WithLabelValues(version.GetString()).Set(1) + + { + server := grpc.Server( + grpc.Logger(logger), + grpc.Context(ctx), + grpc.Config(cfg), + grpc.Metrics(metrics), + ) + + gr.Add(server.Run, func(_ error) { + logger.Info(). + Str("server", "grpc"). + Msg("Shutting down server") + + cancel() + }) + } + + { + server, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Error().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(server.ListenAndServe, func(_ error) { + _ = server.Shutdown(ctx) + cancel() + }) + } + + return gr.Run() + }, + } +} diff --git a/services/store/pkg/command/version.go b/services/store/pkg/command/version.go new file mode 100644 index 00000000000..b1cfe574299 --- /dev/null +++ b/services/store/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/store/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.GetString()) + 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/store/pkg/config/config.go b/services/store/pkg/config/config.go similarity index 100% rename from extensions/store/pkg/config/config.go rename to services/store/pkg/config/config.go diff --git a/extensions/store/pkg/config/debug.go b/services/store/pkg/config/debug.go similarity index 100% rename from extensions/store/pkg/config/debug.go rename to services/store/pkg/config/debug.go diff --git a/services/store/pkg/config/defaults/defaultconfig.go b/services/store/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..1755b81116d --- /dev/null +++ b/services/store/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,63 @@ +package defaults + +import ( + "path" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" + "github.com/owncloud/ocis/v2/services/store/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:9464", + Token: "", + Pprof: false, + Zpages: false, + }, + GRPC: config.GRPC{ + Addr: "127.0.0.1:9460", + Namespace: "com.owncloud.api", + }, + Service: config.Service{ + Name: "store", + }, + Datapath: path.Join(defaults.BaseDataPath(), "store"), + } +} + +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{} + } +} + +func Sanitize(cfg *config.Config) { + // nothing to sanitize here atm +} diff --git a/extensions/store/pkg/config/grpc.go b/services/store/pkg/config/grpc.go similarity index 100% rename from extensions/store/pkg/config/grpc.go rename to services/store/pkg/config/grpc.go diff --git a/extensions/store/pkg/config/log.go b/services/store/pkg/config/log.go similarity index 100% rename from extensions/store/pkg/config/log.go rename to services/store/pkg/config/log.go diff --git a/services/store/pkg/config/parser/parse.go b/services/store/pkg/config/parser/parse.go new file mode 100644 index 00000000000..5fb6a3c557a --- /dev/null +++ b/services/store/pkg/config/parser/parse.go @@ -0,0 +1,38 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/store/pkg/config" + "github.com/owncloud/ocis/v2/services/store/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + // sanitize config + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + return nil +} diff --git a/extensions/store/pkg/config/service.go b/services/store/pkg/config/service.go similarity index 100% rename from extensions/store/pkg/config/service.go rename to services/store/pkg/config/service.go diff --git a/extensions/store/pkg/config/tracing.go b/services/store/pkg/config/tracing.go similarity index 100% rename from extensions/store/pkg/config/tracing.go rename to services/store/pkg/config/tracing.go diff --git a/services/store/pkg/logging/logging.go b/services/store/pkg/logging/logging.go new file mode 100644 index 00000000000..13fcd37052d --- /dev/null +++ b/services/store/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/store/pkg/config" +) + +// 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/store/pkg/metrics/metrics.go b/services/store/pkg/metrics/metrics.go similarity index 100% rename from extensions/store/pkg/metrics/metrics.go rename to services/store/pkg/metrics/metrics.go diff --git a/services/store/pkg/server/debug/option.go b/services/store/pkg/server/debug/option.go new file mode 100644 index 00000000000..1613a3ef87b --- /dev/null +++ b/services/store/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/store/pkg/config" +) + +// 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/services/store/pkg/server/debug/server.go b/services/store/pkg/server/debug/server.go new file mode 100644 index 00000000000..09507ef0dbf --- /dev/null +++ b/services/store/pkg/server/debug/server.go @@ -0,0 +1,59 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/store/pkg/config" +) + +// 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.GetString()), + 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)), + ), 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/services/store/pkg/server/grpc/option.go b/services/store/pkg/server/grpc/option.go new file mode 100644 index 00000000000..dc8e168437c --- /dev/null +++ b/services/store/pkg/server/grpc/option.go @@ -0,0 +1,76 @@ +package grpc + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/store/pkg/config" + "github.com/owncloud/ocis/v2/services/store/pkg/metrics" + "github.com/urfave/cli/v2" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Name string + Logger log.Logger + Context context.Context + Config *config.Config + Metrics *metrics.Metrics + Flags []cli.Flag +} + +// newOptions initializes the available default options. +func newOptions(opts ...Option) Options { + opt := Options{} + + for _, o := range opts { + o(&opt) + } + + return opt +} + +// Name provides a name for the service. +func Name(val string) Option { + return func(o *Options) { + o.Name = val + } +} + +// 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 + } +} + +// Metrics provides a function to set the metrics option. +func Metrics(val *metrics.Metrics) Option { + return func(o *Options) { + o.Metrics = val + } +} + +// Flags provides a function to set the flags option. +func Flags(val []cli.Flag) Option { + return func(o *Options) { + o.Flags = append(o.Flags, val...) + } +} diff --git a/services/store/pkg/server/grpc/server.go b/services/store/pkg/server/grpc/server.go new file mode 100644 index 00000000000..0a3de8549d8 --- /dev/null +++ b/services/store/pkg/server/grpc/server.go @@ -0,0 +1,36 @@ +package grpc + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + storesvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/store/v0" + svc "github.com/owncloud/ocis/v2/services/store/pkg/service/v0" +) + +// Server initializes a new go-micro service ready to run +func Server(opts ...Option) grpc.Service { + options := newOptions(opts...) + + service := grpc.NewService( + grpc.Namespace(options.Config.GRPC.Namespace), + grpc.Name(options.Config.Service.Name), + grpc.Version(version.GetString()), + grpc.Context(options.Context), + grpc.Address(options.Config.GRPC.Addr), + grpc.Logger(options.Logger), + grpc.Flags(options.Flags...), + ) + + hdlr, err := svc.New( + svc.Logger(options.Logger), + svc.Config(options.Config), + ) + if err != nil { + options.Logger.Fatal().Err(err).Msg("could not initialize service handler") + } + if err = storesvc.RegisterStoreHandler(service.Server(), hdlr); err != nil { + options.Logger.Fatal().Err(err).Msg("could not register service handler") + } + + return service +} diff --git a/services/store/pkg/service/v0/option.go b/services/store/pkg/service/v0/option.go new file mode 100644 index 00000000000..46bea789905 --- /dev/null +++ b/services/store/pkg/service/v0/option.go @@ -0,0 +1,63 @@ +package service + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/store/pkg/config" +) + +// 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 + Config *config.Config + + Database, Table string + Nodes []string +} + +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 + } +} + +// Database configures the database option. +func Database(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} + +// Table configures the Table option. +func Table(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} + +// Nodes configures the Nodes option. +func Nodes(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} + +// Config configures the Config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} diff --git a/services/store/pkg/service/v0/service.go b/services/store/pkg/service/v0/service.go new file mode 100644 index 00000000000..57499d89f4c --- /dev/null +++ b/services/store/pkg/service/v0/service.go @@ -0,0 +1,333 @@ +package service + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/blevesearch/bleve/v2" + "github.com/blevesearch/bleve/v2/analysis/analyzer/keyword" + "github.com/owncloud/ocis/v2/ocis-pkg/log" + storemsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/store/v0" + storesvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/store/v0" + "github.com/owncloud/ocis/v2/services/store/pkg/config" + merrors "go-micro.dev/v4/errors" + "google.golang.org/protobuf/encoding/protojson" +) + +// BleveDocument wraps the generated Record.Metadata and adds a property that is used to distinguish documents in the index. +type BleveDocument struct { + Metadata map[string]*storemsg.Field `json:"metadata"` + Database string `json:"database"` + Table string `json:"table"` +} + +// New returns a new instance of Service +func New(opts ...Option) (s *Service, err error) { + options := newOptions(opts...) + logger := options.Logger + cfg := options.Config + + recordsDir := filepath.Join(cfg.Datapath, "databases") + { + var fi os.FileInfo + if fi, err = os.Stat(recordsDir); err != nil { + if os.IsNotExist(err) { + // create store directory + if err = os.MkdirAll(recordsDir, 0700); err != nil { + return nil, err + } + } + } else if !fi.IsDir() { + return nil, fmt.Errorf("%s is not a directory", recordsDir) + } + } + + indexMapping := bleve.NewIndexMapping() + // keep all symbols in terms to allow exact matching, eg. emails + indexMapping.DefaultAnalyzer = keyword.Name + + s = &Service{ + id: cfg.GRPC.Namespace + "." + cfg.Service.Name, + log: logger, + Config: cfg, + } + + indexDir := filepath.Join(cfg.Datapath, "index.bleve") + // for now recreate index on every start + if err = os.RemoveAll(indexDir); err != nil { + return nil, err + } + if s.index, err = bleve.New(indexDir, indexMapping); err != nil { + return + } + if err = s.indexRecords(recordsDir); err != nil { + return nil, err + } + return +} + +// Service implements the AccountsServiceHandler interface +type Service struct { + id string + log log.Logger + Config *config.Config + index bleve.Index +} + +// Read implements the StoreHandler interface. +func (s *Service) Read(c context.Context, rreq *storesvc.ReadRequest, rres *storesvc.ReadResponse) error { + if len(rreq.Key) != 0 { + id := getID(rreq.Options.Database, rreq.Options.Table, rreq.Key) + file := filepath.Join(s.Config.Datapath, "databases", id) + + var data []byte + rec := &storemsg.Record{} + data, err := ioutil.ReadFile(file) + if err != nil { + return merrors.NotFound(s.id, "could not read record") + } + + if err = protojson.Unmarshal(data, rec); err != nil { + return merrors.InternalServerError(s.id, "could not unmarshal record") + } + + rres.Records = append(rres.Records, rec) + return nil + } + + s.log.Info().Interface("request", rreq).Msg("read request") + if rreq.Options.Where != nil { + // build bleve query + // execute search + // fetch the actual record if there's a hit + dtq := bleve.NewTermQuery(rreq.Options.Database) + ttq := bleve.NewTermQuery(rreq.Options.Table) + dtq.SetField("database") + ttq.SetField("table") + + query := bleve.NewConjunctionQuery(dtq, ttq) + for k, v := range rreq.Options.Where { + ntq := bleve.NewTermQuery(v.Value) + ntq.SetField("metadata." + k + ".value") + query.AddQuery(ntq) + } + + searchRequest := bleve.NewSearchRequest(query) + var searchResult *bleve.SearchResult + searchResult, err := s.index.Search(searchRequest) + if err != nil { + s.log.Error().Err(err).Msg("could not execute bleve search") + return merrors.InternalServerError(s.id, "could not execute bleve search: %v", err.Error()) + } + + for _, hit := range searchResult.Hits { + rec := &storemsg.Record{} + + dest := filepath.Join(s.Config.Datapath, "databases", hit.ID) + + var data []byte + data, err := ioutil.ReadFile(dest) + s.log.Info().Str("path", dest).Interface("hit", hit).Msgf("hit info") + if err != nil { + s.log.Info().Str("path", dest).Interface("hit", hit).Msgf("file not found") + return merrors.NotFound(s.id, "could not read record") + } + + if err = protojson.Unmarshal(data, rec); err != nil { + return merrors.InternalServerError(s.id, "could not unmarshal record") + } + + rres.Records = append(rres.Records, rec) + } + return nil + } + + return merrors.InternalServerError(s.id, "neither id nor metadata present") +} + +// Write implements the StoreHandler interface. +func (s *Service) Write(c context.Context, wreq *storesvc.WriteRequest, wres *storesvc.WriteResponse) error { + id := getID(wreq.Options.Database, wreq.Options.Table, wreq.Record.Key) + file := filepath.Join(s.Config.Datapath, "databases", id) + + var bytes []byte + bytes, err := protojson.Marshal(wreq.Record) + if err != nil { + return merrors.InternalServerError(s.id, "could not marshal record") + } + + err = os.MkdirAll(filepath.Dir(file), 0700) + if err != nil { + return err + } + err = ioutil.WriteFile(file, bytes, 0600) + if err != nil { + return merrors.InternalServerError(s.id, "could not write record") + } + + doc := BleveDocument{ + Metadata: wreq.Record.Metadata, + Database: wreq.Options.Database, + Table: wreq.Options.Table, + } + if err := s.index.Index(id, doc); err != nil { + s.log.Error().Err(err).Interface("document", doc).Msg("could not index record metadata") + return err + } + + return nil +} + +// Delete implements the StoreHandler interface. +func (s *Service) Delete(c context.Context, dreq *storesvc.DeleteRequest, dres *storesvc.DeleteResponse) error { + id := getID(dreq.Options.Database, dreq.Options.Table, dreq.Key) + file := filepath.Join(s.Config.Datapath, "databases", id) + if err := os.Remove(file); err != nil { + if os.IsNotExist(err) { + return merrors.NotFound(s.id, "could not find record") + } + + return merrors.InternalServerError(s.id, "could not delete record") + } + + if err := s.index.Delete(id); err != nil { + s.log.Error().Err(err).Str("id", id).Msg("could not remove record from index") + return merrors.InternalServerError(s.id, "could not remove record from index") + } + + return nil +} + +// List implements the StoreHandler interface. +func (s *Service) List(context.Context, *storesvc.ListRequest, storesvc.Store_ListStream) error { + return nil +} + +// Databases implements the StoreHandler interface. +func (s *Service) Databases(c context.Context, dbreq *storesvc.DatabasesRequest, dbres *storesvc.DatabasesResponse) error { + file := filepath.Join(s.Config.Datapath, "databases") + f, err := os.Open(file) + if err != nil { + return merrors.InternalServerError(s.id, "could not open database directory") + } + defer f.Close() + + dnames, err := f.Readdirnames(0) + if err != nil { + return merrors.InternalServerError(s.id, "could not read database directory") + } + + dbres.Databases = dnames + return nil +} + +// Tables implements the StoreHandler interface. +func (s *Service) Tables(ctx context.Context, in *storesvc.TablesRequest, out *storesvc.TablesResponse) error { + file := filepath.Join(s.Config.Datapath, "databases", in.Database) + f, err := os.Open(file) + if err != nil { + return merrors.InternalServerError(s.id, "could not open tables directory") + } + defer f.Close() + + tnames, err := f.Readdirnames(0) + if err != nil { + return merrors.InternalServerError(s.id, "could not read tables directory") + } + + out.Tables = tnames + return nil +} + +// TODO sanitize key. As it may contain invalid characters, such as slashes. +// file: /tmp/ocis-store/databases/{database}/{table}/{record.key}. +func getID(database string, table string, key string) string { + // TODO sanitize input. + return filepath.Join(database, table, key) +} + +func (s Service) indexRecords(recordsDir string) (err error) { + + // TODO use filepath.Walk to clean up code + rh, err := os.Open(recordsDir) + if err != nil { + return merrors.InternalServerError(s.id, "could not open database directory") + } + defer rh.Close() + + dbs, err := rh.Readdirnames(0) + if err != nil { + return merrors.InternalServerError(s.id, "could not read databases directory") + } + + for i := range dbs { + tp := filepath.Join(s.Config.Datapath, "databases", dbs[i]) + th, err := os.Open(tp) + if err != nil { + s.log.Error().Err(err).Str("database", dbs[i]).Msg("could not open database directory") + continue + } + defer th.Close() + + tables, err := th.Readdirnames(0) + if err != nil { + s.log.Error().Err(err).Str("database", dbs[i]).Msg("could not read database directory") + continue + } + + for j := range tables { + + tp := filepath.Join(s.Config.Datapath, "databases", dbs[i], tables[j]) + kh, err := os.Open(tp) + if err != nil { + s.log.Error().Err(err).Str("database", dbs[i]).Str("table", tables[i]).Msg("could not open table directory") + continue + } + defer kh.Close() + + keys, err := kh.Readdirnames(0) + if err != nil { + s.log.Error().Err(err).Str("database", dbs[i]).Str("table", tables[i]).Msg("could not read table directory") + continue + } + + for k := range keys { + + id := getID(dbs[i], tables[j], keys[k]) + kp := filepath.Join(s.Config.Datapath, "databases", id) + + // read record + var data []byte + rec := &storemsg.Record{} + data, err = ioutil.ReadFile(kp) + if err != nil { + s.log.Error().Err(err).Str("id", id).Msg("could not read record") + continue + } + + if err = protojson.Unmarshal(data, rec); err != nil { + s.log.Error().Err(err).Str("id", id).Msg("could not unmarshal record") + continue + } + + // index record + doc := BleveDocument{ + Metadata: rec.Metadata, + Database: dbs[i], + Table: tables[j], + } + if err := s.index.Index(id, doc); err != nil { + s.log.Error().Err(err).Interface("document", doc).Str("id", id).Msg("could not index record metadata") + continue + } + + s.log.Debug().Str("id", id).Msg("indexed record") + } + } + } + + return +} diff --git a/services/store/pkg/tracing/tracing.go b/services/store/pkg/tracing/tracing.go new file mode 100644 index 00000000000..6e6e7052617 --- /dev/null +++ b/services/store/pkg/tracing/tracing.go @@ -0,0 +1,23 @@ +package tracing + +import ( + pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/store/pkg/config" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the store service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config) error { + var err error + if cfg.Tracing.Enabled { + if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { + return err + } + } + + return nil +} diff --git a/extensions/store/reflex.conf b/services/store/reflex.conf similarity index 100% rename from extensions/store/reflex.conf rename to services/store/reflex.conf diff --git a/extensions/thumbnails/Makefile b/services/thumbnails/Makefile similarity index 100% rename from extensions/thumbnails/Makefile rename to services/thumbnails/Makefile diff --git a/services/thumbnails/cmd/thumbnails/main.go b/services/thumbnails/cmd/thumbnails/main.go new file mode 100644 index 00000000000..f0a86f58bf2 --- /dev/null +++ b/services/thumbnails/cmd/thumbnails/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/command" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/thumbnails/docker/Dockerfile.linux.amd64 b/services/thumbnails/docker/Dockerfile.linux.amd64 similarity index 100% rename from extensions/thumbnails/docker/Dockerfile.linux.amd64 rename to services/thumbnails/docker/Dockerfile.linux.amd64 diff --git a/extensions/thumbnails/docker/Dockerfile.linux.arm b/services/thumbnails/docker/Dockerfile.linux.arm similarity index 100% rename from extensions/thumbnails/docker/Dockerfile.linux.arm rename to services/thumbnails/docker/Dockerfile.linux.arm diff --git a/extensions/thumbnails/docker/Dockerfile.linux.arm64 b/services/thumbnails/docker/Dockerfile.linux.arm64 similarity index 100% rename from extensions/thumbnails/docker/Dockerfile.linux.arm64 rename to services/thumbnails/docker/Dockerfile.linux.arm64 diff --git a/extensions/thumbnails/docker/manifest.tmpl b/services/thumbnails/docker/manifest.tmpl similarity index 100% rename from extensions/thumbnails/docker/manifest.tmpl rename to services/thumbnails/docker/manifest.tmpl diff --git a/services/thumbnails/pkg/command/health.go b/services/thumbnails/pkg/command/health.go new file mode 100644 index 00000000000..d8e1210b01c --- /dev/null +++ b/services/thumbnails/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/config" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/thumbnails/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/services/thumbnails/pkg/command/root.go b/services/thumbnails/pkg/command/root.go new file mode 100644 index 00000000000..efa87a675e4 --- /dev/null +++ b/services/thumbnails/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/thumbnails/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-thumbnails command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "thumbnails", + Usage: "Example usage", + Commands: GetCommands(cfg), + }) + + cli.HelpFlag = &cli.BoolFlag{ + Name: "help,h", + Usage: "Show the help", + } + + return app.Run(os.Args) +} + +// SutureService allows for the thumbnails command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new thumbnails.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.Thumbnails.Commons = cfg.Commons + return SutureService{ + cfg: cfg.Thumbnails, + } +} + +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/services/thumbnails/pkg/command/server.go b/services/thumbnails/pkg/command/server.go new file mode 100644 index 00000000000..7f8392c7f84 --- /dev/null +++ b/services/thumbnails/pkg/command/server.go @@ -0,0 +1,112 @@ +package command + +import ( + "context" + "fmt" + "os" + + "github.com/oklog/run" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/config" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/logging" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/metrics" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/server/grpc" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/server/http" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/tracing" + "github.com/urfave/cli/v2" +) + +// Server is the entrypoint 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) + os.Exit(1) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg) + if err != nil { + return err + } + + var ( + gr = run.Group{} + ctx, cancel = func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() + metrics = metrics.New() + ) + + defer cancel() + + metrics.BuildInfo.WithLabelValues(version.GetString()).Set(1) + + service := grpc.NewService( + grpc.Logger(logger), + grpc.Context(ctx), + grpc.Config(cfg), + grpc.Name(cfg.Service.Name), + grpc.Namespace(cfg.GRPC.Namespace), + grpc.Address(cfg.GRPC.Addr), + grpc.Metrics(metrics), + ) + + gr.Add(service.Run, func(_ error) { + fmt.Println("shutting down grpc server") + cancel() + }) + + server, err := debug.Server( + debug.Logger(logger), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("transport", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(server.ListenAndServe, func(_ error) { + _ = server.Shutdown(ctx) + cancel() + }) + + httpServer, err := http.Server( + http.Logger(logger), + http.Context(ctx), + http.Config(cfg), + http.Metrics(metrics), + http.Namespace(cfg.HTTP.Namespace), + ) + + if err != nil { + logger.Info(). + Err(err). + Str("transport", "http"). + Msg("Failed to initialize server") + + return err + } + + gr.Add(httpServer.Run, func(_ error) { + logger.Info().Str("server", "http").Msg("shutting down server") + cancel() + }) + + return gr.Run() + }, + } +} diff --git a/services/thumbnails/pkg/command/version.go b/services/thumbnails/pkg/command/version.go new file mode 100644 index 00000000000..5b893a8979e --- /dev/null +++ b/services/thumbnails/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/thumbnails/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.GetString()) + 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/thumbnails/pkg/config/config.go b/services/thumbnails/pkg/config/config.go similarity index 100% rename from extensions/thumbnails/pkg/config/config.go rename to services/thumbnails/pkg/config/config.go diff --git a/extensions/thumbnails/pkg/config/debug.go b/services/thumbnails/pkg/config/debug.go similarity index 100% rename from extensions/thumbnails/pkg/config/debug.go rename to services/thumbnails/pkg/config/debug.go diff --git a/services/thumbnails/pkg/config/defaults/defaultconfig.go b/services/thumbnails/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..752169b8e29 --- /dev/null +++ b/services/thumbnails/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,81 @@ +package defaults + +import ( + "path" + "strings" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" + "github.com/owncloud/ocis/v2/services/thumbnails/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:9189", + Token: "", + Pprof: false, + Zpages: false, + }, + GRPC: config.GRPC{ + Addr: "127.0.0.1:9185", + Namespace: "com.owncloud.api", + }, + HTTP: config.HTTP{ + Addr: "127.0.0.1:9186", + Root: "/thumbnails", + Namespace: "com.owncloud.web", + }, + Service: config.Service{ + Name: "thumbnails", + }, + Thumbnail: config.Thumbnail{ + Resolutions: []string{"16x16", "32x32", "64x64", "128x128", "1920x1080", "3840x2160", "7680x4320"}, + FileSystemStorage: config.FileSystemStorage{ + RootDirectory: path.Join(defaults.BaseDataPath(), "thumbnails"), + }, + WebdavAllowInsecure: false, + RevaGateway: "127.0.0.1:9142", + CS3AllowInsecure: false, + DataEndpoint: "http://127.0.0.1:9186/thumbnails/data", + }, + } +} + +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{} + } +} + +func Sanitize(cfg *config.Config) { + // nothing to sanitize here atm + if len(cfg.Thumbnail.Resolutions) == 1 && strings.Contains(cfg.Thumbnail.Resolutions[0], ",") { + cfg.Thumbnail.Resolutions = strings.Split(cfg.Thumbnail.Resolutions[0], ",") + } +} diff --git a/extensions/thumbnails/pkg/config/grpc.go b/services/thumbnails/pkg/config/grpc.go similarity index 100% rename from extensions/thumbnails/pkg/config/grpc.go rename to services/thumbnails/pkg/config/grpc.go diff --git a/extensions/thumbnails/pkg/config/http.go b/services/thumbnails/pkg/config/http.go similarity index 100% rename from extensions/thumbnails/pkg/config/http.go rename to services/thumbnails/pkg/config/http.go diff --git a/extensions/thumbnails/pkg/config/log.go b/services/thumbnails/pkg/config/log.go similarity index 100% rename from extensions/thumbnails/pkg/config/log.go rename to services/thumbnails/pkg/config/log.go diff --git a/services/thumbnails/pkg/config/parser/parse.go b/services/thumbnails/pkg/config/parser/parse.go new file mode 100644 index 00000000000..020c1374737 --- /dev/null +++ b/services/thumbnails/pkg/config/parser/parse.go @@ -0,0 +1,38 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/config" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + // sanitize config + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + return nil +} diff --git a/extensions/thumbnails/pkg/config/service.go b/services/thumbnails/pkg/config/service.go similarity index 100% rename from extensions/thumbnails/pkg/config/service.go rename to services/thumbnails/pkg/config/service.go diff --git a/extensions/thumbnails/pkg/config/tracing.go b/services/thumbnails/pkg/config/tracing.go similarity index 100% rename from extensions/thumbnails/pkg/config/tracing.go rename to services/thumbnails/pkg/config/tracing.go diff --git a/services/thumbnails/pkg/logging/logging.go b/services/thumbnails/pkg/logging/logging.go new file mode 100644 index 00000000000..86405182a95 --- /dev/null +++ b/services/thumbnails/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/config" +) + +// 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/thumbnails/pkg/metrics/metrics.go b/services/thumbnails/pkg/metrics/metrics.go similarity index 100% rename from extensions/thumbnails/pkg/metrics/metrics.go rename to services/thumbnails/pkg/metrics/metrics.go diff --git a/extensions/thumbnails/pkg/preprocessor/fontloader.go b/services/thumbnails/pkg/preprocessor/fontloader.go similarity index 100% rename from extensions/thumbnails/pkg/preprocessor/fontloader.go rename to services/thumbnails/pkg/preprocessor/fontloader.go diff --git a/extensions/thumbnails/pkg/preprocessor/preprocessor.go b/services/thumbnails/pkg/preprocessor/preprocessor.go similarity index 100% rename from extensions/thumbnails/pkg/preprocessor/preprocessor.go rename to services/thumbnails/pkg/preprocessor/preprocessor.go diff --git a/extensions/thumbnails/pkg/preprocessor/textanalyzer.go b/services/thumbnails/pkg/preprocessor/textanalyzer.go similarity index 100% rename from extensions/thumbnails/pkg/preprocessor/textanalyzer.go rename to services/thumbnails/pkg/preprocessor/textanalyzer.go diff --git a/extensions/thumbnails/pkg/preprocessor/textanalyzer_test.go b/services/thumbnails/pkg/preprocessor/textanalyzer_test.go similarity index 100% rename from extensions/thumbnails/pkg/preprocessor/textanalyzer_test.go rename to services/thumbnails/pkg/preprocessor/textanalyzer_test.go diff --git a/services/thumbnails/pkg/server/debug/option.go b/services/thumbnails/pkg/server/debug/option.go new file mode 100644 index 00000000000..534a47f1ccd --- /dev/null +++ b/services/thumbnails/pkg/server/debug/option.go @@ -0,0 +1,42 @@ +package debug + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/config" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Name string + Address string + Logger log.Logger + 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 + } +} + +// 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/services/thumbnails/pkg/server/debug/server.go b/services/thumbnails/pkg/server/debug/server.go new file mode 100644 index 00000000000..3a1ddcea740 --- /dev/null +++ b/services/thumbnails/pkg/server/debug/server.go @@ -0,0 +1,59 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/config" +) + +// 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.GetString()), + 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)), + ), 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/services/thumbnails/pkg/server/grpc/option.go b/services/thumbnails/pkg/server/grpc/option.go new file mode 100644 index 00000000000..ede5c120700 --- /dev/null +++ b/services/thumbnails/pkg/server/grpc/option.go @@ -0,0 +1,92 @@ +package grpc + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/config" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/metrics" + "github.com/urfave/cli/v2" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Name string + Address string + Logger log.Logger + Context context.Context + Config *config.Config + Metrics *metrics.Metrics + Namespace string + Flags []cli.Flag +} + +// 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 + } +} + +// Name provides a name for the service. +func Name(val string) Option { + return func(o *Options) { + o.Name = val + } +} + +// Address provides an address for the service. +func Address(val string) Option { + return func(o *Options) { + o.Address = 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 + } +} + +// Metrics provides a function to set the metrics option. +func Metrics(val *metrics.Metrics) Option { + return func(o *Options) { + o.Metrics = val + } +} + +// Namespace provides a function to set the namespace option. +func Namespace(val string) Option { + return func(o *Options) { + o.Namespace = val + } +} + +// Flags provides a function to set the flags option. +func Flags(flags []cli.Flag) Option { + return func(o *Options) { + o.Flags = append(o.Flags, flags...) + } +} diff --git a/services/thumbnails/pkg/server/grpc/server.go b/services/thumbnails/pkg/server/grpc/server.go new file mode 100644 index 00000000000..9f78524f0fc --- /dev/null +++ b/services/thumbnails/pkg/server/grpc/server.go @@ -0,0 +1,60 @@ +package grpc + +import ( + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" + "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + thumbnailssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/thumbnails/v0" + svc "github.com/owncloud/ocis/v2/services/thumbnails/pkg/service/grpc/v0" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/service/grpc/v0/decorators" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/thumbnail/imgsource" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/thumbnail/storage" +) + +// NewService initializes the grpc service and server. +func NewService(opts ...Option) grpc.Service { + options := newOptions(opts...) + + service := grpc.NewService( + grpc.Logger(options.Logger), + grpc.Namespace(options.Namespace), + grpc.Name(options.Name), + grpc.Version(version.GetString()), + grpc.Address(options.Address), + grpc.Context(options.Context), + grpc.Flags(options.Flags...), + grpc.Version(version.GetString()), + ) + tconf := options.Config.Thumbnail + gc, err := pool.GetGatewayServiceClient(tconf.RevaGateway) + if err != nil { + options.Logger.Error().Err(err).Msg("could not get gateway client") + return grpc.Service{} + } + var thumbnail decorators.DecoratedService + { + thumbnail = svc.NewService( + svc.Config(options.Config), + svc.Logger(options.Logger), + svc.ThumbnailSource(imgsource.NewWebDavSource(tconf)), + svc.ThumbnailStorage( + storage.NewFileSystemStorage( + tconf.FileSystemStorage, + options.Logger, + ), + ), + svc.CS3Source(imgsource.NewCS3Source(tconf, gc)), + svc.CS3Client(gc), + ) + thumbnail = decorators.NewInstrument(thumbnail, options.Metrics) + thumbnail = decorators.NewLogging(thumbnail, options.Logger) + thumbnail = decorators.NewTracing(thumbnail) + } + + _ = thumbnailssvc.RegisterThumbnailServiceHandler( + service.Server(), + thumbnail, + ) + + return service +} diff --git a/services/thumbnails/pkg/server/http/option.go b/services/thumbnails/pkg/server/http/option.go new file mode 100644 index 00000000000..6cb7020ee56 --- /dev/null +++ b/services/thumbnails/pkg/server/http/option.go @@ -0,0 +1,69 @@ +package http + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/config" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/metrics" + "github.com/urfave/cli/v2" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Namespace string + Logger log.Logger + Context context.Context + Config *config.Config + Metrics *metrics.Metrics + Flags []cli.Flag +} + +// 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 + } +} + +// Metrics provides a function to set the metrics option. +func Metrics(val *metrics.Metrics) Option { + return func(o *Options) { + o.Metrics = val + } +} + +// Namespace provides a function to set the Namespace option. +func Namespace(val string) Option { + return func(o *Options) { + o.Namespace = val + } +} diff --git a/services/thumbnails/pkg/server/http/server.go b/services/thumbnails/pkg/server/http/server.go new file mode 100644 index 00000000000..7f03f4080e5 --- /dev/null +++ b/services/thumbnails/pkg/server/http/server.go @@ -0,0 +1,58 @@ +package http + +import ( + "github.com/go-chi/chi/v5/middleware" + ocismiddleware "github.com/owncloud/ocis/v2/ocis-pkg/middleware" + "github.com/owncloud/ocis/v2/ocis-pkg/service/http" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + svc "github.com/owncloud/ocis/v2/services/thumbnails/pkg/service/http/v0" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/thumbnail/storage" + "go-micro.dev/v4" +) + +// Server initializes the http service and server. +func Server(opts ...Option) (http.Service, error) { + options := newOptions(opts...) + + service := http.NewService( + http.Logger(options.Logger), + http.Name(options.Config.Service.Name), + http.Version(version.GetString()), + http.Namespace(options.Config.HTTP.Namespace), + http.Address(options.Config.HTTP.Addr), + http.Context(options.Context), + ) + + handle := svc.NewService( + svc.Logger(options.Logger), + svc.Config(options.Config), + svc.Middleware( + middleware.RealIP, + middleware.RequestID, + // ocismiddleware.Secure, + ocismiddleware.Version( + options.Config.Service.Name, + version.GetString(), + ), + ocismiddleware.Logger(options.Logger), + ), + svc.ThumbnailStorage( + storage.NewFileSystemStorage( + options.Config.Thumbnail.FileSystemStorage, + options.Logger, + ), + ), + ) + + { + handle = svc.NewInstrument(handle, options.Metrics) + handle = svc.NewLogging(handle, options.Logger) + handle = svc.NewTracing(handle) + } + + if err := micro.RegisterHandler(service.Server(), handle); err != nil { + return http.Service{}, err + } + + return service, nil +} diff --git a/extensions/thumbnails/pkg/service/grpc/v0/decorators/base.go b/services/thumbnails/pkg/service/grpc/v0/decorators/base.go similarity index 100% rename from extensions/thumbnails/pkg/service/grpc/v0/decorators/base.go rename to services/thumbnails/pkg/service/grpc/v0/decorators/base.go diff --git a/services/thumbnails/pkg/service/grpc/v0/decorators/instrument.go b/services/thumbnails/pkg/service/grpc/v0/decorators/instrument.go new file mode 100644 index 00000000000..66f481ee1df --- /dev/null +++ b/services/thumbnails/pkg/service/grpc/v0/decorators/instrument.go @@ -0,0 +1,39 @@ +package decorators + +import ( + "context" + + thumbnailssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/thumbnails/v0" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/metrics" + "github.com/prometheus/client_golang/prometheus" +) + +// NewInstrument returns a service that instruments metrics. +func NewInstrument(next DecoratedService, metrics *metrics.Metrics) DecoratedService { + return instrument{ + Decorator: Decorator{next: next}, + metrics: metrics, + } +} + +type instrument struct { + Decorator + metrics *metrics.Metrics +} + +// GetThumbnail implements the ThumbnailServiceHandler interface. +func (i instrument) GetThumbnail(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, rsp *thumbnailssvc.GetThumbnailResponse) error { + timer := prometheus.NewTimer(prometheus.ObserverFunc(func(v float64) { + us := v * 1000_000 + i.metrics.Latency.WithLabelValues().Observe(us) + i.metrics.Duration.WithLabelValues().Observe(v) + })) + defer timer.ObserveDuration() + + err := i.next.GetThumbnail(ctx, req, rsp) + + if err != nil { + i.metrics.Counter.WithLabelValues().Inc() + } + return err +} diff --git a/extensions/thumbnails/pkg/service/grpc/v0/decorators/logging.go b/services/thumbnails/pkg/service/grpc/v0/decorators/logging.go similarity index 100% rename from extensions/thumbnails/pkg/service/grpc/v0/decorators/logging.go rename to services/thumbnails/pkg/service/grpc/v0/decorators/logging.go diff --git a/services/thumbnails/pkg/service/grpc/v0/decorators/tracing.go b/services/thumbnails/pkg/service/grpc/v0/decorators/tracing.go new file mode 100644 index 00000000000..d19828a70c3 --- /dev/null +++ b/services/thumbnails/pkg/service/grpc/v0/decorators/tracing.go @@ -0,0 +1,42 @@ +package decorators + +import ( + "context" + + "go.opentelemetry.io/otel/trace" + + thumbnailssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/thumbnails/v0" + thumbnailsTracing "github.com/owncloud/ocis/v2/services/thumbnails/pkg/tracing" + "go.opentelemetry.io/otel/attribute" +) + +// NewTracing returns a service that instruments traces. +func NewTracing(next DecoratedService) DecoratedService { + return tracing{ + Decorator: Decorator{next: next}, + } +} + +type tracing struct { + Decorator +} + +// GetThumbnail implements the ThumbnailServiceHandler interface. +func (t tracing) GetThumbnail(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, rsp *thumbnailssvc.GetThumbnailResponse) error { + var span trace.Span + + if thumbnailsTracing.TraceProvider != nil { + tracer := thumbnailsTracing.TraceProvider.Tracer("thumbnails") + ctx, span = tracer.Start(ctx, "Thumbnails.GetThumbnail") + defer span.End() + + span.SetAttributes( + attribute.KeyValue{Key: "filepath", Value: attribute.StringValue(req.Filepath)}, + attribute.KeyValue{Key: "thumbnail_type", Value: attribute.StringValue(req.ThumbnailType.String())}, + attribute.KeyValue{Key: "width", Value: attribute.IntValue(int(req.Width))}, + attribute.KeyValue{Key: "height", Value: attribute.IntValue(int(req.Height))}, + ) + } + + return t.next.GetThumbnail(ctx, req, rsp) +} diff --git a/services/thumbnails/pkg/service/grpc/v0/option.go b/services/thumbnails/pkg/service/grpc/v0/option.go new file mode 100644 index 00000000000..19b341c5804 --- /dev/null +++ b/services/thumbnails/pkg/service/grpc/v0/option.go @@ -0,0 +1,84 @@ +package svc + +import ( + "net/http" + + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/config" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/thumbnail/imgsource" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/thumbnail/storage" +) + +// 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 + Config *config.Config + Middleware []func(http.Handler) http.Handler + ThumbnailStorage storage.Storage + ImageSource imgsource.Source + CS3Source imgsource.Source + CS3Client gateway.GatewayAPIClient +} + +// 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 + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} + +// Middleware provides a function to set the middleware option. +func Middleware(val ...func(http.Handler) http.Handler) Option { + return func(o *Options) { + o.Middleware = val + } +} + +// ThumbnailStorage provides a function to set the thumbnail storage option. +func ThumbnailStorage(val storage.Storage) Option { + return func(o *Options) { + o.ThumbnailStorage = val + } +} + +// ThumbnailSource provides a function to set the image source option. +func ThumbnailSource(val imgsource.Source) Option { + return func(o *Options) { + o.ImageSource = val + } +} + +func CS3Source(val imgsource.Source) Option { + return func(o *Options) { + o.CS3Source = val + } +} + +func CS3Client(c gateway.GatewayAPIClient) Option { + return func(o *Options) { + o.CS3Client = c + } +} diff --git a/services/thumbnails/pkg/service/grpc/v0/service.go b/services/thumbnails/pkg/service/grpc/v0/service.go new file mode 100644 index 00000000000..e6cc0cac598 --- /dev/null +++ b/services/thumbnails/pkg/service/grpc/v0/service.go @@ -0,0 +1,295 @@ +package svc + +import ( + "context" + "image" + "net/url" + "path" + "strings" + "time" + + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + 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/v2/ocis-pkg/log" + thumbnailsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/thumbnails/v0" + thumbnailssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/thumbnails/v0" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/preprocessor" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/service/grpc/v0/decorators" + tjwt "github.com/owncloud/ocis/v2/services/thumbnails/pkg/service/jwt" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/thumbnail" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/thumbnail/imgsource" + "github.com/pkg/errors" + merrors "go-micro.dev/v4/errors" + "google.golang.org/grpc/metadata" +) + +// NewService returns a service implementation for Service. +func NewService(opts ...Option) decorators.DecoratedService { + options := newOptions(opts...) + logger := options.Logger + resolutions, err := thumbnail.ParseResolutions(options.Config.Thumbnail.Resolutions) + if err != nil { + logger.Fatal().Err(err).Msg("resolutions not configured correctly") + } + svc := Thumbnail{ + serviceID: options.Config.GRPC.Namespace + "." + options.Config.Service.Name, + manager: thumbnail.NewSimpleManager( + resolutions, + options.ThumbnailStorage, + logger, + ), + webdavSource: options.ImageSource, + cs3Source: options.CS3Source, + logger: logger, + cs3Client: options.CS3Client, + preprocessorOpts: PreprocessorOpts{ + TxtFontFileMap: options.Config.Thumbnail.FontMapFile, + }, + dataEndpoint: options.Config.Thumbnail.DataEndpoint, + transferSecret: options.Config.Thumbnail.TransferSecret, + } + + return svc +} + +// Thumbnail implements the GRPC handler. +type Thumbnail struct { + serviceID string + dataEndpoint string + transferSecret string + manager thumbnail.Manager + webdavSource imgsource.Source + cs3Source imgsource.Source + logger log.Logger + cs3Client gateway.GatewayAPIClient + preprocessorOpts PreprocessorOpts +} + +type PreprocessorOpts struct { + TxtFontFileMap string +} + +// GetThumbnail retrieves a thumbnail for an image +func (g Thumbnail) GetThumbnail(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, rsp *thumbnailssvc.GetThumbnailResponse) error { + tType, ok := thumbnailsmsg.ThumbnailType_name[int32(req.ThumbnailType)] + if !ok { + g.logger.Debug().Str("thumbnail_type", tType).Msg("unsupported thumbnail type") + return nil + } + generator, err := thumbnail.GeneratorForType(tType) + if err != nil { + g.logger.Debug().Str("thumbnail_type", tType).Msg("unsupported thumbnail type") + return nil + } + encoder, err := thumbnail.EncoderForType(tType) + if err != nil { + g.logger.Debug().Str("thumbnail_type", tType).Msg("unsupported thumbnail type") + return nil + } + + var key string + switch { + case req.GetWebdavSource() != nil: + key, err = g.handleWebdavSource(ctx, req, generator, encoder) + case req.GetCs3Source() != nil: + key, err = g.handleCS3Source(ctx, req, generator, encoder) + default: + g.logger.Error().Msg("no image source provided") + return merrors.BadRequest(g.serviceID, "image source is missing") + } + if err != nil { + return err + } + + claims := tjwt.ThumbnailClaims{ + Key: key, + RegisteredClaims: jwt.RegisteredClaims{ + IssuedAt: jwt.NewNumericDate(time.Now()), + ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Minute)), + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + transferToken, err := token.SignedString([]byte(g.transferSecret)) + if err != nil { + g.logger.Error(). + Err(err). + Msg("GetThumbnail: failed to sign token") + return merrors.InternalServerError(g.serviceID, "couldn't finish request") + } + rsp.DataEndpoint = g.dataEndpoint + rsp.TransferToken = transferToken + rsp.Mimetype = encoder.MimeType() + return nil +} + +func (g Thumbnail) handleCS3Source(ctx context.Context, + req *thumbnailssvc.GetThumbnailRequest, + generator thumbnail.Generator, + encoder thumbnail.Encoder) (string, error) { + src := req.GetCs3Source() + sRes, err := g.stat(src.Path, src.Authorization) + if err != nil { + return "", err + } + + tr := thumbnail.Request{ + Resolution: image.Rect(0, 0, int(req.Width), int(req.Height)), + Generator: generator, + Encoder: encoder, + Checksum: sRes.GetInfo().GetChecksum().GetSum(), + } + + if key, exists := g.manager.CheckThumbnail(tr); exists { + return key, nil + } + + ctx = imgsource.ContextSetAuthorization(ctx, src.Authorization) + r, err := g.cs3Source.Get(ctx, src.Path) + if err != nil { + return "", merrors.InternalServerError(g.serviceID, "could not get image from source: %s", err.Error()) + } + defer r.Close() // nolint:errcheck + ppOpts := map[string]interface{}{ + "fontFileMap": g.preprocessorOpts.TxtFontFileMap, + } + pp := preprocessor.ForType(sRes.GetInfo().GetMimeType(), ppOpts) + img, err := pp.Convert(r) + if img == nil || err != nil { + return "", merrors.InternalServerError(g.serviceID, "could not get image") + } + + key, err := g.manager.Generate(tr, img) + if err != nil { + return "", err + } + return key, nil +} + +func (g Thumbnail) handleWebdavSource(ctx context.Context, + req *thumbnailssvc.GetThumbnailRequest, + generator thumbnail.Generator, + encoder thumbnail.Encoder) (string, error) { + src := req.GetWebdavSource() + imgURL, err := url.Parse(src.Url) + if err != nil { + return "", errors.Wrap(err, "source url is invalid") + } + + var auth, statPath string + + if src.IsPublicLink { + q := imgURL.Query() + var rsp *gateway.AuthenticateResponse + if q.Get("signature") != "" && q.Get("expiration") != "" { + // Handle pre-signed public links + sig := q.Get("signature") + exp := q.Get("expiration") + rsp, err = g.cs3Client.Authenticate(ctx, &gateway.AuthenticateRequest{ + Type: "publicshares", + ClientId: src.PublicLinkToken, + ClientSecret: strings.Join([]string{"signature", sig, exp}, "|"), + }) + } else { + rsp, err = g.cs3Client.Authenticate(ctx, &gateway.AuthenticateRequest{ + Type: "publicshares", + ClientId: src.PublicLinkToken, + // We pass an empty password because we expect non pre-signed public links + // to not be password protected + ClientSecret: "password|", + }) + } + + if err != nil { + return "", merrors.InternalServerError(g.serviceID, "could not authenticate: %s", err.Error()) + } + auth = rsp.Token + statPath = path.Join("/public", src.PublicLinkToken, req.Filepath) + } else { + auth = src.RevaAuthorization + statPath = req.Filepath + } + sRes, err := g.stat(statPath, auth) + if err != nil { + return "", err + } + tr := thumbnail.Request{ + Resolution: image.Rect(0, 0, int(req.Width), int(req.Height)), + Generator: generator, + Encoder: encoder, + Checksum: sRes.GetInfo().GetChecksum().GetSum(), + } + + if key, exists := g.manager.CheckThumbnail(tr); exists { + return key, nil + } + + if src.WebdavAuthorization != "" { + ctx = imgsource.ContextSetAuthorization(ctx, src.WebdavAuthorization) + } + imgURL.RawQuery = "" + r, err := g.webdavSource.Get(ctx, imgURL.String()) + if err != nil { + return "", merrors.InternalServerError(g.serviceID, "could not get image from source: %s", err.Error()) + } + defer r.Close() // nolint:errcheck + ppOpts := map[string]interface{}{ + "fontFileMap": g.preprocessorOpts.TxtFontFileMap, + } + pp := preprocessor.ForType(sRes.GetInfo().GetMimeType(), ppOpts) + img, err := pp.Convert(r) + if img == nil || err != nil { + return "", merrors.InternalServerError(g.serviceID, "could not get image") + } + + key, err := g.manager.Generate(tr, img) + if err != nil { + return "", err + } + return key, nil +} + +func (g Thumbnail) stat(path, auth string) (*provider.StatResponse, error) { + ctx := metadata.AppendToOutgoingContext(context.Background(), revactx.TokenHeader, auth) + + ref, err := storagespace.ParseReference(path) + if err != nil { + // If the path is not a spaces reference try to handle it like a plain + // path reference. + ref = provider.Reference{ + Path: path, + } + } + + req := &provider.StatRequest{Ref: &ref} + rsp, err := g.cs3Client.Stat(ctx, req) + if err != nil { + g.logger.Error().Err(err).Str("path", path).Msg("could not stat file") + return nil, merrors.InternalServerError(g.serviceID, "could not stat file: %s", err.Error()) + } + + if rsp.Status.Code != rpc.Code_CODE_OK { + switch rsp.Status.Code { + case rpc.Code_CODE_NOT_FOUND: + return nil, merrors.NotFound(g.serviceID, "could not stat file: %s", rsp.Status.Message) + default: + g.logger.Error().Str("status_message", rsp.Status.Message).Str("path", path).Msg("could not stat file") + return nil, merrors.InternalServerError(g.serviceID, "could not stat file: %s", rsp.Status.Message) + } + } + if rsp.Info.Type != provider.ResourceType_RESOURCE_TYPE_FILE { + return nil, merrors.BadRequest(g.serviceID, "Unsupported file type") + } + if rsp.Info.GetChecksum().GetSum() == "" { + g.logger.Error().Msg("resource info is missing checksum") + return nil, merrors.NotFound(g.serviceID, "resource info is missing a checksum") + } + if !thumbnail.IsMimeTypeSupported(rsp.Info.MimeType) { + return nil, merrors.NotFound(g.serviceID, "Unsupported file type") + } + return rsp, nil +} diff --git a/services/thumbnails/pkg/service/http/v0/instrument.go b/services/thumbnails/pkg/service/http/v0/instrument.go new file mode 100644 index 00000000000..91fc4f9a621 --- /dev/null +++ b/services/thumbnails/pkg/service/http/v0/instrument.go @@ -0,0 +1,30 @@ +package svc + +import ( + "net/http" + + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/metrics" +) + +// NewInstrument returns a service that instruments metrics. +func NewInstrument(next Service, metrics *metrics.Metrics) Service { + return instrument{ + next: next, + metrics: metrics, + } +} + +type instrument struct { + next Service + metrics *metrics.Metrics +} + +// ServeHTTP implements the Service interface. +func (i instrument) ServeHTTP(w http.ResponseWriter, r *http.Request) { + i.next.ServeHTTP(w, r) +} + +// GetThumbnail implements the Service interface. +func (i instrument) GetThumbnail(w http.ResponseWriter, r *http.Request) { + i.next.GetThumbnail(w, r) +} diff --git a/extensions/thumbnails/pkg/service/http/v0/logging.go b/services/thumbnails/pkg/service/http/v0/logging.go similarity index 100% rename from extensions/thumbnails/pkg/service/http/v0/logging.go rename to services/thumbnails/pkg/service/http/v0/logging.go diff --git a/services/thumbnails/pkg/service/http/v0/option.go b/services/thumbnails/pkg/service/http/v0/option.go new file mode 100644 index 00000000000..13f2f52a7f8 --- /dev/null +++ b/services/thumbnails/pkg/service/http/v0/option.go @@ -0,0 +1,59 @@ +package svc + +import ( + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/config" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/thumbnail/storage" +) + +// 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 + Config *config.Config + Middleware []func(http.Handler) http.Handler + ThumbnailStorage storage.Storage +} + +// 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 + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} + +// Middleware provides a function to set the middleware option. +func Middleware(val ...func(http.Handler) http.Handler) Option { + return func(o *Options) { + o.Middleware = val + } +} + +// ThumbnailStorage provides a function to set the ThumbnailStorage option. +func ThumbnailStorage(storage storage.Storage) Option { + return func(o *Options) { + o.ThumbnailStorage = storage + } +} diff --git a/services/thumbnails/pkg/service/http/v0/service.go b/services/thumbnails/pkg/service/http/v0/service.go new file mode 100644 index 00000000000..a124ab7e631 --- /dev/null +++ b/services/thumbnails/pkg/service/http/v0/service.go @@ -0,0 +1,123 @@ +package svc + +import ( + "context" + "fmt" + "net/http" + "strconv" + + "github.com/go-chi/chi/v5" + "github.com/golang-jwt/jwt/v4" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/config" + tjwt "github.com/owncloud/ocis/v2/services/thumbnails/pkg/service/jwt" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/thumbnail" +) + +type contextKey string + +const ( + keyContextKey contextKey = "key" +) + +// Service defines the extension handlers. +type Service interface { + ServeHTTP(http.ResponseWriter, *http.Request) + GetThumbnail(http.ResponseWriter, *http.Request) +} + +// NewService returns a service implementation for Service. +func NewService(opts ...Option) Service { + options := newOptions(opts...) + + m := chi.NewMux() + m.Use(options.Middleware...) + + logger := options.Logger + resolutions, err := thumbnail.ParseResolutions(options.Config.Thumbnail.Resolutions) + if err != nil { + logger.Fatal().Err(err).Msg("resolutions not configured correctly") + } + svc := Thumbnails{ + config: options.Config, + mux: m, + logger: options.Logger, + manager: thumbnail.NewSimpleManager( + resolutions, + options.ThumbnailStorage, + logger, + ), + } + + m.Route(options.Config.HTTP.Root, func(r chi.Router) { + r.Use(svc.TransferTokenValidator) + r.Get("/data", svc.GetThumbnail) + }) + + return svc +} + +// Thumbnails implements the business logic for Service. +type Thumbnails struct { + config *config.Config + logger log.Logger + mux *chi.Mux + manager thumbnail.Manager +} + +// ServeHTTP implements the Service interface. +func (s Thumbnails) ServeHTTP(w http.ResponseWriter, r *http.Request) { + s.mux.ServeHTTP(w, r) +} + +// GetThumbnail implements the Service interface. +func (s Thumbnails) GetThumbnail(w http.ResponseWriter, r *http.Request) { + key := r.Context().Value(keyContextKey).(string) + + thumbnail, err := s.manager.GetThumbnail(key) + if err != nil { + s.logger.Error(). + Err(err). + Str("key", key). + Msg("could not get the thumbnail") + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Length", strconv.Itoa(len(thumbnail))) + if _, err = w.Write(thumbnail); err != nil { + s.logger.Error(). + Err(err). + Str("key", key). + Msg("could not write the thumbnail response") + } +} + +func (s Thumbnails) TransferTokenValidator(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + tokenString := r.Header.Get("Transfer-Token") + token, err := jwt.ParseWithClaims(tokenString, &tjwt.ThumbnailClaims{}, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + return []byte(s.config.Thumbnail.TransferSecret), nil + }) + if err != nil { + s.logger.Error(). + Err(err). + Str("transfer-token", tokenString). + Msg("failed to parse transfer token") + w.WriteHeader(http.StatusUnauthorized) + return + } + + if claims, ok := token.Claims.(*tjwt.ThumbnailClaims); ok && token.Valid { + ctx := context.WithValue(r.Context(), keyContextKey, claims.Key) + next.ServeHTTP(w, r.WithContext(ctx)) + return + } + w.WriteHeader(http.StatusUnauthorized) + }) +} diff --git a/extensions/thumbnails/pkg/service/http/v0/tracing.go b/services/thumbnails/pkg/service/http/v0/tracing.go similarity index 100% rename from extensions/thumbnails/pkg/service/http/v0/tracing.go rename to services/thumbnails/pkg/service/http/v0/tracing.go diff --git a/extensions/thumbnails/pkg/service/jwt/jwt.go b/services/thumbnails/pkg/service/jwt/jwt.go similarity index 100% rename from extensions/thumbnails/pkg/service/jwt/jwt.go rename to services/thumbnails/pkg/service/jwt/jwt.go diff --git a/extensions/thumbnails/pkg/thumbnail/encoding.go b/services/thumbnails/pkg/thumbnail/encoding.go similarity index 100% rename from extensions/thumbnails/pkg/thumbnail/encoding.go rename to services/thumbnails/pkg/thumbnail/encoding.go diff --git a/extensions/thumbnails/pkg/thumbnail/encoding_test.go b/services/thumbnails/pkg/thumbnail/encoding_test.go similarity index 100% rename from extensions/thumbnails/pkg/thumbnail/encoding_test.go rename to services/thumbnails/pkg/thumbnail/encoding_test.go diff --git a/extensions/thumbnails/pkg/thumbnail/generator.go b/services/thumbnails/pkg/thumbnail/generator.go similarity index 100% rename from extensions/thumbnails/pkg/thumbnail/generator.go rename to services/thumbnails/pkg/thumbnail/generator.go diff --git a/services/thumbnails/pkg/thumbnail/imgsource/cs3.go b/services/thumbnails/pkg/thumbnail/imgsource/cs3.go new file mode 100644 index 00000000000..b9d46bbea62 --- /dev/null +++ b/services/thumbnails/pkg/thumbnail/imgsource/cs3.go @@ -0,0 +1,97 @@ +package imgsource + +import ( + "context" + "crypto/tls" + "fmt" + "io" + "net/http" + + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + 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/rhttp" + "github.com/cs3org/reva/v2/pkg/storagespace" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/config" + "github.com/pkg/errors" + "google.golang.org/grpc/metadata" +) + +const ( + // "github.com/cs3org/reva/v2/internal/http/services/datagateway" is internal so we redeclare it here + // TokenTransportHeader holds the header key for the reva transfer token + TokenTransportHeader = "X-Reva-Transfer" +) + +type CS3 struct { + client gateway.GatewayAPIClient + insecure bool +} + +func NewCS3Source(cfg config.Thumbnail, c gateway.GatewayAPIClient) CS3 { + return CS3{ + client: c, + insecure: cfg.CS3AllowInsecure, + } +} + +// Get downloads the file from a cs3 service +// The caller MUST make sure to close the returned ReadCloser +func (s CS3) Get(ctx context.Context, path string) (io.ReadCloser, error) { + auth, ok := ContextGetAuthorization(ctx) + if !ok { + return nil, errors.New("cs3source: authorization missing") + } + ref, err := storagespace.ParseReference(path) + if err != nil { + // If the path is not a spaces reference try to handle it like a plain + // path reference. + ref = provider.Reference{ + Path: path, + } + } + ctx = metadata.AppendToOutgoingContext(context.Background(), revactx.TokenHeader, auth) + rsp, err := s.client.InitiateFileDownload(ctx, &provider.InitiateFileDownloadRequest{Ref: &ref}) + + if err != nil { + return nil, err + } + + if rsp.Status.Code != rpc.Code_CODE_OK { + return nil, fmt.Errorf("could not load image: %s", rsp.Status.Message) + } + var ep, tk string + for _, p := range rsp.Protocols { + if p.Protocol == "spaces" { + ep, tk = p.DownloadEndpoint, p.Token + break + } + } + if (ep == "" || tk == "") && len(rsp.Protocols) > 0 { + ep, tk = rsp.Protocols[0].DownloadEndpoint, rsp.Protocols[0].Token + } + + httpReq, err := rhttp.NewRequest(ctx, "GET", ep, nil) + if err != nil { + return nil, err + } + httpReq.Header.Set(revactx.TokenHeader, auth) + httpReq.Header.Set(TokenTransportHeader, tk) + + http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{ + InsecureSkipVerify: s.insecure, //nolint:gosec + } + client := &http.Client{} + + resp, err := client.Do(httpReq) // nolint:bodyclose + if err != nil { + return nil, err + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("could not get the image \"%s\". Request returned with statuscode %d ", path, resp.StatusCode) + } + + return resp.Body, nil +} diff --git a/extensions/thumbnails/pkg/thumbnail/imgsource/imgsource.go b/services/thumbnails/pkg/thumbnail/imgsource/imgsource.go similarity index 100% rename from extensions/thumbnails/pkg/thumbnail/imgsource/imgsource.go rename to services/thumbnails/pkg/thumbnail/imgsource/imgsource.go diff --git a/extensions/thumbnails/pkg/thumbnail/imgsource/webdav.go b/services/thumbnails/pkg/thumbnail/imgsource/webdav.go similarity index 95% rename from extensions/thumbnails/pkg/thumbnail/imgsource/webdav.go rename to services/thumbnails/pkg/thumbnail/imgsource/webdav.go index 00287d102d5..3d1edd56f90 100644 --- a/extensions/thumbnails/pkg/thumbnail/imgsource/webdav.go +++ b/services/thumbnails/pkg/thumbnail/imgsource/webdav.go @@ -10,7 +10,7 @@ import ( "io" "net/http" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/config" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/config" "github.com/pkg/errors" ) diff --git a/extensions/thumbnails/pkg/thumbnail/resolutions.go b/services/thumbnails/pkg/thumbnail/resolutions.go similarity index 100% rename from extensions/thumbnails/pkg/thumbnail/resolutions.go rename to services/thumbnails/pkg/thumbnail/resolutions.go diff --git a/extensions/thumbnails/pkg/thumbnail/resolutions_test.go b/services/thumbnails/pkg/thumbnail/resolutions_test.go similarity index 100% rename from extensions/thumbnails/pkg/thumbnail/resolutions_test.go rename to services/thumbnails/pkg/thumbnail/resolutions_test.go diff --git a/extensions/thumbnails/pkg/thumbnail/storage/filesystem.go b/services/thumbnails/pkg/thumbnail/storage/filesystem.go similarity index 97% rename from extensions/thumbnails/pkg/thumbnail/storage/filesystem.go rename to services/thumbnails/pkg/thumbnail/storage/filesystem.go index 4b4d9fc7ff5..692669d3024 100644 --- a/extensions/thumbnails/pkg/thumbnail/storage/filesystem.go +++ b/services/thumbnails/pkg/thumbnail/storage/filesystem.go @@ -6,8 +6,8 @@ import ( "path/filepath" "strconv" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/config" "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/config" "github.com/pkg/errors" ) diff --git a/extensions/thumbnails/pkg/thumbnail/storage/inmemory.go b/services/thumbnails/pkg/thumbnail/storage/inmemory.go similarity index 100% rename from extensions/thumbnails/pkg/thumbnail/storage/inmemory.go rename to services/thumbnails/pkg/thumbnail/storage/inmemory.go diff --git a/extensions/thumbnails/pkg/thumbnail/storage/storage.go b/services/thumbnails/pkg/thumbnail/storage/storage.go similarity index 100% rename from extensions/thumbnails/pkg/thumbnail/storage/storage.go rename to services/thumbnails/pkg/thumbnail/storage/storage.go diff --git a/services/thumbnails/pkg/thumbnail/thumbnail.go b/services/thumbnails/pkg/thumbnail/thumbnail.go new file mode 100644 index 00000000000..c1efbe22e9b --- /dev/null +++ b/services/thumbnails/pkg/thumbnail/thumbnail.go @@ -0,0 +1,111 @@ +package thumbnail + +import ( + "bytes" + "image" + "image/gif" + "mime" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/thumbnail/storage" +) + +var ( + // SupportedMimeTypes contains a all mimetypes which are supported by the thumbnailer. + SupportedMimeTypes = map[string]struct{}{ + "image/png": {}, + "image/jpg": {}, + "image/jpeg": {}, + "image/gif": {}, + "text/plain": {}, + } +) + +// Request bundles information needed to generate a thumbnail for afile +type Request struct { + Resolution image.Rectangle + Encoder Encoder + Generator Generator + Checksum string +} + +// Manager is responsible for generating thumbnails +type Manager interface { + // Generate creates a thumbnail and stores it. + // The function returns a key with which the actual file can be retrieved. + Generate(Request, interface{}) (string, error) + // CheckThumbnail checks if a thumbnail with the requested attributes exists. + // The function will return a status if the file exists and the key to the file. + CheckThumbnail(Request) (string, bool) + // GetThumbnail will load the thumbnail from the storage and return its content. + GetThumbnail(key string) ([]byte, error) +} + +// NewSimpleManager creates a new instance of SimpleManager +func NewSimpleManager(resolutions Resolutions, storage storage.Storage, logger log.Logger) SimpleManager { + return SimpleManager{ + storage: storage, + logger: logger, + resolutions: resolutions, + } +} + +// SimpleManager is a simple implementation of Manager +type SimpleManager struct { + storage storage.Storage + logger log.Logger + resolutions Resolutions +} + +func (s SimpleManager) Generate(r Request, img interface{}) (string, error) { + var match image.Rectangle + switch m := img.(type) { + case *gif.GIF: + match = s.resolutions.ClosestMatch(r.Resolution, m.Image[0].Bounds()) + case image.Image: + match = s.resolutions.ClosestMatch(r.Resolution, m.Bounds()) + } + + thumbnail, err := r.Generator.GenerateThumbnail(match, img) + if err != nil { + return "", err + } + + buf := new(bytes.Buffer) + if err := r.Encoder.Encode(buf, thumbnail); err != nil { + return "", err + } + + k := s.storage.BuildKey(mapToStorageRequest(r)) + if err := s.storage.Put(k, buf.Bytes()); err != nil { + s.logger.Error().Err(err).Msg("could not store thumbnail") + return "", err + } + return k, nil +} + +func (s SimpleManager) CheckThumbnail(r Request) (string, bool) { + k := s.storage.BuildKey(mapToStorageRequest(r)) + return k, s.storage.Stat(k) +} + +func (s SimpleManager) GetThumbnail(key string) ([]byte, error) { + return s.storage.Get(key) +} + +func mapToStorageRequest(r Request) storage.Request { + return storage.Request{ + Checksum: r.Checksum, + Resolution: r.Resolution, + Types: r.Encoder.Types(), + } +} + +func IsMimeTypeSupported(m string) bool { + mimeType, _, err := mime.ParseMediaType(m) + if err != nil { + return false + } + _, supported := SupportedMimeTypes[mimeType] + return supported +} diff --git a/extensions/thumbnails/pkg/thumbnail/thumbnail_test.go b/services/thumbnails/pkg/thumbnail/thumbnail_test.go similarity index 91% rename from extensions/thumbnails/pkg/thumbnail/thumbnail_test.go rename to services/thumbnails/pkg/thumbnail/thumbnail_test.go index 3b368d12cdc..cf301a0ddb4 100644 --- a/extensions/thumbnails/pkg/thumbnail/thumbnail_test.go +++ b/services/thumbnails/pkg/thumbnail/thumbnail_test.go @@ -6,8 +6,8 @@ import ( "path/filepath" "testing" - "github.com/owncloud/ocis/v2/extensions/thumbnails/pkg/thumbnail/storage" "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/thumbnail/storage" ) type NoOpManager struct { diff --git a/services/thumbnails/pkg/tracing/tracing.go b/services/thumbnails/pkg/tracing/tracing.go new file mode 100644 index 00000000000..984cdc95bdc --- /dev/null +++ b/services/thumbnails/pkg/tracing/tracing.go @@ -0,0 +1,23 @@ +package tracing + +import ( + pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/config" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the thumbnails service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config) error { + var err error + if cfg.Tracing.Enabled { + if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { + return err + } + } + + return nil +} diff --git a/extensions/thumbnails/reflex.conf b/services/thumbnails/reflex.conf similarity index 100% rename from extensions/thumbnails/reflex.conf rename to services/thumbnails/reflex.conf diff --git a/extensions/thumbnails/testdata/oc.png b/services/thumbnails/testdata/oc.png similarity index 100% rename from extensions/thumbnails/testdata/oc.png rename to services/thumbnails/testdata/oc.png diff --git a/extensions/users/Makefile b/services/users/Makefile similarity index 100% rename from extensions/users/Makefile rename to services/users/Makefile diff --git a/services/users/cmd/user/main.go b/services/users/cmd/user/main.go new file mode 100644 index 00000000000..cae358d3c0b --- /dev/null +++ b/services/users/cmd/user/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/users/pkg/command" + "github.com/owncloud/ocis/v2/services/users/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/services/users/pkg/command/health.go b/services/users/pkg/command/health.go new file mode 100644 index 00000000000..729dcf4d095 --- /dev/null +++ b/services/users/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/users/pkg/config" + "github.com/owncloud/ocis/v2/services/users/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/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/services/users/pkg/command/root.go b/services/users/pkg/command/root.go new file mode 100644 index 00000000000..7ce50ddf076 --- /dev/null +++ b/services/users/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/users/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.Users.Commons = cfg.Commons + return SutureService{ + cfg: cfg.Users, + } +} + +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/services/users/pkg/command/server.go b/services/users/pkg/command/server.go new file mode 100644 index 00000000000..0073a8a6377 --- /dev/null +++ b/services/users/pkg/command/server.go @@ -0,0 +1,121 @@ +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/v2/ocis-pkg/ldap" + "github.com/owncloud/ocis/v2/ocis-pkg/service/external" + "github.com/owncloud/ocis/v2/ocis-pkg/sync" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/users/pkg/config" + "github.com/owncloud/ocis/v2/services/users/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/users/pkg/logging" + "github.com/owncloud/ocis/v2/services/users/pkg/revaconfig" + "github.com/owncloud/ocis/v2/services/users/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/users/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) + os.Exit(1) + } + 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.GetString(), + 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/services/users/pkg/command/version.go b/services/users/pkg/command/version.go new file mode 100644 index 00000000000..d4f8c4038aa --- /dev/null +++ b/services/users/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/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.GetString()) + 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/users/pkg/config/config.go b/services/users/pkg/config/config.go similarity index 100% rename from extensions/users/pkg/config/config.go rename to services/users/pkg/config/config.go diff --git a/services/users/pkg/config/defaults/defaultconfig.go b/services/users/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..fb8c5b13ac7 --- /dev/null +++ b/services/users/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,126 @@ +package defaults + +import ( + "path/filepath" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" + "github.com/owncloud/ocis/v2/services/users/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:9145", + Token: "", + Pprof: false, + Zpages: false, + }, + GRPC: config.GRPCConfig{ + Addr: "127.0.0.1:9144", + Namespace: "com.owncloud.api", + Protocol: "tcp", + }, + Service: config.Service{ + Name: "users", + }, + Reva: &config.Reva{ + Address: "127.0.0.1:9142", + }, + Driver: "ldap", + Drivers: config.Drivers{ + LDAP: config.LDAPDriver{ + URI: "ldaps://localhost:9235", + CACert: filepath.Join(defaults.BaseDataPath(), "idm", "ldap.crt"), + Insecure: false, + UserBaseDN: "ou=users,o=libregraph-idm", + GroupBaseDN: "ou=groups,o=libregraph-idm", + UserScope: "sub", + GroupScope: "sub", + UserFilter: "", + GroupFilter: "", + UserObjectClass: "inetOrgPerson", + GroupObjectClass: "groupOfNames", + BindDN: "uid=reva,ou=sysusers,o=libregraph-idm", + IDP: "https://localhost:9200", + UserSchema: config.LDAPUserSchema{ + ID: "ownclouduuid", + Mail: "mail", + DisplayName: "displayname", + Username: "uid", + }, + GroupSchema: config.LDAPGroupSchema{ + ID: "ownclouduuid", + Mail: "mail", + DisplayName: "cn", + Groupname: "cn", + Member: "member", + }, + }, + JSON: config.JSONDriver{}, + OwnCloudSQL: config.OwnCloudSQLDriver{ + DBUsername: "owncloud", + DBPassword: "secret", + DBHost: "mysql", + DBPort: 3306, + DBName: "owncloud", + IDP: "https://localhost:9200", + Nobody: 90, + JoinUsername: false, + JoinOwnCloudUUID: false, + EnableMedialSearch: false, + }, + }, + } +} + +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/services/users/pkg/config/parser/parse.go b/services/users/pkg/config/parser/parse.go new file mode 100644 index 00000000000..b776daa64cd --- /dev/null +++ b/services/users/pkg/config/parser/parse.go @@ -0,0 +1,46 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/services/users/pkg/config" + "github.com/owncloud/ocis/v2/services/users/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + if cfg.TokenManager.JWTSecret == "" { + return shared.MissingJWTTokenError(cfg.Service.Name) + } + + if cfg.Driver == "ldap" && cfg.Drivers.LDAP.BindPassword == "" { + return shared.MissingLDAPBindPassword(cfg.Service.Name) + } + + return nil +} diff --git a/extensions/users/pkg/config/reva.go b/services/users/pkg/config/reva.go similarity index 100% rename from extensions/users/pkg/config/reva.go rename to services/users/pkg/config/reva.go diff --git a/services/users/pkg/logging/logging.go b/services/users/pkg/logging/logging.go new file mode 100644 index 00000000000..88769574a54 --- /dev/null +++ b/services/users/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/users/pkg/config" +) + +// 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/services/users/pkg/revaconfig/config.go b/services/users/pkg/revaconfig/config.go new file mode 100644 index 00000000000..434daf23d37 --- /dev/null +++ b/services/users/pkg/revaconfig/config.go @@ -0,0 +1,85 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/v2/services/users/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, + "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/services/users/pkg/server/debug/option.go b/services/users/pkg/server/debug/option.go new file mode 100644 index 00000000000..2b1b96a8a5d --- /dev/null +++ b/services/users/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/users/pkg/config" +) + +// 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/services/users/pkg/server/debug/server.go b/services/users/pkg/server/debug/server.go new file mode 100644 index 00000000000..b7871834c17 --- /dev/null +++ b/services/users/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/users/pkg/config" +) + +// 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.GetString()), + 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/services/users/pkg/tracing/tracing.go b/services/users/pkg/tracing/tracing.go new file mode 100644 index 00000000000..b75884091dc --- /dev/null +++ b/services/users/pkg/tracing/tracing.go @@ -0,0 +1,18 @@ +package tracing + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/users/pkg/config" + "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/.dockerignore b/services/web/.dockerignore similarity index 100% rename from extensions/web/.dockerignore rename to services/web/.dockerignore diff --git a/extensions/web/Makefile b/services/web/Makefile similarity index 100% rename from extensions/web/Makefile rename to services/web/Makefile diff --git a/extensions/web/assets/.keep b/services/web/assets/.keep similarity index 100% rename from extensions/web/assets/.keep rename to services/web/assets/.keep diff --git a/services/web/cmd/web/main.go b/services/web/cmd/web/main.go new file mode 100644 index 00000000000..9974c37fc9a --- /dev/null +++ b/services/web/cmd/web/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/web/pkg/command" + "github.com/owncloud/ocis/v2/services/web/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/web/docker/Dockerfile.linux.amd64 b/services/web/docker/Dockerfile.linux.amd64 similarity index 100% rename from extensions/web/docker/Dockerfile.linux.amd64 rename to services/web/docker/Dockerfile.linux.amd64 diff --git a/extensions/web/docker/Dockerfile.linux.arm b/services/web/docker/Dockerfile.linux.arm similarity index 100% rename from extensions/web/docker/Dockerfile.linux.arm rename to services/web/docker/Dockerfile.linux.arm diff --git a/extensions/web/docker/Dockerfile.linux.arm64 b/services/web/docker/Dockerfile.linux.arm64 similarity index 100% rename from extensions/web/docker/Dockerfile.linux.arm64 rename to services/web/docker/Dockerfile.linux.arm64 diff --git a/extensions/web/docker/manifest.tmpl b/services/web/docker/manifest.tmpl similarity index 100% rename from extensions/web/docker/manifest.tmpl rename to services/web/docker/manifest.tmpl diff --git a/services/web/pkg/assets/option.go b/services/web/pkg/assets/option.go new file mode 100644 index 00000000000..c839c29dddb --- /dev/null +++ b/services/web/pkg/assets/option.go @@ -0,0 +1,50 @@ +package assets + +import ( + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/assetsfs" + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/web" + "github.com/owncloud/ocis/v2/services/web/pkg/config" +) + +// New returns a new http filesystem to serve assets. +func New(opts ...Option) http.FileSystem { + options := newOptions(opts...) + return assetsfs.New(web.Assets, options.Config.Asset.Path, options.Logger) +} + +// 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 + 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 + } +} + +// 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/web/pkg/assets/server.go b/services/web/pkg/assets/server.go similarity index 100% rename from extensions/web/pkg/assets/server.go rename to services/web/pkg/assets/server.go diff --git a/services/web/pkg/command/health.go b/services/web/pkg/command/health.go new file mode 100644 index 00000000000..633067f8567 --- /dev/null +++ b/services/web/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/web/pkg/config" + "github.com/owncloud/ocis/v2/services/web/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/web/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/services/web/pkg/command/root.go b/services/web/pkg/command/root.go new file mode 100644 index 00000000000..ef57b495663 --- /dev/null +++ b/services/web/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/web/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 web command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "web", + Usage: "Serve ownCloud Web 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 web command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new web.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.Web.Commons = cfg.Commons + return SutureService{ + cfg: cfg.Web, + } +} + +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/services/web/pkg/command/server.go b/services/web/pkg/command/server.go new file mode 100644 index 00000000000..ba7f1f539cb --- /dev/null +++ b/services/web/pkg/command/server.go @@ -0,0 +1,125 @@ +package command + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "os" + + "github.com/oklog/run" + "github.com/owncloud/ocis/v2/services/web/pkg/config" + "github.com/owncloud/ocis/v2/services/web/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/web/pkg/logging" + "github.com/owncloud/ocis/v2/services/web/pkg/metrics" + "github.com/owncloud/ocis/v2/services/web/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/web/pkg/server/http" + "github.com/owncloud/ocis/v2/services/web/pkg/tracing" + "github.com/urfave/cli/v2" +) + +// Server is the entrypoint 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) + os.Exit(1) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg) + if err != nil { + return err + } + + // actually read the contents of the config file and override defaults + if cfg.File != "" { + contents, err := ioutil.ReadFile(cfg.File) + if err != nil { + logger.Err(err).Msg("error opening config file") + return err + } + if err = json.Unmarshal(contents, &cfg.Web.Config); err != nil { + logger.Fatal().Err(err).Msg("error unmarshalling config file") + return err + } + } + + var ( + gr = run.Group{} + ctx, cancel = func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() + metrics = metrics.New() + ) + + defer cancel() + + { + server, err := http.Server( + http.Logger(logger), + http.Context(ctx), + http.Namespace(cfg.HTTP.Namespace), + http.Config(cfg), + http.Metrics(metrics), + ) + + if err != nil { + logger.Info(). + Err(err). + Str("transport", "http"). + Msg("Failed to initialize server") + + return err + } + + gr.Add(func() error { + err := server.Run() + if err != nil { + logger.Error(). + Err(err). + Str("transport", "http"). + Msg("Failed to start server") + } + return err + }, func(_ error) { + logger.Info(). + Str("transport", "http"). + Msg("Shutting down server") + + cancel() + }) + } + + { + server, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("transport", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(server.ListenAndServe, func(_ error) { + _ = server.Shutdown(ctx) + cancel() + }) + } + + return gr.Run() + }, + } +} diff --git a/services/web/pkg/command/version.go b/services/web/pkg/command/version.go new file mode 100644 index 00000000000..faef4e15bc9 --- /dev/null +++ b/services/web/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/web/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.GetString()) + 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/web/pkg/config/config.go b/services/web/pkg/config/config.go similarity index 100% rename from extensions/web/pkg/config/config.go rename to services/web/pkg/config/config.go diff --git a/extensions/web/pkg/config/debug.go b/services/web/pkg/config/debug.go similarity index 100% rename from extensions/web/pkg/config/debug.go rename to services/web/pkg/config/debug.go diff --git a/services/web/pkg/config/defaults/defaultconfig.go b/services/web/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..e1f09a6928c --- /dev/null +++ b/services/web/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,94 @@ +package defaults + +import ( + "strings" + + "github.com/owncloud/ocis/v2/services/web/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:9104", + Token: "", + Pprof: false, + Zpages: false, + }, + HTTP: config.HTTP{ + Addr: "127.0.0.1:9100", + Root: "/", + Namespace: "com.owncloud.web", + CacheTTL: 604800, // 7 days + }, + Service: config.Service{ + Name: "web", + }, + Asset: config.Asset{ + Path: "", + }, + Web: config.Web{ + Path: "", + ThemeServer: "https://localhost:9200", + ThemePath: "/themes/owncloud/theme.json", + Config: config.WebConfig{ + Server: "https://localhost:9200", + Theme: "", + Version: "0.1.0", + OpenIDConnect: config.OIDC{ + MetadataURL: "", + Authority: "https://localhost:9200", + ClientID: "web", + ResponseType: "code", + Scope: "openid profile email", + }, + Apps: []string{"files", "search", "preview", "text-editor", "pdf-viewer", "external", "user-management"}, + Options: map[string]interface{}{ + "hideSearchBar": false, + }, + }, + }, + } +} + +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{} + } +} + +func Sanitize(cfg *config.Config) { + // sanitize config + if cfg.HTTP.Root != "/" { + cfg.HTTP.Root = strings.TrimRight(cfg.HTTP.Root, "/") + } + // build well known openid-configuration endpoint if it is not set + if cfg.Web.Config.OpenIDConnect.MetadataURL == "" { + cfg.Web.Config.OpenIDConnect.MetadataURL = strings.TrimRight(cfg.Web.Config.OpenIDConnect.Authority, "/") + "/.well-known/openid-configuration" + } +} diff --git a/extensions/web/pkg/config/http.go b/services/web/pkg/config/http.go similarity index 100% rename from extensions/web/pkg/config/http.go rename to services/web/pkg/config/http.go diff --git a/extensions/web/pkg/config/log.go b/services/web/pkg/config/log.go similarity index 100% rename from extensions/web/pkg/config/log.go rename to services/web/pkg/config/log.go diff --git a/services/web/pkg/config/parser/parse.go b/services/web/pkg/config/parser/parse.go new file mode 100644 index 00000000000..664a6f59691 --- /dev/null +++ b/services/web/pkg/config/parser/parse.go @@ -0,0 +1,37 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/web/pkg/config" + "github.com/owncloud/ocis/v2/services/web/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + return nil +} diff --git a/extensions/web/pkg/config/service.go b/services/web/pkg/config/service.go similarity index 100% rename from extensions/web/pkg/config/service.go rename to services/web/pkg/config/service.go diff --git a/extensions/web/pkg/config/tracing.go b/services/web/pkg/config/tracing.go similarity index 100% rename from extensions/web/pkg/config/tracing.go rename to services/web/pkg/config/tracing.go diff --git a/services/web/pkg/logging/logging.go b/services/web/pkg/logging/logging.go new file mode 100644 index 00000000000..c0a956944db --- /dev/null +++ b/services/web/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/web/pkg/config" +) + +// 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/web/pkg/metrics/metrics.go b/services/web/pkg/metrics/metrics.go similarity index 100% rename from extensions/web/pkg/metrics/metrics.go rename to services/web/pkg/metrics/metrics.go diff --git a/extensions/web/pkg/middleware/silentrefresh.go b/services/web/pkg/middleware/silentrefresh.go similarity index 100% rename from extensions/web/pkg/middleware/silentrefresh.go rename to services/web/pkg/middleware/silentrefresh.go diff --git a/services/web/pkg/server/debug/option.go b/services/web/pkg/server/debug/option.go new file mode 100644 index 00000000000..e1327912186 --- /dev/null +++ b/services/web/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/web/pkg/config" +) + +// 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/services/web/pkg/server/debug/server.go b/services/web/pkg/server/debug/server.go new file mode 100644 index 00000000000..4fa164df782 --- /dev/null +++ b/services/web/pkg/server/debug/server.go @@ -0,0 +1,59 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/web/pkg/config" +) + +// 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.GetString()), + 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)), + ), 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/services/web/pkg/server/http/option.go b/services/web/pkg/server/http/option.go new file mode 100644 index 00000000000..5ae833c3614 --- /dev/null +++ b/services/web/pkg/server/http/option.go @@ -0,0 +1,76 @@ +package http + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/web/pkg/config" + "github.com/owncloud/ocis/v2/services/web/pkg/metrics" + "github.com/urfave/cli/v2" +) + +// 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 + Metrics *metrics.Metrics + Flags []cli.Flag + Namespace string +} + +// 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 + } +} + +// Metrics provides a function to set the metrics option. +func Metrics(val *metrics.Metrics) Option { + return func(o *Options) { + o.Metrics = val + } +} + +// Flags provides a function to set the flags option. +func Flags(val []cli.Flag) Option { + return func(o *Options) { + o.Flags = append(o.Flags, val...) + } +} + +// Namespace provides a function to set the Namespace option. +func Namespace(val string) Option { + return func(o *Options) { + o.Namespace = val + } +} diff --git a/services/web/pkg/server/http/server.go b/services/web/pkg/server/http/server.go new file mode 100644 index 00000000000..6984c65b44a --- /dev/null +++ b/services/web/pkg/server/http/server.go @@ -0,0 +1,57 @@ +package http + +import ( + chimiddleware "github.com/go-chi/chi/v5/middleware" + "github.com/owncloud/ocis/v2/ocis-pkg/middleware" + "github.com/owncloud/ocis/v2/ocis-pkg/service/http" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + webmid "github.com/owncloud/ocis/v2/services/web/pkg/middleware" + svc "github.com/owncloud/ocis/v2/services/web/pkg/service/v0" + "go-micro.dev/v4" +) + +// Server initializes the http service and server. +func Server(opts ...Option) (http.Service, error) { + options := newOptions(opts...) + + service := http.NewService( + http.Logger(options.Logger), + http.Namespace(options.Namespace), + http.Name("web"), + http.Version(version.GetString()), + http.Address(options.Config.HTTP.Addr), + http.Context(options.Context), + http.Flags(options.Flags...), + ) + + handle := svc.NewService( + svc.Logger(options.Logger), + svc.Config(options.Config), + svc.Middleware( + chimiddleware.RealIP, + chimiddleware.RequestID, + middleware.NoCache, + middleware.Secure, + webmid.SilentRefresh, + middleware.Version( + "web", + version.GetString(), + ), + middleware.Logger( + options.Logger, + ), + ), + ) + + { + handle = svc.NewInstrument(handle, options.Metrics) + handle = svc.NewLogging(handle, options.Logger) + handle = svc.NewTracing(handle) + } + + if err := micro.RegisterHandler(service.Server(), handle); err != nil { + return http.Service{}, err + } + + return service, nil +} diff --git a/services/web/pkg/service/v0/instrument.go b/services/web/pkg/service/v0/instrument.go new file mode 100644 index 00000000000..2d89f069d6e --- /dev/null +++ b/services/web/pkg/service/v0/instrument.go @@ -0,0 +1,30 @@ +package svc + +import ( + "net/http" + + "github.com/owncloud/ocis/v2/services/web/pkg/metrics" +) + +// NewInstrument returns a service that instruments metrics. +func NewInstrument(next Service, metrics *metrics.Metrics) Service { + return instrument{ + next: next, + metrics: metrics, + } +} + +type instrument struct { + next Service + metrics *metrics.Metrics +} + +// ServeHTTP implements the Service interface. +func (i instrument) ServeHTTP(w http.ResponseWriter, r *http.Request) { + i.next.ServeHTTP(w, r) +} + +// Config implements the Service interface. +func (i instrument) Config(w http.ResponseWriter, r *http.Request) { + i.next.Config(w, r) +} diff --git a/extensions/web/pkg/service/v0/logging.go b/services/web/pkg/service/v0/logging.go similarity index 100% rename from extensions/web/pkg/service/v0/logging.go rename to services/web/pkg/service/v0/logging.go diff --git a/services/web/pkg/service/v0/option.go b/services/web/pkg/service/v0/option.go new file mode 100644 index 00000000000..a7f4cd0312b --- /dev/null +++ b/services/web/pkg/service/v0/option.go @@ -0,0 +1,50 @@ +package svc + +import ( + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/web/pkg/config" +) + +// 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 + Config *config.Config + Middleware []func(http.Handler) http.Handler +} + +// 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 + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} + +// Middleware provides a function to set the middleware option. +func Middleware(val ...func(http.Handler) http.Handler) Option { + return func(o *Options) { + o.Middleware = val + } +} diff --git a/services/web/pkg/service/v0/service.go b/services/web/pkg/service/v0/service.go new file mode 100644 index 00000000000..342c39e2546 --- /dev/null +++ b/services/web/pkg/service/v0/service.go @@ -0,0 +1,168 @@ +package svc + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "os" + "strconv" + "strings" + "time" + + "github.com/go-chi/chi/v5" + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/web/pkg/assets" + "github.com/owncloud/ocis/v2/services/web/pkg/config" +) + +var ( + // ErrConfigInvalid is returned when the config parse is invalid. + ErrConfigInvalid = `Invalid or missing config` +) + +// Service defines the extension handlers. +type Service interface { + ServeHTTP(http.ResponseWriter, *http.Request) + Config(http.ResponseWriter, *http.Request) +} + +// NewService returns a service implementation for Service. +func NewService(opts ...Option) Service { + options := newOptions(opts...) + + m := chi.NewMux() + m.Use(options.Middleware...) + + svc := Web{ + logger: options.Logger, + config: options.Config, + mux: m, + } + + m.Route(options.Config.HTTP.Root, func(r chi.Router) { + r.Get("/config.json", svc.Config) + r.Mount("/", svc.Static(options.Config.HTTP.CacheTTL)) + }) + + return svc +} + +// Web defines implements the business logic for Service. +type Web struct { + logger log.Logger + config *config.Config + mux *chi.Mux +} + +// ServeHTTP implements the Service interface. +func (p Web) ServeHTTP(w http.ResponseWriter, r *http.Request) { + p.mux.ServeHTTP(w, r) +} + +func (p Web) getPayload() (payload []byte, err error) { + + if p.config.Web.Path == "" { + // render dynamically using config + + // build theme url + if themeServer, err := url.Parse(p.config.Web.ThemeServer); err == nil { + p.config.Web.Config.Theme = themeServer.String() + p.config.Web.ThemePath + } else { + p.config.Web.Config.Theme = p.config.Web.ThemePath + } + + if p.config.Web.Config.ExternalApps == nil { + p.config.Web.Config.ExternalApps = []config.ExternalApp{ + { + ID: "settings", + Path: "/settings.js", + }, + } + } + + // make apps render as empty array if it is empty + // TODO remove once https://github.com/golang/go/issues/27589 is fixed + if len(p.config.Web.Config.Apps) == 0 { + p.config.Web.Config.Apps = make([]string, 0) + } + + return json.Marshal(p.config.Web.Config) + } + + // try loading from file + if _, err = os.Stat(p.config.Web.Path); os.IsNotExist(err) { + p.logger.Fatal(). + Err(err). + Str("config", p.config.Web.Path). + Msg("web config doesn't exist") + } + + payload, err = ioutil.ReadFile(p.config.Web.Path) + + if err != nil { + p.logger.Fatal(). + Err(err). + Str("config", p.config.Web.Path). + Msg("failed to read custom config") + } + return +} + +// Config implements the Service interface. +func (p Web) Config(w http.ResponseWriter, r *http.Request) { + + payload, err := p.getPayload() + if err != nil { + http.Error(w, ErrConfigInvalid, http.StatusUnprocessableEntity) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + if _, err := w.Write(payload); err != nil { + p.logger.Error().Err(err).Msg("could not write config response") + } +} + +// Static simply serves all static files. +func (p Web) Static(ttl int) http.HandlerFunc { + rootWithSlash := p.config.HTTP.Root + + if !strings.HasSuffix(rootWithSlash, "/") { + rootWithSlash = rootWithSlash + "/" + } + + static := http.StripPrefix( + rootWithSlash, + assets.FileServer( + assets.New( + assets.Logger(p.logger), + assets.Config(p.config), + ), + ), + ) + + lastModified := time.Now().UTC().Format(http.TimeFormat) + expires := time.Now().Add(time.Second * time.Duration(ttl)).UTC().Format(http.TimeFormat) + + return func(w http.ResponseWriter, r *http.Request) { + if rootWithSlash != "/" && r.URL.Path == p.config.HTTP.Root { + http.Redirect( + w, + r, + rootWithSlash, + http.StatusMovedPermanently, + ) + return + } + + w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%s, must-revalidate", strconv.Itoa(ttl))) + w.Header().Set("Expires", expires) + w.Header().Set("Last-Modified", lastModified) + w.Header().Set("SameSite", "Strict") + + static.ServeHTTP(w, r) + } +} diff --git a/extensions/web/pkg/service/v0/tracing.go b/services/web/pkg/service/v0/tracing.go similarity index 100% rename from extensions/web/pkg/service/v0/tracing.go rename to services/web/pkg/service/v0/tracing.go diff --git a/services/web/pkg/tracing/tracing.go b/services/web/pkg/tracing/tracing.go new file mode 100644 index 00000000000..378e1e0c36c --- /dev/null +++ b/services/web/pkg/tracing/tracing.go @@ -0,0 +1,23 @@ +package tracing + +import ( + pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/web/pkg/config" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the web service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config) error { + var err error + if cfg.Tracing.Enabled { + if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { + return err + } + } + + return nil +} diff --git a/extensions/web/reflex.conf b/services/web/reflex.conf similarity index 100% rename from extensions/web/reflex.conf rename to services/web/reflex.conf diff --git a/extensions/web/web.go b/services/web/web.go similarity index 100% rename from extensions/web/web.go rename to services/web/web.go diff --git a/extensions/webdav/.dockerignore b/services/webdav/.dockerignore similarity index 100% rename from extensions/webdav/.dockerignore rename to services/webdav/.dockerignore diff --git a/extensions/webdav/Makefile b/services/webdav/Makefile similarity index 100% rename from extensions/webdav/Makefile rename to services/webdav/Makefile diff --git a/services/webdav/cmd/webdav/main.go b/services/webdav/cmd/webdav/main.go new file mode 100644 index 00000000000..84449ec2e3e --- /dev/null +++ b/services/webdav/cmd/webdav/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/webdav/pkg/command" + "github.com/owncloud/ocis/v2/services/webdav/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/extensions/webdav/config/.gitignore b/services/webdav/config/.gitignore similarity index 100% rename from extensions/webdav/config/.gitignore rename to services/webdav/config/.gitignore diff --git a/extensions/webdav/docker/Dockerfile.linux.amd64 b/services/webdav/docker/Dockerfile.linux.amd64 similarity index 100% rename from extensions/webdav/docker/Dockerfile.linux.amd64 rename to services/webdav/docker/Dockerfile.linux.amd64 diff --git a/extensions/webdav/docker/Dockerfile.linux.arm b/services/webdav/docker/Dockerfile.linux.arm similarity index 100% rename from extensions/webdav/docker/Dockerfile.linux.arm rename to services/webdav/docker/Dockerfile.linux.arm diff --git a/extensions/webdav/docker/Dockerfile.linux.arm64 b/services/webdav/docker/Dockerfile.linux.arm64 similarity index 100% rename from extensions/webdav/docker/Dockerfile.linux.arm64 rename to services/webdav/docker/Dockerfile.linux.arm64 diff --git a/extensions/webdav/docker/manifest.tmpl b/services/webdav/docker/manifest.tmpl similarity index 100% rename from extensions/webdav/docker/manifest.tmpl rename to services/webdav/docker/manifest.tmpl diff --git a/services/webdav/pkg/command/health.go b/services/webdav/pkg/command/health.go new file mode 100644 index 00000000000..e0bc1d5d28d --- /dev/null +++ b/services/webdav/pkg/command/health.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/services/webdav/pkg/config" + "github.com/owncloud/ocis/v2/services/webdav/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/webdav/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/services/webdav/pkg/command/root.go b/services/webdav/pkg/command/root.go new file mode 100644 index 00000000000..c8f857ca2cb --- /dev/null +++ b/services/webdav/pkg/command/root.go @@ -0,0 +1,64 @@ +package command + +import ( + "context" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/webdav/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-webdav command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "webdav", + Usage: "Serve 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 webdav command to be embedded and supervised by a suture supervisor tree. +type SutureService struct { + cfg *config.Config +} + +// NewSutureService creates a new webdav.SutureService +func NewSutureService(cfg *ociscfg.Config) suture.Service { + cfg.WebDAV.Commons = cfg.Commons + return SutureService{ + cfg: cfg.WebDAV, + } +} + +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/services/webdav/pkg/command/server.go b/services/webdav/pkg/command/server.go new file mode 100644 index 00000000000..9b59d7d3247 --- /dev/null +++ b/services/webdav/pkg/command/server.go @@ -0,0 +1,107 @@ +package command + +import ( + "context" + "fmt" + "os" + + "github.com/oklog/run" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/webdav/pkg/config" + "github.com/owncloud/ocis/v2/services/webdav/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/webdav/pkg/logging" + "github.com/owncloud/ocis/v2/services/webdav/pkg/metrics" + "github.com/owncloud/ocis/v2/services/webdav/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/webdav/pkg/server/http" + "github.com/owncloud/ocis/v2/services/webdav/pkg/tracing" + "github.com/urfave/cli/v2" +) + +// Server is the entrypoint 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) + os.Exit(1) + } + return err + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg) + if err != nil { + return err + } + + var ( + gr = run.Group{} + ctx, cancel = func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() + metrics = metrics.New() + ) + + defer cancel() + + metrics.BuildInfo.WithLabelValues(version.GetString()).Set(1) + + { + server, err := http.Server( + http.Logger(logger), + http.Context(ctx), + http.Config(cfg), + http.Metrics(metrics), + ) + + if err != nil { + logger.Info(). + Err(err). + Str("transport", "http"). + Msg("Failed to initialize server") + + return err + } + + gr.Add(func() error { + return server.Run() + }, func(err error) { + logger.Error().Err(err).Msg("error ") + logger.Info(). + Str("transport", "http"). + Msg("Shutting down server") + + cancel() + }) + } + + { + server, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("transport", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(server.ListenAndServe, func(err error) { + logger.Error().Err(err) + _ = server.Shutdown(ctx) + cancel() + }) + } + + return gr.Run() + }, + } +} diff --git a/services/webdav/pkg/command/version.go b/services/webdav/pkg/command/version.go new file mode 100644 index 00000000000..fed75e7920e --- /dev/null +++ b/services/webdav/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/webdav/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.GetString()) + 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/webdav/pkg/config/config.go b/services/webdav/pkg/config/config.go similarity index 100% rename from extensions/webdav/pkg/config/config.go rename to services/webdav/pkg/config/config.go diff --git a/extensions/webdav/pkg/config/debug.go b/services/webdav/pkg/config/debug.go similarity index 100% rename from extensions/webdav/pkg/config/debug.go rename to services/webdav/pkg/config/debug.go diff --git a/services/webdav/pkg/config/defaults/defaultconfig.go b/services/webdav/pkg/config/defaults/defaultconfig.go new file mode 100644 index 00000000000..107f8e9b158 --- /dev/null +++ b/services/webdav/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,75 @@ +package defaults + +import ( + "strings" + + "github.com/owncloud/ocis/v2/services/webdav/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:9119", + Token: "", + Pprof: false, + Zpages: false, + }, + HTTP: config.HTTP{ + Addr: "127.0.0.1:9115", + Root: "/", + Namespace: "com.owncloud.web", + CORS: config.CORS{ + AllowedOrigins: []string{"*"}, + AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, + AllowedHeaders: []string{"Authorization", "Origin", "Content-Type", "Accept", "X-Requested-With"}, + AllowCredentials: true, + }, + }, + Service: config.Service{ + Name: "webdav", + }, + OcisPublicURL: "https://127.0.0.1:9200", + WebdavNamespace: "/users/{{.Id.OpaqueId}}", + RevaGateway: "127.0.0.1:9142", + } +} + +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{} + } +} + +func Sanitize(cfg *config.Config) { + // sanitize config + if cfg.HTTP.Root != "/" { + cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") + } + +} diff --git a/extensions/webdav/pkg/config/http.go b/services/webdav/pkg/config/http.go similarity index 100% rename from extensions/webdav/pkg/config/http.go rename to services/webdav/pkg/config/http.go diff --git a/extensions/webdav/pkg/config/log.go b/services/webdav/pkg/config/log.go similarity index 100% rename from extensions/webdav/pkg/config/log.go rename to services/webdav/pkg/config/log.go diff --git a/services/webdav/pkg/config/parser/parse.go b/services/webdav/pkg/config/parser/parse.go new file mode 100644 index 00000000000..f1f34ba4954 --- /dev/null +++ b/services/webdav/pkg/config/parser/parse.go @@ -0,0 +1,37 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/services/webdav/pkg/config" + "github.com/owncloud/ocis/v2/services/webdav/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + return nil +} diff --git a/extensions/webdav/pkg/config/service.go b/services/webdav/pkg/config/service.go similarity index 100% rename from extensions/webdav/pkg/config/service.go rename to services/webdav/pkg/config/service.go diff --git a/extensions/webdav/pkg/config/tracing.go b/services/webdav/pkg/config/tracing.go similarity index 100% rename from extensions/webdav/pkg/config/tracing.go rename to services/webdav/pkg/config/tracing.go diff --git a/extensions/webdav/pkg/constants/constants.go b/services/webdav/pkg/constants/constants.go similarity index 100% rename from extensions/webdav/pkg/constants/constants.go rename to services/webdav/pkg/constants/constants.go diff --git a/services/webdav/pkg/dav/requests/thumbnail.go b/services/webdav/pkg/dav/requests/thumbnail.go new file mode 100644 index 00000000000..4f8d5036c72 --- /dev/null +++ b/services/webdav/pkg/dav/requests/thumbnail.go @@ -0,0 +1,91 @@ +package requests + +import ( + "fmt" + "net/http" + "net/url" + "path/filepath" + "strconv" + + "github.com/go-chi/chi/v5" + + "github.com/owncloud/ocis/v2/services/webdav/pkg/constants" +) + +const ( + // DefaultWidth defines the default width of a thumbnail + DefaultWidth = 32 + // DefaultHeight defines the default height of a thumbnail + DefaultHeight = 32 +) + +// ThumbnailRequest combines all parameters provided when requesting a thumbnail +type ThumbnailRequest struct { + // The file path of the source file + Filepath string + // The file name of the source file including the extension + Filename string + // The file extension + Extension string + // The requested width of the thumbnail + Width int32 + // The requested height of the thumbnail + Height int32 + // In case of a public share the public link token. + PublicLinkToken string + // The Identifier from the requested URL + Identifier string +} + +// ParseThumbnailRequest extracts all required parameters from a http request. +func ParseThumbnailRequest(r *http.Request) (*ThumbnailRequest, error) { + ctx := r.Context() + + fp := ctx.Value(constants.ContextKeyPath).(string) + + id := "" + v := ctx.Value(constants.ContextKeyID) + if v != nil { + id = v.(string) + } + + q := r.URL.Query() + width, height, err := parseDimensions(q) + if err != nil { + return nil, err + } + + return &ThumbnailRequest{ + Filepath: fp, + Filename: filepath.Base(fp), + Extension: filepath.Ext(fp), + Width: int32(width), + Height: int32(height), + PublicLinkToken: chi.URLParam(r, "token"), + Identifier: id, + }, nil +} + +func parseDimensions(q url.Values) (int64, int64, error) { + width, err := parseDimension(q.Get("x"), "width", DefaultWidth) + if err != nil { + return 0, 0, err + } + height, err := parseDimension(q.Get("y"), "height", DefaultHeight) + if err != nil { + return 0, 0, err + } + return width, height, nil +} + +func parseDimension(d, name string, defaultValue int64) (int64, error) { + if d == "" { + return defaultValue, nil + } + result, err := strconv.ParseInt(d, 10, 32) + if err != nil || result < 1 { + // The error message doesn't fit but for OC10 API compatibility reasons we have to set this. + return 0, fmt.Errorf("Cannot set %s of 0 or smaller!", name) + } + return result, nil +} diff --git a/extensions/webdav/pkg/errors/error.go b/services/webdav/pkg/errors/error.go similarity index 100% rename from extensions/webdav/pkg/errors/error.go rename to services/webdav/pkg/errors/error.go diff --git a/services/webdav/pkg/logging/logging.go b/services/webdav/pkg/logging/logging.go new file mode 100644 index 00000000000..e7e5fed88ba --- /dev/null +++ b/services/webdav/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/webdav/pkg/config" +) + +// 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/webdav/pkg/metrics/metrics.go b/services/webdav/pkg/metrics/metrics.go similarity index 100% rename from extensions/webdav/pkg/metrics/metrics.go rename to services/webdav/pkg/metrics/metrics.go diff --git a/extensions/webdav/pkg/net/headers.go b/services/webdav/pkg/net/headers.go similarity index 100% rename from extensions/webdav/pkg/net/headers.go rename to services/webdav/pkg/net/headers.go diff --git a/extensions/webdav/pkg/net/net.go b/services/webdav/pkg/net/net.go similarity index 100% rename from extensions/webdav/pkg/net/net.go rename to services/webdav/pkg/net/net.go diff --git a/extensions/webdav/pkg/prop/prop.go b/services/webdav/pkg/prop/prop.go similarity index 100% rename from extensions/webdav/pkg/prop/prop.go rename to services/webdav/pkg/prop/prop.go diff --git a/extensions/webdav/pkg/propfind/propfind.go b/services/webdav/pkg/propfind/propfind.go similarity index 98% rename from extensions/webdav/pkg/propfind/propfind.go rename to services/webdav/pkg/propfind/propfind.go index a747b5860f8..3bc8e4f9c6b 100644 --- a/extensions/webdav/pkg/propfind/propfind.go +++ b/services/webdav/pkg/propfind/propfind.go @@ -6,8 +6,8 @@ import ( "io" "net/http" - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/errors" - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/prop" + "github.com/owncloud/ocis/v2/services/webdav/pkg/errors" + "github.com/owncloud/ocis/v2/services/webdav/pkg/prop" ) const ( diff --git a/services/webdav/pkg/server/debug/option.go b/services/webdav/pkg/server/debug/option.go new file mode 100644 index 00000000000..c0820ebf566 --- /dev/null +++ b/services/webdav/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/webdav/pkg/config" +) + +// 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/services/webdav/pkg/server/debug/server.go b/services/webdav/pkg/server/debug/server.go new file mode 100644 index 00000000000..e9b9d3c9066 --- /dev/null +++ b/services/webdav/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/webdav/pkg/config" +) + +// 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.GetString()), + 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/services/webdav/pkg/server/http/option.go b/services/webdav/pkg/server/http/option.go new file mode 100644 index 00000000000..e05f6aaf29b --- /dev/null +++ b/services/webdav/pkg/server/http/option.go @@ -0,0 +1,76 @@ +package http + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/webdav/pkg/config" + "github.com/owncloud/ocis/v2/services/webdav/pkg/metrics" + "github.com/urfave/cli/v2" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Namespace string + Logger log.Logger + Context context.Context + Config *config.Config + Metrics *metrics.Metrics + Flags []cli.Flag +} + +// 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 + } +} + +// Metrics provides a function to set the metrics option. +func Metrics(val *metrics.Metrics) Option { + return func(o *Options) { + o.Metrics = val + } +} + +// Flags provides a function to set the flags option. +func Flags(val []cli.Flag) Option { + return func(o *Options) { + o.Flags = append(o.Flags, val...) + } +} + +// Namespace provides a function to set the namespace option. +func Namespace(val string) Option { + return func(o *Options) { + o.Namespace = val + } +} diff --git a/services/webdav/pkg/server/http/server.go b/services/webdav/pkg/server/http/server.go new file mode 100644 index 00000000000..f5b782507fc --- /dev/null +++ b/services/webdav/pkg/server/http/server.go @@ -0,0 +1,66 @@ +package http + +import ( + chimiddleware "github.com/go-chi/chi/v5/middleware" + "github.com/owncloud/ocis/v2/ocis-pkg/cors" + "github.com/owncloud/ocis/v2/ocis-pkg/middleware" + "github.com/owncloud/ocis/v2/ocis-pkg/service/http" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + svc "github.com/owncloud/ocis/v2/services/webdav/pkg/service/v0" + "go-micro.dev/v4" +) + +// Server initializes the http service and server. +func Server(opts ...Option) (http.Service, error) { + options := newOptions(opts...) + + service := http.NewService( + http.Logger(options.Logger), + http.Namespace(options.Config.HTTP.Namespace), + http.Name(options.Config.Service.Name), + http.Version(version.GetString()), + http.Address(options.Config.HTTP.Addr), + http.Context(options.Context), + http.Flags(options.Flags...), + ) + + handle, err := svc.NewService( + svc.Logger(options.Logger), + svc.Config(options.Config), + svc.Middleware( + chimiddleware.RealIP, + chimiddleware.RequestID, + middleware.NoCache, + middleware.Cors( + cors.Logger(options.Logger), + cors.AllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins), + cors.AllowedMethods(options.Config.HTTP.CORS.AllowedMethods), + cors.AllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders), + cors.AllowCredentials(options.Config.HTTP.CORS.AllowCredentials), + ), + middleware.Secure, + middleware.Version( + options.Config.Service.Name, + version.GetString(), + ), + middleware.Logger( + options.Logger, + ), + ), + ) + if err != nil { + return http.Service{}, err + } + + { + handle = svc.NewInstrument(handle, options.Metrics) + handle = svc.NewLogging(handle, options.Logger) + handle = svc.NewTracing(handle) + } + + if err := micro.RegisterHandler(service.Server(), handle); err != nil { + return http.Service{}, err + } + + return service, nil +} diff --git a/services/webdav/pkg/service/v0/instrument.go b/services/webdav/pkg/service/v0/instrument.go new file mode 100644 index 00000000000..3ede8164b46 --- /dev/null +++ b/services/webdav/pkg/service/v0/instrument.go @@ -0,0 +1,30 @@ +package svc + +import ( + "net/http" + + "github.com/owncloud/ocis/v2/services/webdav/pkg/metrics" +) + +// NewInstrument returns a service that instruments metrics. +func NewInstrument(next Service, metrics *metrics.Metrics) Service { + return instrument{ + next: next, + metrics: metrics, + } +} + +type instrument struct { + next Service + metrics *metrics.Metrics +} + +// ServeHTTP implements the Service interface. +func (i instrument) ServeHTTP(w http.ResponseWriter, r *http.Request) { + i.next.ServeHTTP(w, r) +} + +// Thumbnail implements the Service interface. +func (i instrument) Thumbnail(w http.ResponseWriter, r *http.Request) { + i.next.Thumbnail(w, r) +} diff --git a/extensions/webdav/pkg/service/v0/logging.go b/services/webdav/pkg/service/v0/logging.go similarity index 100% rename from extensions/webdav/pkg/service/v0/logging.go rename to services/webdav/pkg/service/v0/logging.go diff --git a/services/webdav/pkg/service/v0/option.go b/services/webdav/pkg/service/v0/option.go new file mode 100644 index 00000000000..a2c5dc641d9 --- /dev/null +++ b/services/webdav/pkg/service/v0/option.go @@ -0,0 +1,50 @@ +package svc + +import ( + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/webdav/pkg/config" +) + +// 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 + Config *config.Config + Middleware []func(http.Handler) http.Handler +} + +// 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 + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} + +// Middleware provides a function to set the middleware option. +func Middleware(val ...func(http.Handler) http.Handler) Option { + return func(o *Options) { + o.Middleware = val + } +} diff --git a/extensions/webdav/pkg/service/v0/search.go b/services/webdav/pkg/service/v0/search.go similarity index 97% rename from extensions/webdav/pkg/service/v0/search.go rename to services/webdav/pkg/service/v0/search.go index 75ff164c553..7ba77db16f4 100644 --- a/extensions/webdav/pkg/service/v0/search.go +++ b/services/webdav/pkg/service/v0/search.go @@ -11,11 +11,11 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" revactx "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/net" - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/prop" - "github.com/owncloud/ocis/v2/extensions/webdav/pkg/propfind" searchmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/search/v0" searchsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/search/v0" + "github.com/owncloud/ocis/v2/services/webdav/pkg/net" + "github.com/owncloud/ocis/v2/services/webdav/pkg/prop" + "github.com/owncloud/ocis/v2/services/webdav/pkg/propfind" merrors "go-micro.dev/v4/errors" "go-micro.dev/v4/metadata" ) diff --git a/services/webdav/pkg/service/v0/service.go b/services/webdav/pkg/service/v0/service.go new file mode 100644 index 00000000000..89f60fe5837 --- /dev/null +++ b/services/webdav/pkg/service/v0/service.go @@ -0,0 +1,493 @@ +package svc + +import ( + "context" + "encoding/xml" + "io" + "net/http" + "net/url" + "path" + "path/filepath" + "strings" + + gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/v2/pkg/storage/utils/templates" + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" + merrors "go-micro.dev/v4/errors" + "google.golang.org/grpc/metadata" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" + thumbnailsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/thumbnails/v0" + searchsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/search/v0" + thumbnailssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/thumbnails/v0" + "github.com/owncloud/ocis/v2/services/webdav/pkg/config" + "github.com/owncloud/ocis/v2/services/webdav/pkg/constants" + "github.com/owncloud/ocis/v2/services/webdav/pkg/dav/requests" +) + +const ( + TokenHeader = "X-Access-Token" +) + +var ( + codesEnum = map[int]string{ + http.StatusBadRequest: "Sabre\\DAV\\Exception\\BadRequest", + http.StatusUnauthorized: "Sabre\\DAV\\Exception\\NotAuthenticated", + http.StatusNotFound: "Sabre\\DAV\\Exception\\NotFound", + http.StatusMethodNotAllowed: "Sabre\\DAV\\Exception\\MethodNotAllowed", + } +) + +// Service defines the extension handlers. +type Service interface { + ServeHTTP(http.ResponseWriter, *http.Request) + Thumbnail(http.ResponseWriter, *http.Request) +} + +// NewService returns a service implementation for Service. +func NewService(opts ...Option) (Service, error) { + options := newOptions(opts...) + conf := options.Config + + m := chi.NewMux() + // 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) + if err != nil { + return nil, err + } + + svc := Webdav{ + config: conf, + log: options.Logger, + mux: m, + searchClient: searchsvc.NewSearchProviderService("com.owncloud.api.search", grpc.DefaultClient), + thumbnailsClient: thumbnailssvc.NewThumbnailService("com.owncloud.api.thumbnails", grpc.DefaultClient), + revaClient: gwc, + } + + m.Route(options.Config.HTTP.Root, func(r chi.Router) { + + r.Group(func(r chi.Router) { + r.Use(svc.DavUserContext()) + + r.Get("/remote.php/dav/spaces/{id}", svc.SpacesThumbnail) + r.Get("/remote.php/dav/spaces/{id}/*", svc.SpacesThumbnail) + r.Get("/dav/spaces/{id}", svc.SpacesThumbnail) + r.Get("/dav/spaces/{id}/*", svc.SpacesThumbnail) + + r.Get("/remote.php/dav/files/{id}", svc.Thumbnail) + r.Get("/remote.php/dav/files/{id}/*", svc.Thumbnail) + r.Get("/dav/files/{id}", svc.Thumbnail) + r.Get("/dav/files/{id}/*", svc.Thumbnail) + }) + + r.Group(func(r chi.Router) { + r.Use(svc.DavPublicContext()) + + r.Head("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnailHead) + r.Head("/dav/public-files/{token}/*", svc.PublicThumbnailHead) + + r.Get("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnail) + r.Get("/dav/public-files/{token}/*", svc.PublicThumbnail) + }) + + r.Group(func(r chi.Router) { + r.Use(svc.WebDAVContext()) + r.Get("/remote.php/webdav/*", svc.Thumbnail) + r.Get("/webdav/*", svc.Thumbnail) + }) + + // 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 +} + +// Webdav implements the business logic for Service. +type Webdav struct { + config *config.Config + log log.Logger + mux *chi.Mux + searchClient searchsvc.SearchProviderService + thumbnailsClient thumbnailssvc.ThumbnailService + revaClient gatewayv1beta1.GatewayAPIClient +} + +// ServeHTTP implements the Service interface. +func (g Webdav) ServeHTTP(w http.ResponseWriter, r *http.Request) { + g.mux.ServeHTTP(w, r) +} + +func (g Webdav) DavUserContext() func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + filePath := r.URL.Path + + id := chi.URLParam(r, "id") + id, err := url.QueryUnescape(id) + if err == nil && id != "" { + ctx = context.WithValue(ctx, constants.ContextKeyID, id) + } + + if id != "" { + filePath = strings.TrimPrefix(filePath, path.Join("/remote.php/dav/spaces", id)) + filePath = strings.TrimPrefix(filePath, path.Join("/dav/spaces", id)) + + filePath = strings.TrimPrefix(filePath, path.Join("/remote.php/dav/files", id)) + filePath = strings.TrimPrefix(filePath, path.Join("/dav/files", id)) + filePath = strings.TrimPrefix(filePath, "/") + } + + ctx = context.WithValue(ctx, constants.ContextKeyPath, filePath) + + next.ServeHTTP(w, r.WithContext(ctx)) + + }) + } +} + +func (g Webdav) DavPublicContext() func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + filePath := r.URL.Path + + if token := chi.URLParam(r, "token"); token != "" { + filePath = strings.TrimPrefix(filePath, path.Join("/remote.php/dav/public-files", token)+"/") + filePath = strings.TrimPrefix(filePath, path.Join("/dav/public-files", token)+"/") + } + ctx = context.WithValue(ctx, constants.ContextKeyPath, filePath) + + next.ServeHTTP(w, r.WithContext(ctx)) + + }) + } +} +func (g Webdav) WebDAVContext() func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + filePath := r.URL.Path + filePath = strings.TrimPrefix(filePath, "/remote.php") + filePath = strings.TrimPrefix(filePath, "/webdav/") + + ctx := context.WithValue(r.Context(), constants.ContextKeyPath, filePath) + + next.ServeHTTP(w, r.WithContext(ctx)) + + }) + } +} + +// SpacesThumbnail is the endpoint for retrieving thumbnails inside of spaces. +func (g Webdav) SpacesThumbnail(w http.ResponseWriter, r *http.Request) { + tr, err := requests.ParseThumbnailRequest(r) + if err != nil { + g.log.Error().Err(err).Msg("could not create Request") + renderError(w, r, errBadRequest(err.Error())) + return + } + t := r.Header.Get(TokenHeader) + + fullPath := filepath.Join(tr.Identifier, tr.Filepath) + rsp, err := g.thumbnailsClient.GetThumbnail(r.Context(), &thumbnailssvc.GetThumbnailRequest{ + Filepath: strings.TrimLeft(tr.Filepath, "/"), + ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")), + Width: tr.Width, + Height: tr.Height, + Source: &thumbnailssvc.GetThumbnailRequest_Cs3Source{ + Cs3Source: &thumbnailsmsg.CS3Source{ + Path: fullPath, + Authorization: t, + }, + }, + }) + if err != nil { + e := merrors.Parse(err.Error()) + switch e.Code { + case http.StatusNotFound: + // StatusNotFound is expected for unsupported files + renderError(w, r, errNotFound(notFoundMsg(tr.Filename))) + return + case http.StatusBadRequest: + renderError(w, r, errBadRequest(err.Error())) + default: + renderError(w, r, errInternalError(err.Error())) + } + g.log.Error().Err(err).Msg("could not get thumbnail") + return + } + + g.sendThumbnailResponse(rsp, w, r) +} + +// Thumbnail implements the Service interface. +func (g Webdav) Thumbnail(w http.ResponseWriter, r *http.Request) { + tr, err := requests.ParseThumbnailRequest(r) + if err != nil { + g.log.Error().Err(err).Msg("could not create Request") + renderError(w, r, errBadRequest(err.Error())) + return + } + + t := r.Header.Get(TokenHeader) + + var user *userv1beta1.User + + if tr.Identifier == "" { + // look up user from token via WhoAmI + userRes, err := g.revaClient.WhoAmI(r.Context(), &gatewayv1beta1.WhoAmIRequest{ + Token: t, + }) + if err != nil || userRes.Status.Code != rpcv1beta1.Code_CODE_OK { + g.log.Error().Err(err).Msg("could not get user") + renderError(w, r, errInternalError("could not get user")) + return + } + user = userRes.GetUser() + } else { + // look up user from URL via GetUserByClaim + ctx := metadata.AppendToOutgoingContext(r.Context(), TokenHeader, t) + userRes, err := g.revaClient.GetUserByClaim(ctx, &userv1beta1.GetUserByClaimRequest{ + Claim: "username", + Value: tr.Identifier, + }) + if err != nil || userRes.Status.Code != rpcv1beta1.Code_CODE_OK { + g.log.Error().Err(err).Msg("could not get user") + renderError(w, r, errInternalError("could not get user")) + return + } + user = userRes.GetUser() + } + + fullPath := filepath.Join(templates.WithUser(user, g.config.WebdavNamespace), tr.Filepath) + rsp, err := g.thumbnailsClient.GetThumbnail(r.Context(), &thumbnailssvc.GetThumbnailRequest{ + Filepath: strings.TrimLeft(tr.Filepath, "/"), + ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")), + Width: tr.Width, + Height: tr.Height, + Source: &thumbnailssvc.GetThumbnailRequest_Cs3Source{ + Cs3Source: &thumbnailsmsg.CS3Source{ + Path: fullPath, + Authorization: t, + }, + }, + }) + if err != nil { + e := merrors.Parse(err.Error()) + switch e.Code { + case http.StatusNotFound: + // StatusNotFound is expected for unsupported files + renderError(w, r, errNotFound(notFoundMsg(tr.Filename))) + return + case http.StatusBadRequest: + renderError(w, r, errBadRequest(err.Error())) + default: + renderError(w, r, errInternalError(err.Error())) + } + g.log.Error().Err(err).Msg("could not get thumbnail") + return + } + + g.sendThumbnailResponse(rsp, w, r) +} + +func (g Webdav) PublicThumbnail(w http.ResponseWriter, r *http.Request) { + tr, err := requests.ParseThumbnailRequest(r) + if err != nil { + g.log.Error().Err(err).Msg("could not create Request") + renderError(w, r, errBadRequest(err.Error())) + return + } + + rsp, err := g.thumbnailsClient.GetThumbnail(r.Context(), &thumbnailssvc.GetThumbnailRequest{ + Filepath: strings.TrimLeft(tr.Filepath, "/"), + ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")), + Width: tr.Width, + Height: tr.Height, + Source: &thumbnailssvc.GetThumbnailRequest_WebdavSource{ + WebdavSource: &thumbnailsmsg.WebdavSource{ + Url: g.config.OcisPublicURL + r.URL.RequestURI(), + IsPublicLink: true, + PublicLinkToken: tr.PublicLinkToken, + }, + }, + }) + if err != nil { + e := merrors.Parse(err.Error()) + switch e.Code { + case http.StatusNotFound: + // StatusNotFound is expected for unsupported files + renderError(w, r, errNotFound(notFoundMsg(tr.Filename))) + return + case http.StatusBadRequest: + renderError(w, r, errBadRequest(err.Error())) + default: + renderError(w, r, errInternalError(err.Error())) + } + g.log.Error().Err(err).Msg("could not get thumbnail") + return + } + + g.sendThumbnailResponse(rsp, w, r) +} + +func (g Webdav) PublicThumbnailHead(w http.ResponseWriter, r *http.Request) { + tr, err := requests.ParseThumbnailRequest(r) + if err != nil { + g.log.Error().Err(err).Msg("could not create Request") + renderError(w, r, errBadRequest(err.Error())) + return + } + + _, err = g.thumbnailsClient.GetThumbnail(r.Context(), &thumbnailssvc.GetThumbnailRequest{ + Filepath: strings.TrimLeft(tr.Filepath, "/"), + ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")), + Width: tr.Width, + Height: tr.Height, + Source: &thumbnailssvc.GetThumbnailRequest_WebdavSource{ + WebdavSource: &thumbnailsmsg.WebdavSource{ + Url: g.config.OcisPublicURL + r.URL.RequestURI(), + IsPublicLink: true, + PublicLinkToken: tr.PublicLinkToken, + }, + }, + }) + if err != nil { + e := merrors.Parse(err.Error()) + switch e.Code { + case http.StatusNotFound: + // StatusNotFound is expected for unsupported files + renderError(w, r, errNotFound(notFoundMsg(tr.Filename))) + return + case http.StatusBadRequest: + renderError(w, r, errBadRequest(err.Error())) + default: + renderError(w, r, errInternalError(err.Error())) + } + g.log.Error().Err(err).Msg("could not get thumbnail") + return + } + + w.WriteHeader(http.StatusOK) +} + +func (g Webdav) sendThumbnailResponse(rsp *thumbnailssvc.GetThumbnailResponse, w http.ResponseWriter, r *http.Request) { + client := &http.Client{ + // Timeout: time.Second * 5, + } + + dlReq, err := http.NewRequest(http.MethodGet, rsp.DataEndpoint, http.NoBody) + if err != nil { + renderError(w, r, errInternalError(err.Error())) + g.log.Error().Err(err).Msg("could not download thumbnail") + return + } + dlReq.Header.Set("Transfer-Token", rsp.TransferToken) + + dlRsp, err := client.Do(dlReq) + if err != nil { + renderError(w, r, errInternalError(err.Error())) + g.log.Error().Err(err).Msg("could not download thumbnail") + return + } + defer dlRsp.Body.Close() + + if dlRsp.StatusCode != http.StatusOK { + g.log.Error(). + Str("transfer_token", rsp.TransferToken). + Str("data_endpoint", rsp.DataEndpoint). + Str("response_status", dlRsp.Status). + Msg("could not download thumbnail") + renderError(w, r, errInternalError("could not download thumbnail")) + return + } + + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", rsp.Mimetype) + _, err = io.Copy(w, dlRsp.Body) + if err != nil { + g.log.Error().Err(err).Msg("failed to write thumbnail to response writer") + } +} + +func extensionToThumbnailType(ext string) thumbnailsmsg.ThumbnailType { + switch strings.ToUpper(ext) { + case "GIF": + return thumbnailsmsg.ThumbnailType_GIF + case "PNG": + return thumbnailsmsg.ThumbnailType_PNG + default: + return thumbnailsmsg.ThumbnailType_JPG + } +} + +// http://www.webdav.org/specs/rfc4918.html#ELEMENT_error +type errResponse struct { + HTTPStatusCode int `json:"-" xml:"-"` + XMLName xml.Name `xml:"d:error"` + Xmlnsd string `xml:"xmlns:d,attr"` + Xmlnss string `xml:"xmlns:s,attr"` + Exception string `xml:"s:exception"` + Message string `xml:"s:message"` + InnerXML []byte `xml:",innerxml"` +} + +func newErrResponse(statusCode int, msg string) *errResponse { + rsp := &errResponse{ + HTTPStatusCode: statusCode, + Xmlnsd: "DAV", + Xmlnss: "http://sabredav.org/ns", + Exception: codesEnum[statusCode], + } + if msg != "" { + rsp.Message = msg + } + return rsp +} + +func errInternalError(msg string) *errResponse { + return newErrResponse(http.StatusInternalServerError, msg) +} + +func errBadRequest(msg string) *errResponse { + return newErrResponse(http.StatusBadRequest, msg) +} + +func errNotFound(msg string) *errResponse { + return newErrResponse(http.StatusNotFound, msg) +} + +func renderError(w http.ResponseWriter, r *http.Request, err *errResponse) { + render.Status(r, err.HTTPStatusCode) + render.XML(w, r, err) +} + +func notFoundMsg(name string) string { + return "File with name " + name + " could not be located" +} diff --git a/extensions/webdav/pkg/service/v0/tracing.go b/services/webdav/pkg/service/v0/tracing.go similarity index 100% rename from extensions/webdav/pkg/service/v0/tracing.go rename to services/webdav/pkg/service/v0/tracing.go diff --git a/services/webdav/pkg/tracing/tracing.go b/services/webdav/pkg/tracing/tracing.go new file mode 100644 index 00000000000..cbcadf44edc --- /dev/null +++ b/services/webdav/pkg/tracing/tracing.go @@ -0,0 +1,23 @@ +package tracing + +import ( + pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/webdav/pkg/config" + "go.opentelemetry.io/otel/trace" +) + +var ( + // TraceProvider is the global trace provider for the proxy service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config) error { + var err error + if cfg.Tracing.Enabled { + if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { + return err + } + } + + return nil +} diff --git a/extensions/webdav/reflex.conf b/services/webdav/reflex.conf similarity index 100% rename from extensions/webdav/reflex.conf rename to services/webdav/reflex.conf