From 31f3ed5970bdcea17b8f2f3650717cf07276153a Mon Sep 17 00:00:00 2001 From: MichalKalke Date: Fri, 19 Apr 2024 11:44:36 +0200 Subject: [PATCH] Extract docker-registry source code from serverless module. --- .github/actions/setup-libgit2/action.yaml | 28 + .github/dependabot.yml | 38 ++ .github/scripts/create_changelog.sh | 56 ++ .github/scripts/create_draft_release.sh | 37 ++ .github/scripts/publish_release.sh | 24 + .github/scripts/release.sh | 66 ++ .../scripts/upgrade-sec-scanners-config.sh | 22 + .github/scripts/verify-actions-status.sh | 26 + .../verify-docker-registry-jobs-status.sh | 34 + .github/scripts/verify-image-changes.sh | 20 + .github/stale.yml | 19 + .github/workflows/create-release.yaml | 118 ++++ .github/workflows/images-verify.yaml | 44 ++ .github/workflows/lint-markdown-links.yml | 14 - .github/workflows/markdown.yaml | 18 + .github/workflows/operator-verify.yaml | 82 +++ .github/workflows/pull-gitleaks.yml | 23 - .github/workflows/stale.yml | 31 - .gitignore | 44 ++ .markdownlint.yaml | 26 + .markdownlintignore | 1 + .mlc.config.json | 24 +- .reuse/dep5 | 26 +- CODEOWNERS | 42 +- CODE_OF_CONDUCT.md | 4 +- CONTRIBUTING.md | 4 +- Makefile | 31 + README.md | 124 ++-- SECURITY.md | 3 - components/operator/.dockerignore | 10 + components/operator/.golangci.yaml | 15 + components/operator/Dockerfile | 34 + components/operator/Makefile | 142 +++++ .../api/v1alpha1/dockerregistry_types.go | 162 +++++ .../api/v1alpha1/groupversion_info.go | 41 ++ components/operator/api/v1alpha1/helpers.go | 26 + .../api/v1alpha1/zz_generated.deepcopy.go | 137 ++++ components/operator/controllers/controller.go | 80 +++ .../operator/controllers/controller_rbac.go | 35 + .../operator/controllers/controller_test.go | 99 +++ components/operator/controllers/suite_test.go | 120 ++++ .../operator/controllers/testhelper_test.go | 285 +++++++++ components/operator/hack/boilerplate.go.txt | 15 + .../hack/verify_dockerregistry_status.sh | 18 + .../internal/annotation/disclaimer.go | 22 + .../internal/annotation/disclaimer_test.go | 17 + components/operator/internal/chart/cache.go | 142 +++++ .../operator/internal/chart/cache_test.go | 262 ++++++++ components/operator/internal/chart/chart.go | 143 +++++ .../operator/internal/chart/chart_test.go | 94 +++ components/operator/internal/chart/check.go | 94 +++ .../operator/internal/chart/check_test.go | 179 ++++++ .../operator/internal/chart/client_getter.go | 44 ++ components/operator/internal/chart/flags.go | 94 +++ .../operator/internal/chart/flags_test.go | 85 +++ components/operator/internal/chart/install.go | 102 +++ .../operator/internal/chart/install_test.go | 247 +++++++ components/operator/internal/chart/pvc.go | 64 ++ .../operator/internal/chart/pvc_test.go | 133 ++++ .../operator/internal/chart/uninstall.go | 160 +++++ .../operator/internal/chart/uninstall_test.go | 99 +++ components/operator/internal/chart/verify.go | 72 +++ .../operator/internal/chart/verify_test.go | 146 +++++ components/operator/internal/config/config.go | 14 + .../kubernetes/configmap_service.go | 98 +++ .../kubernetes/namespace_controller.go | 116 ++++ .../kubernetes/secret_controller.go | 103 +++ .../controllers/kubernetes/secret_service.go | 175 +++++ .../kubernetes/serviceaccount_service.go | 130 ++++ .../internal/controllers/kubernetes/shared.go | 55 ++ .../internal/gitrepository/cleanup.go | 31 + .../internal/gitrepository/cleanup_test.go | 61 ++ .../operator/internal/predicate/predicate.go | 33 + .../internal/predicate/predicate_test.go | 98 +++ .../operator/internal/registry/node_port.go | 158 +++++ .../internal/registry/node_port_test.go | 144 +++++ .../operator/internal/registry/secret.go | 61 ++ .../operator/internal/resource/resource.go | 107 ++++ .../operator/internal/state/add_finalizer.go | 32 + .../internal/state/add_finalizer_test.go | 84 +++ components/operator/internal/state/apply.go | 37 ++ .../operator/internal/state/apply_test.go | 104 +++ .../state/controller_configuration.go | 45 ++ .../state/controller_configuration_test.go | 110 ++++ components/operator/internal/state/delete.go | 99 +++ .../operator/internal/state/delete_test.go | 121 ++++ .../operator/internal/state/emit_event.go | 41 ++ .../internal/state/emit_event_test.go | 94 +++ components/operator/internal/state/fsm.go | 143 +++++ .../operator/internal/state/fsm_test.go | 145 +++++ .../operator/internal/state/initialize.go | 18 + .../internal/state/initialize_test.go | 67 ++ components/operator/internal/state/new.go | 36 ++ .../operator/internal/state/registry.go | 66 ++ .../operator/internal/state/registry_test.go | 39 ++ .../internal/state/remove_finalizer.go | 17 + .../internal/state/remove_finalizer_test.go | 68 ++ .../operator/internal/state/served_filter.go | 50 ++ .../internal/state/served_filter_test.go | 136 ++++ components/operator/internal/state/state.go | 65 ++ .../operator/internal/state/state_test.go | 110 ++++ .../operator/internal/state/update_status.go | 25 + components/operator/internal/state/utils.go | 46 ++ components/operator/internal/state/verify.go | 49 ++ .../operator/internal/state/verify_test.go | 178 ++++++ .../operator/internal/tracing/watcher.go | 57 ++ .../operator/internal/warning/warning.go | 27 + .../operator/internal/warning/warning_test.go | 22 + components/operator/main.go | 218 +++++++ config.yaml | 7 + config/docker-registry/.helmignore | 22 + config/docker-registry/Chart.yaml | 8 + config/docker-registry/README.md | 15 + .../charts/docker-registry/.helmignore | 21 + .../charts/docker-registry/Chart.yaml | 14 + .../charts/docker-registry/README.md | 79 +++ .../docker-registry/templates/_helpers.tpl | 24 + .../docker-registry/templates/configmap.yaml | 13 + .../docker-registry/templates/deployment.yaml | 231 +++++++ .../docker-registry/templates/ingress.yaml | 37 ++ .../templates/poddisruptionbudget.yaml | 18 + .../templates/priorityclass.yaml | 7 + .../charts/docker-registry/templates/pvc.yaml | 27 + .../docker-registry/templates/secret.yaml | 31 + .../docker-registry/templates/service.yaml | 25 + .../charts/docker-registry/values.yaml | 171 +++++ config/docker-registry/templates/_helpers.tpl | 49 ++ .../templates/registry-config.yaml | 21 + config/docker-registry/values.yaml | 47 ++ ...ator.kyma-project.io_dockerregistries.yaml | 165 +++++ config/operator/base/crd/kustomization.yaml | 10 + config/operator/base/crd/kustomizeconfig.yaml | 19 + .../operator/base/deployment/deployment.yaml | 64 ++ .../base/deployment/kustomization.yaml | 8 + config/operator/base/kustomization.yaml | 20 + config/operator/base/rbac/editor_role.yaml | 31 + config/operator/base/rbac/kustomization.yaml | 10 + config/operator/base/rbac/role.yaml | 288 +++++++++ config/operator/base/rbac/role_binding.yaml | 19 + .../operator/base/rbac/service_account.yaml | 12 + config/operator/base/rbac/viewer_role.yaml | 27 + .../base/ui-extensions/dockerregistry/details | 91 +++ .../base/ui-extensions/dockerregistry/form | 25 + .../base/ui-extensions/dockerregistry/general | 15 + .../dockerregistry/kustomization.yaml | 14 + .../base/ui-extensions/dockerregistry/list | 6 + .../base/ui-extensions/kustomization.yaml | 2 + config/operator/dev/.gitignore | 1 + config/operator/dev/kustomization.yaml.tpl | 9 + config/samples/default-dockerregistry-cr.yaml | 6 + docs/README.md | 39 -- docs/assets/modular-serverless.svg | 4 + docs/assets/svls-add-ssh-key.png | Bin 0 -> 59049 bytes docs/assets/svls-api-rules.svg | 4 + docs/assets/svls-architecture.svg | 4 + docs/assets/svls-built.svg | 4 + docs/assets/svls-configured.svg | 4 + docs/assets/svls-create-ssh-key.png | Bin 0 -> 39233 bytes docs/assets/svls-function-ui.png | Bin 0 -> 336968 bytes docs/assets/svls-internal-registry.svg | 4 + docs/assets/svls-kyma-cli-functions.png | Bin 0 -> 197634 bytes docs/assets/svls-running.svg | 4 + docs/assets/svls-settings.png | Bin 0 -> 18152 bytes docs/contributor/03-10-scripts-not-working.md | 19 + docs/contributor/04-10-testing-strategy.md | 23 + docs/contributor/README.md | 1 - docs/user/00-10-from-code-to-function.md | 17 + docs/user/00-20-configure-serverless.md | 222 +++++++ docs/user/00-30-development-toolkit.md | 13 + docs/user/00-40-security-considerations.md | 20 + docs/user/00-50-limitations.md | 86 +++ docs/user/08-10-best-practices.md | 62 ++ docs/user/README.md | 49 +- docs/user/_sidebar.md | 42 +- docs/user/resources/06-10-function-cr.md | 210 ++++++ docs/user/resources/06-20-serverless-cr.md | 130 ++++ docs/user/resources/README.md | 3 + .../technical-reference/04-10-architecture.md | 28 + .../04-20-internal-registry.md | 22 + .../05-20-env-variables.md | 128 ++++ .../07-10-sample-functions.md | 64 ++ .../07-20-function-processing-stages.md | 50 ++ .../07-40-git-source-type.md | 31 + .../07-60-function-configuration-file.md | 178 ++++++ .../07-70-function-specification.md | 234 +++++++ .../07-80-available-presets.md | 35 + docs/user/technical-reference/README.md | 3 + .../03-10-cannot-build-functions.md | 67 ++ .../03-20-failing-function-container.md | 16 + .../03-40-function-build-failing-k3d.md | 52 ++ ...03-50-serverless-periodically-restaring.md | 13 + docs/user/troubleshooting-guides/README.md | 5 + .../tutorials/01-10-create-inline-function.md | 147 +++++ .../01-100-customize-function-traces.md | 100 +++ .../tutorials/01-11-create-git-function.md | 148 +++++ .../01-110-override-runtime-image.md | 88 +++ docs/user/tutorials/01-120-inject-envs.md | 147 +++++ .../tutorials/01-130-use-external-scalers.md | 195 ++++++ .../tutorials/01-140-use-secret-mounts.md | 123 ++++ docs/user/tutorials/01-20-expose-function.md | 163 +++++ .../01-30-manage-functions-with-kyma-cli.md | 111 ++++ docs/user/tutorials/01-40-debug-function.md | 83 +++ .../01-50-sync-function-with-gitops.md | 222 +++++++ .../tutorials/01-60-set-external-registry.md | 244 +++++++ ...1-80-log-into-private-packages-registry.md | 118 ++++ .../01-90-set-asynchronous-connection.md | 146 +++++ docs/user/tutorials/README.md | 3 + go.mod | 164 +++++ go.sum | 602 ++++++++++++++++++ hack/Makefile | 7 + hack/boilerplate.go.txt | 15 + hack/gardener.mk | 27 + hack/get_kyma_file_name.sh | 16 + hack/help.mk | 5 + hack/k3d.mk | 26 + hack/makefile-strategy.md | 40 ++ hack/tools.mk | 77 +++ markdown_heading_capitalization.js | 31 + module-config-template.yaml | 8 + sec-scanners-config.yaml | 11 + 220 files changed, 14617 insertions(+), 229 deletions(-) create mode 100644 .github/actions/setup-libgit2/action.yaml create mode 100644 .github/dependabot.yml create mode 100755 .github/scripts/create_changelog.sh create mode 100755 .github/scripts/create_draft_release.sh create mode 100755 .github/scripts/publish_release.sh create mode 100755 .github/scripts/release.sh create mode 100755 .github/scripts/upgrade-sec-scanners-config.sh create mode 100755 .github/scripts/verify-actions-status.sh create mode 100755 .github/scripts/verify-docker-registry-jobs-status.sh create mode 100755 .github/scripts/verify-image-changes.sh create mode 100644 .github/stale.yml create mode 100644 .github/workflows/create-release.yaml create mode 100644 .github/workflows/images-verify.yaml delete mode 100644 .github/workflows/lint-markdown-links.yml create mode 100644 .github/workflows/markdown.yaml create mode 100644 .github/workflows/operator-verify.yaml delete mode 100644 .github/workflows/pull-gitleaks.yml delete mode 100644 .github/workflows/stale.yml create mode 100644 .gitignore create mode 100644 .markdownlint.yaml create mode 100644 .markdownlintignore mode change 100755 => 100644 .reuse/dep5 create mode 100644 Makefile delete mode 100644 SECURITY.md create mode 100644 components/operator/.dockerignore create mode 100644 components/operator/.golangci.yaml create mode 100644 components/operator/Dockerfile create mode 100644 components/operator/Makefile create mode 100644 components/operator/api/v1alpha1/dockerregistry_types.go create mode 100644 components/operator/api/v1alpha1/groupversion_info.go create mode 100644 components/operator/api/v1alpha1/helpers.go create mode 100644 components/operator/api/v1alpha1/zz_generated.deepcopy.go create mode 100644 components/operator/controllers/controller.go create mode 100644 components/operator/controllers/controller_rbac.go create mode 100644 components/operator/controllers/controller_test.go create mode 100644 components/operator/controllers/suite_test.go create mode 100644 components/operator/controllers/testhelper_test.go create mode 100755 components/operator/hack/boilerplate.go.txt create mode 100755 components/operator/hack/verify_dockerregistry_status.sh create mode 100644 components/operator/internal/annotation/disclaimer.go create mode 100644 components/operator/internal/annotation/disclaimer_test.go create mode 100644 components/operator/internal/chart/cache.go create mode 100644 components/operator/internal/chart/cache_test.go create mode 100644 components/operator/internal/chart/chart.go create mode 100644 components/operator/internal/chart/chart_test.go create mode 100644 components/operator/internal/chart/check.go create mode 100644 components/operator/internal/chart/check_test.go create mode 100644 components/operator/internal/chart/client_getter.go create mode 100644 components/operator/internal/chart/flags.go create mode 100644 components/operator/internal/chart/flags_test.go create mode 100644 components/operator/internal/chart/install.go create mode 100644 components/operator/internal/chart/install_test.go create mode 100644 components/operator/internal/chart/pvc.go create mode 100644 components/operator/internal/chart/pvc_test.go create mode 100644 components/operator/internal/chart/uninstall.go create mode 100644 components/operator/internal/chart/uninstall_test.go create mode 100644 components/operator/internal/chart/verify.go create mode 100644 components/operator/internal/chart/verify_test.go create mode 100644 components/operator/internal/config/config.go create mode 100644 components/operator/internal/controllers/kubernetes/configmap_service.go create mode 100644 components/operator/internal/controllers/kubernetes/namespace_controller.go create mode 100644 components/operator/internal/controllers/kubernetes/secret_controller.go create mode 100644 components/operator/internal/controllers/kubernetes/secret_service.go create mode 100644 components/operator/internal/controllers/kubernetes/serviceaccount_service.go create mode 100644 components/operator/internal/controllers/kubernetes/shared.go create mode 100644 components/operator/internal/gitrepository/cleanup.go create mode 100644 components/operator/internal/gitrepository/cleanup_test.go create mode 100644 components/operator/internal/predicate/predicate.go create mode 100644 components/operator/internal/predicate/predicate_test.go create mode 100644 components/operator/internal/registry/node_port.go create mode 100644 components/operator/internal/registry/node_port_test.go create mode 100644 components/operator/internal/registry/secret.go create mode 100644 components/operator/internal/resource/resource.go create mode 100644 components/operator/internal/state/add_finalizer.go create mode 100644 components/operator/internal/state/add_finalizer_test.go create mode 100644 components/operator/internal/state/apply.go create mode 100644 components/operator/internal/state/apply_test.go create mode 100644 components/operator/internal/state/controller_configuration.go create mode 100644 components/operator/internal/state/controller_configuration_test.go create mode 100644 components/operator/internal/state/delete.go create mode 100644 components/operator/internal/state/delete_test.go create mode 100644 components/operator/internal/state/emit_event.go create mode 100644 components/operator/internal/state/emit_event_test.go create mode 100644 components/operator/internal/state/fsm.go create mode 100644 components/operator/internal/state/fsm_test.go create mode 100644 components/operator/internal/state/initialize.go create mode 100644 components/operator/internal/state/initialize_test.go create mode 100644 components/operator/internal/state/new.go create mode 100644 components/operator/internal/state/registry.go create mode 100644 components/operator/internal/state/registry_test.go create mode 100644 components/operator/internal/state/remove_finalizer.go create mode 100644 components/operator/internal/state/remove_finalizer_test.go create mode 100644 components/operator/internal/state/served_filter.go create mode 100644 components/operator/internal/state/served_filter_test.go create mode 100644 components/operator/internal/state/state.go create mode 100644 components/operator/internal/state/state_test.go create mode 100644 components/operator/internal/state/update_status.go create mode 100644 components/operator/internal/state/utils.go create mode 100644 components/operator/internal/state/verify.go create mode 100644 components/operator/internal/state/verify_test.go create mode 100644 components/operator/internal/tracing/watcher.go create mode 100644 components/operator/internal/warning/warning.go create mode 100644 components/operator/internal/warning/warning_test.go create mode 100644 components/operator/main.go create mode 100644 config.yaml create mode 100644 config/docker-registry/.helmignore create mode 100644 config/docker-registry/Chart.yaml create mode 100644 config/docker-registry/README.md create mode 100644 config/docker-registry/charts/docker-registry/.helmignore create mode 100644 config/docker-registry/charts/docker-registry/Chart.yaml create mode 100644 config/docker-registry/charts/docker-registry/README.md create mode 100644 config/docker-registry/charts/docker-registry/templates/_helpers.tpl create mode 100644 config/docker-registry/charts/docker-registry/templates/configmap.yaml create mode 100644 config/docker-registry/charts/docker-registry/templates/deployment.yaml create mode 100644 config/docker-registry/charts/docker-registry/templates/ingress.yaml create mode 100644 config/docker-registry/charts/docker-registry/templates/poddisruptionbudget.yaml create mode 100644 config/docker-registry/charts/docker-registry/templates/priorityclass.yaml create mode 100644 config/docker-registry/charts/docker-registry/templates/pvc.yaml create mode 100644 config/docker-registry/charts/docker-registry/templates/secret.yaml create mode 100644 config/docker-registry/charts/docker-registry/templates/service.yaml create mode 100644 config/docker-registry/charts/docker-registry/values.yaml create mode 100644 config/docker-registry/templates/_helpers.tpl create mode 100644 config/docker-registry/templates/registry-config.yaml create mode 100644 config/docker-registry/values.yaml create mode 100644 config/operator/base/crd/bases/operator.kyma-project.io_dockerregistries.yaml create mode 100644 config/operator/base/crd/kustomization.yaml create mode 100644 config/operator/base/crd/kustomizeconfig.yaml create mode 100644 config/operator/base/deployment/deployment.yaml create mode 100644 config/operator/base/deployment/kustomization.yaml create mode 100644 config/operator/base/kustomization.yaml create mode 100644 config/operator/base/rbac/editor_role.yaml create mode 100644 config/operator/base/rbac/kustomization.yaml create mode 100644 config/operator/base/rbac/role.yaml create mode 100644 config/operator/base/rbac/role_binding.yaml create mode 100644 config/operator/base/rbac/service_account.yaml create mode 100644 config/operator/base/rbac/viewer_role.yaml create mode 100644 config/operator/base/ui-extensions/dockerregistry/details create mode 100644 config/operator/base/ui-extensions/dockerregistry/form create mode 100644 config/operator/base/ui-extensions/dockerregistry/general create mode 100644 config/operator/base/ui-extensions/dockerregistry/kustomization.yaml create mode 100644 config/operator/base/ui-extensions/dockerregistry/list create mode 100644 config/operator/base/ui-extensions/kustomization.yaml create mode 100644 config/operator/dev/.gitignore create mode 100644 config/operator/dev/kustomization.yaml.tpl create mode 100644 config/samples/default-dockerregistry-cr.yaml delete mode 100644 docs/README.md create mode 100644 docs/assets/modular-serverless.svg create mode 100644 docs/assets/svls-add-ssh-key.png create mode 100644 docs/assets/svls-api-rules.svg create mode 100644 docs/assets/svls-architecture.svg create mode 100644 docs/assets/svls-built.svg create mode 100644 docs/assets/svls-configured.svg create mode 100644 docs/assets/svls-create-ssh-key.png create mode 100644 docs/assets/svls-function-ui.png create mode 100644 docs/assets/svls-internal-registry.svg create mode 100644 docs/assets/svls-kyma-cli-functions.png create mode 100644 docs/assets/svls-running.svg create mode 100644 docs/assets/svls-settings.png create mode 100644 docs/contributor/03-10-scripts-not-working.md create mode 100644 docs/contributor/04-10-testing-strategy.md delete mode 100644 docs/contributor/README.md create mode 100644 docs/user/00-10-from-code-to-function.md create mode 100644 docs/user/00-20-configure-serverless.md create mode 100644 docs/user/00-30-development-toolkit.md create mode 100644 docs/user/00-40-security-considerations.md create mode 100644 docs/user/00-50-limitations.md create mode 100644 docs/user/08-10-best-practices.md create mode 100644 docs/user/resources/06-10-function-cr.md create mode 100644 docs/user/resources/06-20-serverless-cr.md create mode 100644 docs/user/resources/README.md create mode 100644 docs/user/technical-reference/04-10-architecture.md create mode 100644 docs/user/technical-reference/04-20-internal-registry.md create mode 100644 docs/user/technical-reference/05-20-env-variables.md create mode 100644 docs/user/technical-reference/07-10-sample-functions.md create mode 100644 docs/user/technical-reference/07-20-function-processing-stages.md create mode 100644 docs/user/technical-reference/07-40-git-source-type.md create mode 100644 docs/user/technical-reference/07-60-function-configuration-file.md create mode 100644 docs/user/technical-reference/07-70-function-specification.md create mode 100644 docs/user/technical-reference/07-80-available-presets.md create mode 100644 docs/user/technical-reference/README.md create mode 100644 docs/user/troubleshooting-guides/03-10-cannot-build-functions.md create mode 100644 docs/user/troubleshooting-guides/03-20-failing-function-container.md create mode 100644 docs/user/troubleshooting-guides/03-40-function-build-failing-k3d.md create mode 100644 docs/user/troubleshooting-guides/03-50-serverless-periodically-restaring.md create mode 100644 docs/user/troubleshooting-guides/README.md create mode 100644 docs/user/tutorials/01-10-create-inline-function.md create mode 100644 docs/user/tutorials/01-100-customize-function-traces.md create mode 100644 docs/user/tutorials/01-11-create-git-function.md create mode 100644 docs/user/tutorials/01-110-override-runtime-image.md create mode 100644 docs/user/tutorials/01-120-inject-envs.md create mode 100644 docs/user/tutorials/01-130-use-external-scalers.md create mode 100644 docs/user/tutorials/01-140-use-secret-mounts.md create mode 100644 docs/user/tutorials/01-20-expose-function.md create mode 100644 docs/user/tutorials/01-30-manage-functions-with-kyma-cli.md create mode 100644 docs/user/tutorials/01-40-debug-function.md create mode 100644 docs/user/tutorials/01-50-sync-function-with-gitops.md create mode 100644 docs/user/tutorials/01-60-set-external-registry.md create mode 100644 docs/user/tutorials/01-80-log-into-private-packages-registry.md create mode 100644 docs/user/tutorials/01-90-set-asynchronous-connection.md create mode 100644 docs/user/tutorials/README.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 hack/Makefile create mode 100755 hack/boilerplate.go.txt create mode 100644 hack/gardener.mk create mode 100755 hack/get_kyma_file_name.sh create mode 100644 hack/help.mk create mode 100644 hack/k3d.mk create mode 100644 hack/makefile-strategy.md create mode 100644 hack/tools.mk create mode 100644 markdown_heading_capitalization.js create mode 100644 module-config-template.yaml create mode 100644 sec-scanners-config.yaml diff --git a/.github/actions/setup-libgit2/action.yaml b/.github/actions/setup-libgit2/action.yaml new file mode 100644 index 00000000..7028c797 --- /dev/null +++ b/.github/actions/setup-libgit2/action.yaml @@ -0,0 +1,28 @@ +name: 'Setup libgit2' +description: 'Action for the libgit2 setup' + +inputs: + version: + description: 'libgit2 version to checkout' + required: true + default: 'v1.5.2' + +runs: + using: 'composite' + steps: + - name: Install libssh2 + run: | + sudo apt update + sudo apt install libssh2-1-dev -y + shell: bash + + - name: Install libgit2 + run: | + git clone https://github.com/libgit2/libgit2.git + cd libgit2 + git checkout ${{ inputs.version }} + cmake . -DBUILD_TESTS=OFF -DBUILD_CLI=OFF -DUSE_SSH=ON + sudo make install + sudo ldconfig + shell: bash + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..aa1e19da --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,38 @@ +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "gomod" + directory: "/" + labels: + - "area/dependency" + - "kind/chore" + schedule: + interval: "weekly" + commit-message: + prefix: "gomod" + include: "scope" + ignore: + # ignore minor k8s updates, e.g. 1.27.x -> 1.28.x + - dependency-name: "k8s.io/*" + update-types: ["version-update:semver-minor"] + - dependency-name: "sigs.k8s.io/*" + update-types: ["version-update:semver-minor"] + - dependency-name: "helm.sh/helm/v3" + update-types: ["version-update:semver-minor"] + groups: + k8s-io: + patterns: + - "k8s.io/*" + + - package-ecosystem: "docker" + directory: "/components/operator" + labels: + - "area/dependency" + - "kind/chore" + schedule: + interval: "weekly" + commit-message: + prefix: "operator" + include: "scope" + diff --git a/.github/scripts/create_changelog.sh b/.github/scripts/create_changelog.sh new file mode 100755 index 00000000..ace47064 --- /dev/null +++ b/.github/scripts/create_changelog.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +PREVIOUS_RELEASE=$2 # for testability + +# standard bash error handling +set -o nounset # treat unset variables as an error and exit immediately. +set -o errexit # exit immediately when a command fails. +set -E # needs to be set if we want the ERR trap +set -o pipefail # prevents errors in a pipeline from being masked + +RELEASE_TAG=$1 + +REPOSITORY=${REPOSITORY:-kyma-project/docker-registry} +GITHUB_URL=https://api.github.com/repos/${REPOSITORY} +GITHUB_AUTH_HEADER="Authorization: token ${GITHUB_TOKEN}" +CHANGELOG_FILE="CHANGELOG.md" + +if [ "${PREVIOUS_RELEASE}" == "" ] +then + PREVIOUS_RELEASE=$(git describe --tags --abbrev=0) +fi + +echo "## What has changed" >> ${CHANGELOG_FILE} + +git log ${PREVIOUS_RELEASE}..HEAD --pretty=tformat:"%h" --reverse | while read -r commit +do + COMMIT_AUTHOR=$(curl -H "${GITHUB_AUTH_HEADER}" -sS "${GITHUB_URL}/commits/${commit}" | jq -r '.author.login') + if [ "${COMMIT_AUTHOR}" != "kyma-bot" ]; then + git show -s ${commit} --format="* %s by @${COMMIT_AUTHOR}" >> ${CHANGELOG_FILE} + fi +done + +NEW_CONTRIB=$$.new + +join -v2 \ +<(curl -H "${GITHUB_AUTH_HEADER}" -sS "${GITHUB_URL}/compare/$(git rev-list --max-parents=0 HEAD)...${PREVIOUS_RELEASE}" | jq -r '.commits[].author.login' | sort -u) \ +<(curl -H "${GITHUB_AUTH_HEADER}" -sS "${GITHUB_URL}/compare/${PREVIOUS_RELEASE}...HEAD" | jq -r '.commits[].author.login' | sort -u) >${NEW_CONTRIB} + +if [ -s ${NEW_CONTRIB} ] +then + echo -e "\n## New contributors" >> ${CHANGELOG_FILE} + while read -r user + do + REF_PR=$(grep "@${user}" ${CHANGELOG_FILE} | head -1 | grep -o " (#[0-9]\+)" || true) + if [ -n "${REF_PR}" ] #reference found + then + REF_PR=" in ${REF_PR}" + fi + echo "* @${user} made first contribution${REF_PR}" >> ${CHANGELOG_FILE} + done <${NEW_CONTRIB} +fi + +echo -e "\n**Full changelog**: https://github.com/$REPOSITORY/compare/${PREVIOUS_RELEASE}...${RELEASE_TAG}" >> ${CHANGELOG_FILE} + +# cleanup +rm ${NEW_CONTRIB} || echo "cleaned up" \ No newline at end of file diff --git a/.github/scripts/create_draft_release.sh b/.github/scripts/create_draft_release.sh new file mode 100755 index 00000000..75957632 --- /dev/null +++ b/.github/scripts/create_draft_release.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +# This script returns the id of the draft release + +# standard bash error handling +set -o nounset # treat unset variables as an error and exit immediately. +set -o errexit # exit immediately when a command fails. +set -E # needs to be set if we want the ERR trap +set -o pipefail # prevents errors in a pipeline from being masked + +RELEASE_TAG=$1 + +REPOSITORY=${REPOSITORY:-kyma-project/docker-registry} +GITHUB_URL=https://api.github.com/repos/${REPOSITORY} +GITHUB_AUTH_HEADER="Authorization: Bearer ${GITHUB_TOKEN}" +CHANGELOG_FILE=$(cat CHANGELOG.md) + +JSON_PAYLOAD=$(jq -n \ + --arg tag_name "$RELEASE_TAG" \ + --arg name "$RELEASE_TAG" \ + --arg body "$CHANGELOG_FILE" \ + '{ + "tag_name": $tag_name, + "name": $name, + "body": $body, + "draft": true + }') + +CURL_RESPONSE=$(curl -L \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "${GITHUB_AUTH_HEADER}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + ${GITHUB_URL}/releases \ + -d "$JSON_PAYLOAD") + +echo "$(echo $CURL_RESPONSE | jq -r ".id")" diff --git a/.github/scripts/publish_release.sh b/.github/scripts/publish_release.sh new file mode 100755 index 00000000..e56797ab --- /dev/null +++ b/.github/scripts/publish_release.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +# This script publishes a draft release + +# standard bash error handling +set -o nounset # treat unset variables as an error and exit immediately. +set -o errexit # exit immediately when a command fails. +set -E # needs to be set if we want the ERR trap +set -o pipefail # prevents errors in a pipeline from being masked + +RELEASE_ID=$1 +IS_LATEST_RELEASE=$2 + +REPOSITORY=${REPOSITORY:-kyma-project/docker-registry} +GITHUB_URL=https://api.github.com/repos/${REPOSITORY} +GITHUB_AUTH_HEADER="Authorization: Bearer ${GITHUB_TOKEN}" + +CURL_RESPONSE=$(curl -L \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "${GITHUB_AUTH_HEADER}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + ${GITHUB_URL}/releases/${RELEASE_ID} \ + -d '{"draft": false, "make_latest": '"$IS_LATEST_RELEASE"'}') diff --git a/.github/scripts/release.sh b/.github/scripts/release.sh new file mode 100755 index 00000000..cc583cb5 --- /dev/null +++ b/.github/scripts/release.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash + +# standard bash error handling +set -o nounset # treat unset variables as an error and exit immediately. +set -o errexit # exit immediately when a command fails. +set -E # needs to be set if we want the ERR trap +set -o pipefail # prevents errors in a pipeline from being masked + +# Expected variables: +IMG=${IMG?"Define IMG env"} # operator image +PULL_BASE_REF=${PULL_BASE_REF?"Define PULL_BASE_REF env"} # name of the tag +GITHUB_TOKEN=${GITHUB_TOKEN?"Define GITHUB_TOKEN env"} # github token used to upload the template yaml + +uploadFile() { + filePath=${1} + ghAsset=${2} + + echo "Uploading ${filePath} as ${ghAsset}" + response=$(curl -s -o output.txt -w "%{http_code}" \ + --request POST --data-binary @"$filePath" \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Content-Type: text/yaml" \ + $ghAsset) + if [[ "$response" != "201" ]]; then + echo "Unable to upload the asset ($filePath): " + echo "HTTP Status: $response" + cat output.txt + exit 1 + else + echo "$filePath uploaded" + fi +} + +echo "IMG: ${IMG}" +IMG=${IMG} make -C components/operator/ render-manifest + +echo "Generated dockerregistry-operator.yaml:" +cat dockerregistry-operator.yaml + +echo "Fetching releases" +CURL_RESPONSE=$(curl -w "%{http_code}" -sL \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $GITHUB_TOKEN"\ + https://api.github.com/repos/kyma-project/docker-registry/releases) +JSON_RESPONSE=$(sed '$ d' <<< "${CURL_RESPONSE}") +HTTP_CODE=$(tail -n1 <<< "${CURL_RESPONSE}") +if [[ "${HTTP_CODE}" != "200" ]]; then + echo "${CURL_RESPONSE}" + exit 1 +fi + +echo "Finding release id for: ${PULL_BASE_REF}" +RELEASE_ID=$(jq <<< ${JSON_RESPONSE} --arg tag "${PULL_BASE_REF}" '.[] | select(.tag_name == $ARGS.named.tag) | .id') + +echo "Got '${RELEASE_ID}' release id" +if [ -z "${RELEASE_ID}" ] +then + echo "No release with tag = ${PULL_BASE_REF}" + exit 1 +fi + +echo "Updating github release with assets" +UPLOAD_URL="https://uploads.github.com/repos/kyma-project/docker-registry/releases/${RELEASE_ID}/assets" + +uploadFile "dockerregistry-operator.yaml" "${UPLOAD_URL}?name=dockerregistry-operator.yaml" +uploadFile "config/samples/default-dockerregistry-cr.yaml" "${UPLOAD_URL}?name=default-dockerregistry-cr.yaml" diff --git a/.github/scripts/upgrade-sec-scanners-config.sh b/.github/scripts/upgrade-sec-scanners-config.sh new file mode 100755 index 00000000..a9b2fdec --- /dev/null +++ b/.github/scripts/upgrade-sec-scanners-config.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +IMG_VERSION=${IMG_VERSION?"Define IMG_VERSION env"} + +yq eval-all --inplace ' + select(fileIndex == 0).protecode=[ + select(fileIndex == 1) + | .global.containerRegistry.path as $registryPath + | ( + { + "dockerregistry_operator" : { + "name" : "dockerregistry-operator", + "directory" : "prod", + "version" : env(IMG_VERSION) + } + } + + .global.images + )[] + | $registryPath + "/" + .directory + "/" + .name + ":" + .version + ] + | select(fileIndex == 0) + ' sec-scanners-config.yaml config/docker-registry/values.yaml \ No newline at end of file diff --git a/.github/scripts/verify-actions-status.sh b/.github/scripts/verify-actions-status.sh new file mode 100755 index 00000000..598000d0 --- /dev/null +++ b/.github/scripts/verify-actions-status.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +echo "Checking status of github actions for docker-registry" + +REF_NAME="${1:-"main"}" +RAW_EXPECTED_SHA=$(git log "${REF_NAME}" --max-count 1 --format=format:%H) +REPOSITORY_ID="563346860" + +STATUS_URL="https://api.github.com/repositories/${REPOSITORY_ID}/actions/workflows/gardener-integration.yaml/runs?head_sha=${RAW_EXPECTED_SHA}" +GET_STATUS_JQ_QUERY=".workflow_runs[0] | \"\(.status)-\(.conclusion)\"" +GET_COUNT_JQ_QUERY=".total_count" + +response=`curl -s ${STATUS_URL}` + +count=`echo $response | jq -r "${GET_COUNT_JQ_QUERY}"` +if [[ "$count" == "0" ]]; then + echo "No actions to verify" +else + fullstatus=`echo $response | jq -r "${GET_STATUS_JQ_QUERY}"` + if [[ "$fullstatus" == "completed-success" ]]; then + echo "All actions succeeded" + else + echo "Actions failed or pending - Check github actions status" + exit 1 + fi +fi diff --git a/.github/scripts/verify-docker-registry-jobs-status.sh b/.github/scripts/verify-docker-registry-jobs-status.sh new file mode 100755 index 00000000..5adf1528 --- /dev/null +++ b/.github/scripts/verify-docker-registry-jobs-status.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +### Verify post-submit prow jobs status +# +# Optional input args: +# - REF_NAME - branch/tag/commit +# Return status: +# - return 0 - if status is "success" +# - return 1 - if status is "failure" or after timeout (~25min) + +# wait until Prow trigger pipelines +sleep 10 + +echo "Checking status of POST Jobs for docker-registry" + +REF_NAME="${1:-"main"}" +STATUS_URL="https://api.github.com/repos/kyma-project/docker-registry/commits/${REF_NAME}/status" + +function verify_github_jobs_status () { + local number=1 + while [[ $number -le 100 ]] ; do + echo ">--> checking docker-registry job status #$number" + local STATUS=`curl -L -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" ${STATUS_URL} | jq -r .state ` + echo "jobs status: ${STATUS:='UNKNOWN'}" + [[ "$STATUS" == "success" ]] && return 0 + [[ "$STATUS" == "failure" ]] && return 1 + sleep 15 + ((number = number + 1)) + done + + exit 1 +} + +verify_github_jobs_status \ No newline at end of file diff --git a/.github/scripts/verify-image-changes.sh b/.github/scripts/verify-image-changes.sh new file mode 100755 index 00000000..8e03bdff --- /dev/null +++ b/.github/scripts/verify-image-changes.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +MAIN_IMAGES=(${MAIN_IMAGES?"Define MAIN_IMAGES env"}) +PR_NOT_MAIN_IMAGES=(${PR_NOT_MAIN_IMAGES?"Define PR_NOT_MAIN_IMAGES env"}) + +FAIL=false +for main_image in "${MAIN_IMAGES[@]}"; do + echo "${main_image} checking..." + + for pr_image in "${PR_NOT_MAIN_IMAGES[@]}"; do + if [ "${main_image}" == "${pr_image}" ]; then + echo " warning: ${pr_image} tag/version seems to be modified (should be main)!" + FAIL=true + fi + done +done + +if $FAIL; then + exit 1 +fi diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 00000000..b70d9bad --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,19 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 60 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 +# Label to use when marking an issue as stale +staleLabel: lifecycle/stale +# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable +exemptLabels: + - lifecycle/frozen + - lifecycle/active +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: | + This issue has been automatically marked as stale due to the lack of recent activity. It will soon be closed if no further activity occurs. + Thank you for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: | + This issue has been automatically closed due to the lack of recent activity. + /lifecycle rotten + diff --git a/.github/workflows/create-release.yaml b/.github/workflows/create-release.yaml new file mode 100644 index 00000000..b9650864 --- /dev/null +++ b/.github/workflows/create-release.yaml @@ -0,0 +1,118 @@ +name: "Create release" + +on: + workflow_dispatch: + inputs: + name: + description: 'Release name ( e.g. "2.1.3" )' + default: "" + required: true + latest_release: + description: 'Latest release' + type: boolean + default: false + +jobs: + verify-head-status: + name: Verify HEAD + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Verify prow post jobs + run: ./.github/scripts/verify-docker-registry-jobs-status.sh ${{ github.ref_name }} + + - name: Verify github actions + run: ./.github/scripts/verify-actions-status.sh ${{ github.ref_name }} + + upgrade-images: + name: Upgrade main images + needs: verify-head-status + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + token: ${{ secrets.BOT_TOKEN }} + fetch-depth: 0 + + - name: Bump sec-scanners-config.yaml based on values.yaml + run: ./.github/scripts/upgrade-sec-scanners-config.sh + env: + IMG_VERSION: ${{ github.event.inputs.name }} + + - name: Commit&Push + run: | + git config --local user.email "team-otters@sap.com" + git config --local user.name "ottersbot" + + git add . + git commit --allow-empty -m "upgrade dependencies" + git push origin ${{ github.ref_name }} + + create-draft: + name: Create draft release + needs: upgrade-images + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.ref_name }} # checkout to latest branch changes ( by default this action checkouts to the SHA that triggers action ) + + - name: Create changelog + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PULL_BASE_REF: ${{ github.event.inputs.name }} + run: ./.github/scripts/create_changelog.sh ${{ github.event.inputs.name }} + + - name: Create draft release + id: create-draft + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + RELEASE_ID=$(./.github/scripts/create_draft_release.sh ${{ github.event.inputs.name }}) + echo "release_id=$RELEASE_ID" >> $GITHUB_OUTPUT + + - name: Create lightweight tag + run: | + git tag ${{ github.event.inputs.name }} + git push origin ${{ github.event.inputs.name }} + + - name: Create release assets + id: create-assets + env: + IMG: "europe-docker.pkg.dev/kyma-project/prod/dockerregistry-operator:${{ github.event.inputs.name }}" + PULL_BASE_REF: ${{ github.event.inputs.name }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: ./.github/scripts/release.sh + + - name: Verify prow release jobs + run: ./.github/scripts/verify-docker-registry-jobs-status.sh ${{ github.ref_name }} + + outputs: + release_id: ${{ steps.create-draft.outputs.release_id }} + + publish-release: + name: Publish release + needs: create-draft + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.event.inputs.name }} # checkout to latest branch changes ( by default this action checkouts to the SHA that triggers action ) + + - name: Publish release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: ./.github/scripts/publish_release.sh ${{ needs.create-draft.outputs.release_id }} ${{ github.event.inputs.latest_release }} diff --git a/.github/workflows/images-verify.yaml b/.github/workflows/images-verify.yaml new file mode 100644 index 00000000..5bd1a746 --- /dev/null +++ b/.github/workflows/images-verify.yaml @@ -0,0 +1,44 @@ +name: Images verify +run-name: ${{github.event.pull_request.title}} +on: + pull_request: + branches: + - main + paths: + - sec-scanners-config.yaml + - config/docker-registry/values.yaml + +jobs: + # check if developer doesn't change `main` images in the values.yaml and sec-scanners-config.yaml files + check-main-tags: + runs-on: ubuntu-latest + steps: + - name: Checkout to main + uses: actions/checkout@v4 + with: + ref: main + + - name: Export main images from the main ref + run: | + # export sec-scanners-config.yaml images with the main tag as github env + echo SSC_MAIN_IMAGES=$(yq '.protecode[] | select(contains(":main")) | sub(":.*", "")' sec-scanners-config.yaml) >> $GITHUB_ENV + + # export values. images with the main tag as github env + echo VALUES_MAIN_IMAGES=$(yq '.global.images[] | select(.version == "main") | .name' config/docker-registry/values.yaml) >> $GITHUB_ENV + + - name: Checkout to context + uses: actions/checkout@v4 + + - name: Verify sec-scanners-config.yaml images + run: | + PR_NOT_MAIN_IMAGES=$(yq '.protecode[] | select(contains(":main") | not ) | sub(":.*", "")' sec-scanners-config.yaml) \ + .github/scripts/verify-image-changes.sh + env: + MAIN_IMAGES: ${{ env.SSC_MAIN_IMAGES }} + + - name: Verify values.yaml images + run: | + PR_NOT_MAIN_IMAGES=$(yq '.global.images[] | select(.version != "main") | .name' config/docker-registry/values.yaml) \ + .github/scripts/verify-image-changes.sh + env: + MAIN_IMAGES: ${{ env.VALUES_MAIN_IMAGES }} diff --git a/.github/workflows/lint-markdown-links.yml b/.github/workflows/lint-markdown-links.yml deleted file mode 100644 index d1790224..00000000 --- a/.github/workflows/lint-markdown-links.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Lint Markdown Links -run-name: ${{github.event.pull_request.title}} -on: [ pull_request ] -jobs: - markdown-link-check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: gaurav-nelson/github-action-markdown-link-check@v1 - with: - use-verbose-mode: 'no' - config-file: '.mlc.config.json' - folder-path: '.' - max-depth: -1 diff --git a/.github/workflows/markdown.yaml b/.github/workflows/markdown.yaml new file mode 100644 index 00000000..a871742d --- /dev/null +++ b/.github/workflows/markdown.yaml @@ -0,0 +1,18 @@ +name: Markdown +run-name: ${{github.event.pull_request.title}} +on: + schedule: + - cron: "0 5 * * *" # Run everyday at 5:00 AM + pull_request: +jobs: + link-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 +# - uses: gaurav-nelson/github-action-markdown-link-check@v1 //TODO: after adjusting *.md bring the test back +# with: +# use-quiet-mode: 'yes' +# use-verbose-mode: 'yes' +# config-file: '.mlc.config.json' +# folder-path: '.' +# max-depth: -1 diff --git a/.github/workflows/operator-verify.yaml b/.github/workflows/operator-verify.yaml new file mode 100644 index 00000000..f2a14b10 --- /dev/null +++ b/.github/workflows/operator-verify.yaml @@ -0,0 +1,82 @@ +name: Operator verify + +on: + push: + branches: [ "main", "release-*" ] + pull_request: + paths-ignore: + - 'docs/**' + - 'examples/**' + types: + - opened + - reopened + - synchronize + - ready_for_review + - converted_to_draft + +jobs: + lint: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: golangci/golangci-lint-action@v3 + with: + version: latest + working-directory: 'components/operator' + + unit-test: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: run test + run: make -C components/operator test + + +# upgrade-test: //TODO: change it to run dockerregistry verify (need to be implemented) +# runs-on: ubuntu-latest +# if: github.event_name == 'push' +# steps: +# - uses: actions/checkout@v4 +# - name: create single cluster +# uses: AbsaOSS/k3d-action@4e8b3239042be1dc0aed6c5eb80c13b18200fc79 #v2.4.0 +# with: +# cluster-name: "k3dCluster" +# args: >- +# --agents 1 +# --image rancher/k3s:v1.28.6-k3s1 +# --port 80:80@loadbalancer +# --port 443:443@loadbalancer +# --wait +# - name: upgrade test +# run: make -C hack upgrade-test +# env: +# IMG: europe-docker.pkg.dev/kyma-project/prod/dockerregistry-operator:${{ github.sha }} + +# gardener-integration-test: //TODO: change it to run dockerregistry verify (need to be implemented) +# if: github.event_name == 'push' +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v4 +# - name: save sa +# shell: bash +# run: 'echo "$GARDENER_SA" > /tmp/gardener-sa.yaml' +# env: +# GARDENER_SA: ${{ secrets.GARDENER_SA }} +# - name: provision gardener +# run: make -C hack provision-gardener +# env: +# GARDENER_SECRET_NAME: ${{ secrets.GARDENER_SECRET_NAME }} +# GARDENER_PROJECT: ${{ secrets.GARDENER_PROJECT }} +# GARDENER_SA_PATH: /tmp/gardener-sa.yaml +# - name: run test +# run: make -C hack integration-test-on-cluster +# env: +# IMG: europe-docker.pkg.dev/kyma-project/prod/dockerregistry-operator:${{ github.sha }} +# - name: deprovision gardener +## https://docs.github.com/en/actions/learn-github-actions/expressions#always +# if: ${{ always() }} +# run: make -C hack deprovision-gardener +# env: +# GARDENER_SA_PATH: /tmp/gardener-sa.yaml diff --git a/.github/workflows/pull-gitleaks.yml b/.github/workflows/pull-gitleaks.yml deleted file mode 100644 index ee92cf44..00000000 --- a/.github/workflows/pull-gitleaks.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: pull-gitleaks -on: - pull_request: - types: [opened, edited, synchronize, reopened, ready_for_review] - -env: - GITLEAKS_VERSION: 8.18.2 - -jobs: - scan: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Fetch gitleaks ${{ env.GITLEAKS_VERSION }} - run: curl -Lso gitleaks.tar.gz https://github.com/gitleaks/gitleaks/releases/download/v${{ env.GITLEAKS_VERSION }}/gitleaks_${{ env.GITLEAKS_VERSION }}_linux_x64.tar.gz && tar -xvzf ./gitleaks.tar.gz - - name: Run gitleaks - # Scan commits between base and head of the pull request - run: ./gitleaks detect --log-opts=${PULL_BASE_SHA}...${PULL_HEAD_SHA} --verbose --redact - env: - PULL_BASE_SHA: ${{ github.event.pull_request.base.sha }} - PULL_HEAD_SHA: ${{ github.event.pull_request.head.sha }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml deleted file mode 100644 index 1d415fed..00000000 --- a/.github/workflows/stale.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: 'Manage Stale Issues and Pull Requests' - -on: - schedule: - - cron: '0 0 * * *' # Runs daily at midnight - workflow_dispatch: # Allows manual triggering of the workflow - -jobs: - stale: - runs-on: ubuntu-latest - steps: - - uses: actions/stale@v9 - with: - days-before-stale: 60 - days-before-close: 7 - stale-issue-label: 'lifecycle/stale' - stale-pr-label: 'lifecycle/stale' - exempt-issue-labels: 'lifecycle/frozen,lifecycle/active' - exempt-pr-labels: 'lifecycle/frozen,lifecycle/active' - stale-issue-message: | - This issue has been automatically marked as stale due to the lack of recent activity. It will soon be closed if no further activity occurs. - Thank you for your contributions. - stale-pr-message: | - This pull request has been automatically marked as stale due to the lack of recent activity. It will soon be closed if no further activity occurs. - Thank you for your contributions. - close-issue-message: | - This issue has been automatically closed due to the lack of recent activity. - /lifecycle rotten - close-pr-message: | - This pull request has been automatically closed due to the lack of recent activity. - /lifecycle rotten diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..7d4dd69e --- /dev/null +++ b/.gitignore @@ -0,0 +1,44 @@ +# IDEs +.vscode +.idea +*.swp +*.swo +*~ + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +bin +testbin/* +Dockerfile.cross + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Kubernetes Generated files - skip generated files, except for vendored files + +!vendor/**/zz_generated.* + +module-chart +module-chart-test +mod +default.yaml +moduletemplate.yaml +moduletemplate-k3d.yaml +docs/.DS_Store +.DS_Store +__debug_bin +vendor + +moduletemplate-latest.yaml +module-config.yaml +dockerregistry-operator.yaml +dockerregistry.yaml + +examples/python-text2img/resources/secrets/deepai.env \ No newline at end of file diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 00000000..51f4cf9c --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,26 @@ +# This is a configuration file for the markdownlint. You can use this file to overwrite the default settings. +# MD013 is set to false by default because many files include lines longer than the conventional 80 character limit +MD013: false +# Disable the Multiple headings with the same content rule +MD024: false +# MD029 is set to false because it generated some issues with longer lists +MD029: false +# MD044 is used to set capitalization for particular words. You can determine whether it should be used also for code blocks and HTML elements +MD044: + code_blocks: false + html_elements: false + names: + - Kyma + - Kubernetes + - ConfigMap + - CronJob + - CustomResourceDefinition + - Ingress + - Node + - PodPreset + - Pod + - ProwJob + - Secret + - ServiceBinding + - ServiceClass + - ServiceInstance \ No newline at end of file diff --git a/.markdownlintignore b/.markdownlintignore new file mode 100644 index 00000000..578db9cb --- /dev/null +++ b/.markdownlintignore @@ -0,0 +1 @@ +_sidebar.md \ No newline at end of file diff --git a/.mlc.config.json b/.mlc.config.json index 9e9e139e..70d4d516 100644 --- a/.mlc.config.json +++ b/.mlc.config.json @@ -1,14 +1,12 @@ { - "_comment": "This is a configuration file for the [Markdown link check](https://github.com/tcort/markdown-link-check).", - "_comment": "All `/kyma-project` repositories in GitHub use [Markdown link check](https://github.com/tcort/markdown-link-check) to check their Markdown files for broken links.", - "_comment": "Configuration and maintenance of the Markdown link check tool is the responsibility of a repository owner.", - "_comment": "See the following configuration example.", - "_comment": "For more details read the [repository guidelines](https://github.com/kyma-project/community/blob/main/docs/guidelines/repository-guidelines/01-new-repository-settings.md).", - "replacementPatterns": [ - { - "_comment": "a replacement rule for all the in-repository references", - "pattern": "^/", - "replacement": "{{BASEURL}}/" - } - ] -} + "replacementPatterns": [ + { + "_comment": "a replacement rule for all the in-repository references", + "pattern": "^/", + "replacement": "{{BASEURL}}/" + } + ], + "timeout": "20s", + "retryCount": 5, + "fallbackRetryDelay": "30s" + } \ No newline at end of file diff --git a/.reuse/dep5 b/.reuse/dep5 old mode 100755 new mode 100644 index 96d499f7..62c36819 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -1,11 +1,11 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: -Upstream-Contact: -Source: -Disclaimer: The code in this project may include calls to APIs ("API Calls") of +Upstream-Name: docker-registry +Upstream-Contact: krzysztof.kwiatosz@sap.com +Source: https://github.com/kyma-project/docker-registry +Disclaimer: The code in this project may include calls to APIs (“API Calls”) of SAP or third-party products or services developed outside of this project - ("External Products"). - "APIs" means application programming interfaces, as well as their respective + (“External Products”). + “APIs” means application programming interfaces, as well as their respective specifications and implementing code that allows software to communicate with other software. API Calls to External Products are not licensed under the open source license @@ -16,7 +16,7 @@ Disclaimer: The code in this project may include calls to APIs ("API Calls") of alter, expand or supersede any terms of the applicable additional agreements. If you have a valid license agreement with SAP for the use of a particular SAP External Product, then you may make use of any API Calls included in this - project's code for that SAP External Product, subject to the terms of such + project’s code for that SAP External Product, subject to the terms of such license agreement. If you do not have a valid license agreement for the use of a particular SAP External Product, then you may only make use of any API Calls in this project for that SAP External Product for your internal, non-productive @@ -24,14 +24,6 @@ Disclaimer: The code in this project may include calls to APIs ("API Calls") of you any rights to use or access any SAP External Product, or provide any third parties the right to use of access any SAP External Product, through API Calls. -Files: -Copyright: SAP SE or an SAP affiliate company and contributors +Files: * +Copyright: 2023 SAP SE or an SAP affiliate company and Kyma contributors License: Apache-2.0 - -Files: -Copyright: -License: - -Files: -Copyright: -License: \ No newline at end of file diff --git a/CODEOWNERS b/CODEOWNERS index 35d99741..164aba18 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,38 +1,8 @@ -# Overview +# These are the default owners for the whole content of the `dockerregistry` repository. The default owners are automatically added as reviewers when you open a pull request unless different owners are specified in the file. +* @kyma-project/otters -# The CODEOWNERS file is a GitHub's feature which allows you to create an overview of the code ownership structure in your repository. -# Specify the default owners of your repository and code owners of particular repository parts to define who is automatically requested for a review each time a contributor creates a pull request to the main branch. -# Modify the default settings of the repository and select the "Require review from Code Owners" option on the protected main branch to require one approval from the owners of every part of the repository included in the pull request. For more details, read the following article on GitHub: https://help.github.com/articles/enabling-required-reviews-for-pull-requests/. +# All files and subdirectories in /docs +/docs/ @kyma-project/technical-writers -# Details - -# The CODEOWNERS file is located at the root of your repository and includes a specification of the code ownership structure of the repository. -# It is up to you to decide who is responsible for the review of particular parts and types of files in your repository. - -# When defining the file, keep in mind the following rules: - -# Lines starting with a hash (#) are comments. -# Each line of the file is a file pattern followed by one or more owners. -# You can use individual GitHub usernames, e-mail addresses, or team names to define owners. To define the owners with a team name, first add the team to your repository as collaborators with write access permissions. For more details, read the following article on GitHub: https://help.github.com/articles/adding-outside-collaborators-to-repositories-in-your-organization/. -# Define the default owners of the repository. They are automatically requested for a review of any content at the root of the repository and any content for which no owners are specified in this file. -# Provide granular ownership specification for folders and subfolders. You can also define the owners of specific file types in your repository. -# The order is important. The last matching pattern in the file has the most precedence. - -# Examples - -# These are the default owners for the whole content of the repository, including the content for which no owners are specified in the file. -# * @global-owner1 globalowner@example.com @org/team-name -# The following rule indicates that if a pull request affects folder1 at the root of the repository and any content in that folder, only this owner is requested for a review. -# /folder1/ @testuser1 -# When you use the following pattern, you specify that @testuser2 is responsible for the review of any file in folder2, excluding subfolders located therein. -# /folder2/* @testuser2 -# In this example, you define @testuser3 as the owner of any content in every "docs" folder in the repository. -# docs/ @testuser3 -# When you open a pull request that modifies the "yaml" files, only @testuser4 is requested for a review, and the global owner(s) are not. -# *.yaml @testuser4 - -# Reference - -# For more details, read the following articles on GitHub: -# https://help.github.com/articles/about-codeowners/ -# https://github.com/blog/2392-introducing-code-owners/ +# All .md files +*.md @kyma-project/technical-writers diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index ed7a6b01..b86285c7 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,3 +1,3 @@ -# Code of Conduct +# Code of conduct -Each contributor and maintainer of this project agrees to follow the community [Code of Conduct](https://github.com/kyma-project/community/blob/main/docs/contributing/01-code-of-conduct.md) that relies on the CNCF Code of Conduct. Read it to learn about the agreed standards of behavior, shared values that govern our community, and details on how to report any suspected Code of Conduct violations. +Each contributor and maintainer of this project agrees to follow the [community Code of Conduct](https://github.com/kyma-project/community/blob/main/docs/contributing/01-code-of-conduct.md) that relies on the CNCF Code of Conduct. Read it to learn about the agreed standards of behavior, shared values that govern our community, and details on how to report any suspected Code of Conduct violations. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1ac0012c..71bc0fc6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# Contributing +## Overview -To contribute to this project, follow the general [Contributing Rules](https://github.com/kyma-project/community/blob/main/docs/contributing/02-contributing.md). +To contribute to this project, follow the general [contributing](https://github.com/kyma-project/community/blob/main/docs/contributing/02-contributing.md) guidelines. diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..a2ecafa1 --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +PROJECT_ROOT=. +OPERATOR_ROOT=./components/operator +include ${PROJECT_ROOT}/hack/help.mk +include ${PROJECT_ROOT}/hack/k3d.mk + +##@ Installation +.PHONY: install-dockerregistry-main +install-dockerregistry-main: ## Install dockerregistry with operator using default dockerregistry cr + make -C ${OPERATOR_ROOT} deploy-main apply-default-dockerregistry-cr check-dockerregistry-installation + +.PHONY: install-dockerregistry-custom-operator +install-dockerregistry-custom-operator: ## Install dockerregistry with operator from IMG env using default dockerregistry cr + $(call check-var,IMG) + make -C ${OPERATOR_ROOT} deploy apply-default-dockerregistry-cr check-dockerregistry-installation + +.PHONY: install-dockerregistry-latest-release +install-dockerregistry-latest-release: ## Install dockerregistry from latest release + kubectl create namespace kyma-system || true + kubectl apply -f https://github.com/kyma-project/docker-registry/releases/latest/download/dockerregistry-operator.yaml + kubectl apply -f https://github.com/kyma-project/docker-registry/releases/latest/download/default-dockerregistry-cr.yaml -n kyma-system + make -C ${OPERATOR_ROOT} check-dockerregistry-installation + +.PHONY: remove-dockerregistry +remove-dockerregistry: ## Remove dockerregistry-cr and dockerregistry operator + make -C ${OPERATOR_ROOT} remove-dockerregistry undeploy + +.PHONY: run +run: create-k3d install-dockerregistry-main ## Create k3d cluster and install dockerregistry from main + +check-var = $(if $(strip $($1)),,$(error "$1" is not defined)) + diff --git a/README.md b/README.md index 5714e17b..3933cd73 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,105 @@ -> **NOTE:** This is a general template that you can use for a project README.md. Except for the mandatory sections, use only those sections that suit your use case but keep the proposed section order. -> -> Mandatory sections: -> - `Overview` -> - `Prerequisites`, if there are any requirements regarding hard- or software -> - `Installation` -> - `Contributing` - do not change this! -> - `Code of Conduct` - do not change this! -> - `Licensing` - do not change this! - -# {Project Title} - -> Modify the title and insert the name of your project. Use Heading 1 (H1). +# Serverless + +## Status +![GitHub tag checks state](https://img.shields.io/github/checks-status/kyma-project/serverless-manager/main?label=serverless-operator&link=https%3A%2F%2Fgithub.com%2Fkyma-project%2Fserverless-manager%2Fcommits%2Fmain) + +[![REUSE status](https://api.reuse.software/badge/github.com/kyma-project/docker-registry)](https://api.reuse.software/info/github.com/kyma-project/docker-registry) + ## Overview - -> Provide a description of the project's functionality. -> -> If it is an example README.md, describe what the example illustrates. +Serverless Operator allows deploying the [Serverless](https://kyma-project.io/docs/kyma/latest/01-overview/serverless/) component in the Kyma cluster in compatibility with [Lifecycle Manager](https://github.com/kyma-project/lifecycle-manager). -## Prerequisites +## Install -> List the requirements to run the project or example. +Create the `kyma-system` namespace: -## Installation +```bash +kubectl create namespace kyma-system +``` -> Explain the steps to install your project. If there are multiple installation options, mention the recommended one and include others in a separate document. Create an ordered list for each installation task. -> -> If it is an example README.md, describe how to build, run locally, and deploy the example. Format the example as code blocks and specify the language, highlighting where possible. Explain how you can validate that the example ran successfully. For example, define the expected output or commands to run which check a successful deployment. -> -> Add subsections (H3) for better readability. +Apply the following script to install Serverless Operator: -## Usage +```bash +kubectl apply -f https://github.com/kyma-project/docker-registry/releases/latest/download/serverless-operator.yaml +``` -> Explain how to use the project. You can create multiple subsections (H3). Include the instructions or provide links to the related documentation. +To get Serverless installed, apply the sample Serverless CR: + +```bash +kubectl apply -f https://github.com/kyma-project/docker-registry/releases/latest/download/default-serverless-cr.yaml +``` ## Development -> Add instructions on how to develop the project or example. It must be clear what to do and, for example, how to trigger the tests so that other contributors know how to make their pull requests acceptable. Include the instructions or provide links to related documentation. +### Prerequisites + +- Access to a Kubernetes (v1.24 or higher) cluster +- [Go](https://go.dev/) +- [k3d](https://k3d.io/) +- [Docker](https://www.docker.com/) +- [kubectl](https://kubernetes.io/docs/tasks/tools/) +- [Kubebuilder](https://book.kubebuilder.io/) + + +## Installation in the k3d Cluster Using Make Targets + +1. Clone the project. + + ```bash + git clone https://github.com/kyma-project/docker-registry.git && cd serverless/ + ``` + +2. Build Serverless Operator locally and run it in the k3d cluster. + + ```bash + make run + ``` + +> **NOTE:** To clean up the k3d cluster, use the `make delete-k3d` make target. + + +## Using Serverless Operator + +- Create a Serverless instance. + + ```bash + kubectl apply -f config/samples/default-dockerregistry-cr.yaml + ``` + +- Delete a Serverless instance. -## Contributing - + ```bash + kubectl delete -f config/samples/default-dockerregistry-cr.yaml + ``` -See the [Contributing Rules](CONTRIBUTING.md). +- Use external registry. -## Code of Conduct - + The following example shows how you can modify the Serverless Docker registry address using the `serverless.operator.kyma-project.io` CR: -See the [Code of Conduct](CODE_OF_CONDUCT.md) document. + ```bash + kubectl create secret generic my-secret \ + --namespace kyma-system \ + --from-literal username="" \ + --from-literal password="" \ + --from-literal serverAddress="" \ + --from-literal registryAddress="" + ``` -## Licensing - + > **NOTE:** For DockerHub: + + > - SERVER_ADDRESS is "https://index.docker.io/v1/", + > - USERNAME and REGISTRY_ADDRESS must be identical. -See the [license](./LICENSE) file. + ```bash + cat < $(PROJECT_ROOT)/dockerregistry-operator.yaml + + +.PHONY: apply-default-dockerregistry-cr +apply-default-dockerregistry-cr: ## Apply the k3d dockerregistry CR. + kubectl apply \ + -f ${PROJECT_ROOT}/config/samples/default-dockerregistry-cr.yaml + +.PHONY: remove-dockerregistry +remove-dockerregistry: ## Remove Dockerregistry CR + kubectl delete dockerregistry -n kyma-system default --timeout 2m || (kubectl get dockerregistry -n kyma-system -oyaml && false) + + +.PHONY: check-dockerregistry-installation +check-dockerregistry-installation: ## Wait for Dockerregistry CR to be in Ready state. + # wait some time to make sure operator starts the reconciliation first + sleep 10 + + ./hack/verify_dockerregistry_status.sh || \ + (make print-dockerregistry-details && false) + + kubectl wait --for condition=Available -n kyma-system deployment dockerregistry-operator --timeout=60s || \ + (make print-dockerregistry-details && false) + +.PHONY: print-dockerregistry-details +print-dockerregistry-details: ## Print all pods, deploys and dockerregistry CRs in the kyma-system namespace. + kubectl get dockerregistry -n kyma-system -oyaml + kubectl get deploy -n kyma-system -oyaml + kubectl get pods -n kyma-system -oyaml + + +##@ Module +.PHONY: module-image +module-image: docker-build docker-push ## Build the Module Image and push it to a registry defined in IMG. + echo "built and pushed module image $(IMG)" diff --git a/components/operator/api/v1alpha1/dockerregistry_types.go b/components/operator/api/v1alpha1/dockerregistry_types.go new file mode 100644 index 00000000..4d3b574a --- /dev/null +++ b/components/operator/api/v1alpha1/dockerregistry_types.go @@ -0,0 +1,162 @@ +/* +Copyright 2022. + +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 v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type Endpoint struct { + Endpoint string `json:"endpoint"` +} + +// DockerRegistrySpec defines the desired state of DockerRegistry +type DockerRegistrySpec struct { + // Sets the timeout for the Function health check. The default value in seconds is `10` + HealthzLivenessTimeout string `json:"healthzLivenessTimeout,omitempty"` +} + +type State string + +type Served string + +type ConditionReason string + +type ConditionType string + +const ( + StateReady State = "Ready" + StateProcessing State = "Processing" + StateWarning State = "Warning" + StateError State = "Error" + StateDeleting State = "Deleting" + + ServedTrue Served = "True" + ServedFalse Served = "False" + + // installation and deletion details + ConditionTypeInstalled = ConditionType("Installed") + + // prerequisites and soft dependencies + ConditionTypeConfigured = ConditionType("Configured") + + // deletion + ConditionTypeDeleted = ConditionType("Deleted") + + ConditionReasonConfiguration = ConditionReason("Configuration") + ConditionReasonConfigurationErr = ConditionReason("ConfigurationErr") + ConditionReasonConfigured = ConditionReason("Configured") + ConditionReasonInstallation = ConditionReason("Installation") + ConditionReasonInstallationErr = ConditionReason("InstallationErr") + ConditionReasonInstalled = ConditionReason("Installed") + ConditionReasonDuplicated = ConditionReason("Duplicated") + ConditionReasonDeletion = ConditionReason("Deletion") + ConditionReasonDeletionErr = ConditionReason("DeletionErr") + ConditionReasonDeleted = ConditionReason("Deleted") + + Finalizer = "dockerregistry-operator.kyma-project.io/deletion-hook" +) + +type DockerRegistryStatus struct { + SecretName string `json:"secretName,omitempty"` + + HealthzLivenessTimeout string `json:"healthzLivenessTimeout,omitempty"` + + // State signifies current state of DockerRegistry. + // Value can be one of ("Ready", "Processing", "Error", "Deleting"). + // +kubebuilder:validation:Required + // +kubebuilder:validation:Enum=Processing;Deleting;Ready;Error;Warning + State State `json:"state,omitempty"` + + // Served signifies that current DockerRegistry is managed. + // Value can be one of ("True", "False"). + // +kubebuilder:validation:Enum=True;False + Served Served `json:"served"` + + // Conditions associated with CustomStatus. + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// +k8s:deepcopy-gen=true + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:printcolumn:name="Configured",type="string",JSONPath=".status.conditions[?(@.type=='Configured')].status" +//+kubebuilder:printcolumn:name="Installed",type="string",JSONPath=".status.conditions[?(@.type=='Installed')].status" +//+kubebuilder:printcolumn:name="generation",type="integer",JSONPath=".metadata.generation" +//+kubebuilder:printcolumn:name="age",type="date",JSONPath=".metadata.creationTimestamp" +//+kubebuilder:printcolumn:name="state",type="string",JSONPath=".status.state" + +// DockerRegistry is the Schema for the dockerregistry API +type DockerRegistry struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec DockerRegistrySpec `json:"spec,omitempty"` + Status DockerRegistryStatus `json:"status,omitempty"` +} + +func (s *DockerRegistry) UpdateConditionFalse(c ConditionType, r ConditionReason, err error) { + condition := metav1.Condition{ + Type: string(c), + Status: "False", + LastTransitionTime: metav1.Now(), + Reason: string(r), + Message: err.Error(), + } + meta.SetStatusCondition(&s.Status.Conditions, condition) +} + +func (s *DockerRegistry) UpdateConditionUnknown(c ConditionType, r ConditionReason, msg string) { + condition := metav1.Condition{ + Type: string(c), + Status: "Unknown", + LastTransitionTime: metav1.Now(), + Reason: string(r), + Message: msg, + } + meta.SetStatusCondition(&s.Status.Conditions, condition) +} + +func (s *DockerRegistry) UpdateConditionTrue(c ConditionType, r ConditionReason, msg string) { + condition := metav1.Condition{ + Type: string(c), + Status: "True", + LastTransitionTime: metav1.Now(), + Reason: string(r), + Message: msg, + } + meta.SetStatusCondition(&s.Status.Conditions, condition) +} + +func (s *DockerRegistry) IsServedEmpty() bool { + return s.Status.Served == "" +} + +//+kubebuilder:object:root=true + +// DockerRegistryList contains a list of DockerRegistry +type DockerRegistryList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []DockerRegistry `json:"items"` +} + +func init() { + SchemeBuilder.Register(&DockerRegistry{}, &DockerRegistryList{}) +} diff --git a/components/operator/api/v1alpha1/groupversion_info.go b/components/operator/api/v1alpha1/groupversion_info.go new file mode 100644 index 00000000..9a7c8cb2 --- /dev/null +++ b/components/operator/api/v1alpha1/groupversion_info.go @@ -0,0 +1,41 @@ +/* +Copyright 2022. + +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 v1alpha1 contains API Schema definitions for the operator v1alpha1 API group +// +kubebuilder:object:generate=true +// +groupName=operator.kyma-project.io +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +const ( + DockerregistryGroup = "operator.kyma-project.io" + DockerregistryVersion = "v1alpha1" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: DockerregistryGroup, Version: DockerregistryVersion} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/components/operator/api/v1alpha1/helpers.go b/components/operator/api/v1alpha1/helpers.go new file mode 100644 index 00000000..eea1d0e1 --- /dev/null +++ b/components/operator/api/v1alpha1/helpers.go @@ -0,0 +1,26 @@ +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func (s *DockerRegistry) IsInState(state State) bool { + return s.Status.State == state +} + +func (s *DockerRegistry) IsCondition(conditionType ConditionType) bool { + return meta.FindStatusCondition( + s.Status.Conditions, string(conditionType), + ) != nil +} + +func (s *DockerRegistry) IsConditionTrue(conditionType ConditionType) bool { + condition := meta.FindStatusCondition(s.Status.Conditions, string(conditionType)) + return condition != nil && condition.Status == metav1.ConditionTrue +} + +const ( + DefaultEnableInternal = false + EndpointDisabled = "" +) diff --git a/components/operator/api/v1alpha1/zz_generated.deepcopy.go b/components/operator/api/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000..74d48ab7 --- /dev/null +++ b/components/operator/api/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,137 @@ +//go:build !ignore_autogenerated + +/* +Copyright 2022. + +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. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DockerRegistry) DeepCopyInto(out *DockerRegistry) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DockerRegistry. +func (in *DockerRegistry) DeepCopy() *DockerRegistry { + if in == nil { + return nil + } + out := new(DockerRegistry) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DockerRegistry) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DockerRegistryList) DeepCopyInto(out *DockerRegistryList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]DockerRegistry, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DockerRegistryList. +func (in *DockerRegistryList) DeepCopy() *DockerRegistryList { + if in == nil { + return nil + } + out := new(DockerRegistryList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DockerRegistryList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DockerRegistrySpec) DeepCopyInto(out *DockerRegistrySpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DockerRegistrySpec. +func (in *DockerRegistrySpec) DeepCopy() *DockerRegistrySpec { + if in == nil { + return nil + } + out := new(DockerRegistrySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DockerRegistryStatus) DeepCopyInto(out *DockerRegistryStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DockerRegistryStatus. +func (in *DockerRegistryStatus) DeepCopy() *DockerRegistryStatus { + if in == nil { + return nil + } + out := new(DockerRegistryStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Endpoint) DeepCopyInto(out *Endpoint) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Endpoint. +func (in *Endpoint) DeepCopy() *Endpoint { + if in == nil { + return nil + } + out := new(Endpoint) + in.DeepCopyInto(out) + return out +} diff --git a/components/operator/controllers/controller.go b/components/operator/controllers/controller.go new file mode 100644 index 00000000..658ee537 --- /dev/null +++ b/components/operator/controllers/controller.go @@ -0,0 +1,80 @@ +/* +Copyright 2022. + +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 controllers + +import ( + "context" + + "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + "github.com/kyma-project/docker-registry/components/operator/internal/chart" + "github.com/kyma-project/docker-registry/components/operator/internal/predicate" + "github.com/kyma-project/docker-registry/components/operator/internal/state" + "github.com/kyma-project/docker-registry/components/operator/internal/tracing" + "github.com/pkg/errors" + "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// dockerRegistryReconciler reconciles a DockerRegistry object +type dockerRegistryReconciler struct { + initStateMachine func(*zap.SugaredLogger) state.StateReconciler + client client.Client + log *zap.SugaredLogger +} + +func NewDockerRegistryReconciler(client client.Client, config *rest.Config, recorder record.EventRecorder, log *zap.SugaredLogger, chartPath string) *dockerRegistryReconciler { + cache := chart.NewSecretManifestCache(client) + + return &dockerRegistryReconciler{ + initStateMachine: func(log *zap.SugaredLogger) state.StateReconciler { + return state.NewMachine(client, config, recorder, log, cache, chartPath) + }, + client: client, + log: log, + } +} + +// SetupWithManager sets up the controller with the Manager. +func (sr *dockerRegistryReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&v1alpha1.DockerRegistry{}, builder.WithPredicates(predicate.NoStatusChangePredicate{})). + Watches(&corev1.Service{}, tracing.ServiceCollectorWatcher()). + Complete(sr) +} + +func (sr *dockerRegistryReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := sr.log.With("request", req) + log.Info("reconciliation started") + + instance, err := state.GetDockerRegistryOrServed(ctx, req, sr.client) + if err != nil { + log.Warnf("while getting dockerregistry, got error: %s", err.Error()) + return ctrl.Result{}, errors.Wrap(err, "while fetching dockerregistry instance") + } + if instance == nil { + log.Info("Couldn't find proper instance of dockerregistry") + return ctrl.Result{}, nil + } + + r := sr.initStateMachine(log) + return r.Reconcile(ctx, *instance) +} diff --git a/components/operator/controllers/controller_rbac.go b/components/operator/controllers/controller_rbac.go new file mode 100644 index 00000000..4d94ff8a --- /dev/null +++ b/components/operator/controllers/controller_rbac.go @@ -0,0 +1,35 @@ +package controllers + +// TODO: dockerregistry-manager doesn't need almost half of these rbscs. It uses them only to create another rbacs ( is there any onther option? - investigate ) + +//+kubebuilder:rbac:groups="",resources=events,verbs=get;list;watch;create;patch +//+kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch;create;update;patch;delete;deletecollection +//+kubebuilder:rbac:groups="",resources=services;secrets;serviceaccounts;configmaps,verbs=get;list;watch;create;update;patch;delete;deletecollection +//+kubebuilder:rbac:groups="",resources=nodes,verbs=list;watch;get +//+kubebuilder:rbac:groups="",resources=persistentvolumeclaims,verbs=get;list;watch;create;update;patch;delete;deletecollection + +//+kubebuilder:rbac:groups=apps,resources=replicasets,verbs=list +//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete;deletecollection +//+kubebuilder:rbac:groups=apps,resources=deployments/status,verbs=get +//+kubebuilder:rbac:groups=apps,resources=daemonsets,verbs=get;list;watch;create;update;patch;delete;deletecollection + +//+kubebuilder:rbac:groups=autoscaling,resources=horizontalpodautoscalers,verbs=get;list;watch;create;update;patch;delete;deletecollection + +//+kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete;deletecollection +//+kubebuilder:rbac:groups=batch,resources=jobs/status,verbs=get + +//+kubebuilder:rbac:groups=policy,resources=podsecuritypolicies,verbs=use + +//+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles;clusterrolebindings,verbs=get;list;watch;create;update;patch;delete;deletecollection +//+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings;roles,verbs=get;list;watch;create;update;patch;delete;deletecollection + +//+kubebuilder:rbac:groups=operator.kyma-project.io,resources=dockerregistries,verbs=get;list;watch;create;update;patch;delete;deletecollection +//+kubebuilder:rbac:groups=operator.kyma-project.io,resources=dockerregistries/status,verbs=get;list;watch;create;update;patch;delete;deletecollection +//+kubebuilder:rbac:groups=operator.kyma-project.io,resources=dockerregistries/finalizers,verbs=get;list;watch;create;update;patch;delete;deletecollection + +//+kubebuilder:rbac:groups=admissionregistration.k8s.io,resources=validatingwebhookconfigurations;mutatingwebhookconfigurations,verbs=get;list;watch;create;update;patch;delete;deletecollection + +//+kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get;list;watch;create;update;patch;delete;deletecollection + +//+kubebuilder:rbac:groups=coordination.k8s.io,resources=leases,verbs=get;list;watch;create;update;patch;delete;deletecollection +//+kubebuilder:rbac:groups=scheduling.k8s.io,resources=priorityclasses,verbs=get;list;watch;create;update;patch;delete;deletecollection diff --git a/components/operator/controllers/controller_test.go b/components/operator/controllers/controller_test.go new file mode 100644 index 00000000..30c26993 --- /dev/null +++ b/components/operator/controllers/controller_test.go @@ -0,0 +1,99 @@ +package controllers + +import ( + "context" + "github.com/kyma-project/docker-registry/components/operator/internal/registry" + "time" + + "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + appsv1 "k8s.io/api/apps/v1" + "k8s.io/utils/ptr" +) + +var _ = Describe("DockerRegistry controller", func() { + Context("When creating fresh instance", func() { + const ( + namespaceName = "kyma-system" + crName = "cr-test" + deploymentName = "internal-docker-registry" + registrySecret = registry.SecretName + ) + + var ( + defaultData = dockerRegistryData{ + TraceCollectorURL: ptr.To[string](v1alpha1.EndpointDisabled), + EnableInternal: ptr.To[bool](v1alpha1.DefaultEnableInternal), + } + ) + + It("The status should be Success", func() { + h := testHelper{ + ctx: context.Background(), + namespaceName: namespaceName, + } + h.createNamespace() + + { + emptyData := v1alpha1.DockerRegistrySpec{} + shouldCreateDockerRegistry(h, crName, deploymentName, emptyData) + shouldPropagateSpecProperties(h, registrySecret, defaultData) + } + + shouldDeleteDockerRegistry(h, crName, deploymentName) + }) + }) +}) + +func shouldCreateDockerRegistry(h testHelper, name, deploymentName string, spec v1alpha1.DockerRegistrySpec) { + // act + h.createDockerRegistry(name, spec) + + // we have to update deployment status manually + h.updateDeploymentStatus(deploymentName) + + // assert + Eventually(h.getDockerRegistryStatusFunc(name)). + WithPolling(time.Second * 2). + WithTimeout(time.Second * 20). + Should(ConditionTrueMatcher()) +} + +func shouldPropagateSpecProperties(h testHelper, registrySecretName string, expected dockerRegistryData) { + Eventually(h.createCheckRegistrySecretFunc(registrySecretName, expected.registrySecretData)). + WithPolling(time.Second * 2). + WithTimeout(time.Second * 10). + Should(BeTrue()) +} + +func shouldDeleteDockerRegistry(h testHelper, name, deploymentName string) { + // initial assert + var deployList appsv1.DeploymentList + Eventually(h.listKubernetesObjectFunc(&deployList)). + WithPolling(time.Second * 2). + WithTimeout(time.Second * 10). + Should(BeTrue()) + + Expect(deployList.Items).To(HaveLen(1)) + + // act + var dockerRegistry v1alpha1.DockerRegistry + Eventually(h.getKubernetesObjectFunc(name, &dockerRegistry)). + WithPolling(time.Second * 2). + WithTimeout(time.Second * 10). + Should(BeTrue()) + + Expect(k8sClient.Delete(h.ctx, &dockerRegistry)).To(Succeed()) + + Eventually(h.getKubernetesObjectFunc(name, &dockerRegistry)). + WithPolling(time.Second * 2). + WithTimeout(time.Second * 10). + Should(BeTrue()) + + // assert + Eventually(h.getKubernetesObjectFunc(deploymentName, &appsv1.Deployment{})). + WithPolling(time.Second * 2). + WithTimeout(time.Second * 10). + Should(BeTrue()) +} diff --git a/components/operator/controllers/suite_test.go b/components/operator/controllers/suite_test.go new file mode 100644 index 00000000..64dca95e --- /dev/null +++ b/components/operator/controllers/suite_test.go @@ -0,0 +1,120 @@ +/* +Copyright 2022. + +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 controllers + +import ( + "context" + "path/filepath" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + uberzap "go.uber.org/zap" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + operatorv1alpha1 "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + //+kubebuilder:scaffold:imports +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var ( + config *rest.Config + k8sClient client.Client + testEnv *envtest.Environment + + suiteCtx context.Context + cancelSuiteCtx context.CancelFunc +) + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{ + filepath.Join("..", "..", "..", "config", "operator", "base", "crd", "bases"), + }, + BinaryAssetsDirectory: filepath.Join("..", "..", "..", "..", "..", "bin", "k8s", "kubebuilder_assets"), + ErrorIfCRDPathMissing: true, + } + + var err error + // cfg is defined in this file globally. + config, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(config).NotTo(BeNil()) + + err = operatorv1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + k8sClient, err = client.New(config, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + + k8sManager, err := ctrl.NewManager(config, ctrl.Options{ + Scheme: scheme.Scheme, + }) + Expect(err).ToNot(HaveOccurred()) + + config := uberzap.NewProductionConfig() + reconcilerLogger, err := config.Build() + Expect(err).NotTo(HaveOccurred()) + + chartPath := filepath.Join("..", "..", "..", "config", "docker-registry") + err = (NewDockerRegistryReconciler( + k8sManager.GetClient(), + k8sManager.GetConfig(), + record.NewFakeRecorder(100), + reconcilerLogger.Sugar(), + chartPath)). + SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + go func() { + defer GinkgoRecover() + + suiteCtx, cancelSuiteCtx = context.WithCancel(context.Background()) + + err = k8sManager.Start(suiteCtx) + Expect(err).ToNot(HaveOccurred(), "failed to run manager") + }() +}) + +var _ = AfterSuite(func() { + cancelSuiteCtx() + + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/components/operator/controllers/testhelper_test.go b/components/operator/controllers/testhelper_test.go new file mode 100644 index 00000000..bb5b299d --- /dev/null +++ b/components/operator/controllers/testhelper_test.go @@ -0,0 +1,285 @@ +package controllers + +import ( + "context" + "fmt" + "time" + + "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + gomegatypes "github.com/onsi/gomega/types" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type conditionMatcher struct { + expectedState v1alpha1.State + expectedConditionStatus metav1.ConditionStatus +} + +func ConditionTrueMatcher() gomegatypes.GomegaMatcher { + return &conditionMatcher{ + expectedState: v1alpha1.StateReady, + expectedConditionStatus: metav1.ConditionTrue, + } +} + +func (matcher *conditionMatcher) Match(actual interface{}) (success bool, err error) { + status, ok := actual.(v1alpha1.DockerRegistryStatus) + if !ok { + return false, fmt.Errorf("ConditionMatcher matcher expects an v1alpha1.DockerRegistryStatus") + } + + if status.State != matcher.expectedState { + return false, nil + } + + for _, condition := range status.Conditions { + if condition.Status != matcher.expectedConditionStatus { + return false, nil + } + } + + return true, nil +} + +func (matcher *conditionMatcher) FailureMessage(actual interface{}) (message string) { + return fmt.Sprintf("Expected\n\t%#v\nto be in %s state with all %s conditions", + actual, matcher.expectedState, matcher.expectedConditionStatus) +} + +func (matcher *conditionMatcher) NegatedFailureMessage(actual interface{}) (message string) { + return fmt.Sprintf("Expected\n\t%#v\nto be in %s state with all %s conditions", + actual, matcher.expectedState, matcher.expectedConditionStatus) +} + +type testHelper struct { + ctx context.Context + namespaceName string +} + +func (h *testHelper) updateDeploymentStatus(deploymentName string) { + By(fmt.Sprintf("Updating deployment status: %s", deploymentName)) + var deployment appsv1.Deployment + Eventually(h.getKubernetesObjectFunc(deploymentName, &deployment)). + WithPolling(time.Second * 2). + WithTimeout(time.Second * 30). + Should(BeTrue()) + + deployment.Status.Conditions = append(deployment.Status.Conditions, appsv1.DeploymentCondition{ + Type: appsv1.DeploymentAvailable, + Status: corev1.ConditionTrue, + Reason: "test-reason", + Message: "test-message", + }) + deployment.Status.Replicas = 1 + Expect(k8sClient.Status().Update(h.ctx, &deployment)).To(Succeed()) + + replicaSetName := h.createReplicaSetForDeployment(deployment) + + var replicaSet appsv1.ReplicaSet + Eventually(h.getKubernetesObjectFunc(replicaSetName, &replicaSet)). + WithPolling(time.Second * 2). + WithTimeout(time.Second * 30). + Should(BeTrue()) + + replicaSet.Status.ReadyReplicas = 1 + replicaSet.Status.Replicas = 1 + Expect(k8sClient.Status().Update(h.ctx, &replicaSet)).To(Succeed()) + + By(fmt.Sprintf("Deployment status updated: %s", deploymentName)) +} + +func (h *testHelper) createReplicaSetForDeployment(deployment appsv1.Deployment) string { + replicaSetName := fmt.Sprintf("%s-replica-set", deployment.Name) + By(fmt.Sprintf("Creating replica set (for deployment): %s", replicaSetName)) + var ( + trueValue = true + one = int32(1) + ) + replicaSet := appsv1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: replicaSetName, + Namespace: h.namespaceName, + Labels: deployment.Spec.Selector.MatchLabels, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "apps/v1", + Kind: "Deployment", + Name: deployment.Name, + UID: deployment.GetUID(), + Controller: &trueValue, + }, + }, + }, + // dummy values + Spec: appsv1.ReplicaSetSpec{ + Replicas: &one, + Selector: deployment.Spec.Selector, + Template: deployment.Spec.Template, + }, + } + Expect(k8sClient.Create(h.ctx, &replicaSet)).To(Succeed()) + By(fmt.Sprintf("Replica set (for deployment) created: %s", replicaSetName)) + return replicaSetName +} + +func (h *testHelper) createDockerRegistry(crName string, spec v1alpha1.DockerRegistrySpec) { + By(fmt.Sprintf("Creating cr: %s", crName)) + dockerRegistry := v1alpha1.DockerRegistry{ + ObjectMeta: metav1.ObjectMeta{ + Name: crName, + Namespace: h.namespaceName, + Labels: map[string]string{ + "operator.kyma-project.io/kyma-name": "test", + }, + }, + Spec: spec, + } + Expect(k8sClient.Create(h.ctx, &dockerRegistry)).To(Succeed()) + By(fmt.Sprintf("Crd created: %s", crName)) +} + +func (h *testHelper) createNamespace() { + By(fmt.Sprintf("Creating namespace: %s", h.namespaceName)) + namespace := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: h.namespaceName, + }, + } + Expect(k8sClient.Create(h.ctx, &namespace)).To(Succeed()) + By(fmt.Sprintf("Namespace created: %s", h.namespaceName)) +} + +func (h *testHelper) getKubernetesObjectFunc(objectName string, obj client.Object) func() (bool, error) { + return func() (bool, error) { + return h.getKubernetesObject(objectName, obj) + } +} + +func (h *testHelper) getKubernetesObject(objectName string, obj client.Object) (bool, error) { + key := types.NamespacedName{ + Name: objectName, + Namespace: h.namespaceName, + } + + err := k8sClient.Get(h.ctx, key, obj) + if err != nil { + return false, err + } + return true, err +} + +func (h *testHelper) listKubernetesObjectFunc(list client.ObjectList) func() (bool, error) { + return func() (bool, error) { + return h.listKubernetesObject(list) + } +} + +func (h *testHelper) listKubernetesObject(list client.ObjectList) (bool, error) { + opts := client.ListOptions{ + Namespace: h.namespaceName, + } + + err := k8sClient.List(h.ctx, list, &opts) + if err != nil { + return false, err + } + return true, err +} + +func (h *testHelper) getDockerRegistryStatusFunc(name string) func() (v1alpha1.DockerRegistryStatus, error) { + return func() (v1alpha1.DockerRegistryStatus, error) { + return h.getDockerRegistryStatus(name) + } +} + +func (h *testHelper) getDockerRegistryStatus(name string) (v1alpha1.DockerRegistryStatus, error) { + var dockerRegistry v1alpha1.DockerRegistry + key := types.NamespacedName{ + Name: name, + Namespace: h.namespaceName, + } + err := k8sClient.Get(h.ctx, key, &dockerRegistry) + if err != nil { + return v1alpha1.DockerRegistryStatus{}, err + } + return dockerRegistry.Status, nil +} + +type dockerRegistryData struct { + EventPublisherProxyURL *string + TraceCollectorURL *string + EnableInternal *bool + registrySecretData +} + +type registrySecretData struct { + Username *string + Password *string + ServerAddress *string + RegistryAddress *string +} + +func (d *registrySecretData) toMap() map[string]string { + result := map[string]string{} + if d.Username != nil { + result["username"] = *d.Username + } + if d.Password != nil { + result["password"] = *d.Password + } + if d.ServerAddress != nil { + result["serverAddress"] = *d.ServerAddress + } + if d.RegistryAddress != nil { + result["registryAddress"] = *d.RegistryAddress + } + return result +} + +func (h *testHelper) createCheckRegistrySecretFunc(registrySecret string, expected registrySecretData) func() (bool, error) { + return func() (bool, error) { + var configurationSecret corev1.Secret + + if ok, err := h.getKubernetesObject( + registrySecret, &configurationSecret); !ok || err != nil { + return ok, err + } + if ok, err := secretContainsSameValues( + expected.toMap(), configurationSecret); err != nil { + return ok, err + } + if ok, err := secretContainsRequired(configurationSecret); err != nil { + return ok, err + } + return true, nil + } +} + +func secretContainsRequired(configurationSecret corev1.Secret) (bool, error) { + for _, k := range []string{"username", "password", "pullRegAddr", "pushRegAddr", ".dockerconfigjson"} { + _, ok := configurationSecret.Data[k] + if !ok { + return false, fmt.Errorf("values not propagated (%s is required)", k) + } + } + return false, nil +} + +func secretContainsSameValues(expected map[string]string, configurationSecret corev1.Secret) (bool, error) { + for k, expectedV := range expected { + v, okV := configurationSecret.Data[k] + if okV == false { + return false, fmt.Errorf("values not propagated (%s: nil != %s )", k, expectedV) + } + if expectedV != string(v) { + return false, fmt.Errorf("values not propagated (%s: %s != %s )", k, string(v), expectedV) + } + } + return false, nil +} diff --git a/components/operator/hack/boilerplate.go.txt b/components/operator/hack/boilerplate.go.txt new file mode 100755 index 00000000..29c55ecd --- /dev/null +++ b/components/operator/hack/boilerplate.go.txt @@ -0,0 +1,15 @@ +/* +Copyright 2022. + +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. +*/ \ No newline at end of file diff --git a/components/operator/hack/verify_dockerregistry_status.sh b/components/operator/hack/verify_dockerregistry_status.sh new file mode 100755 index 00000000..5726552c --- /dev/null +++ b/components/operator/hack/verify_dockerregistry_status.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +function get_dockerregistry_status () { + local number=1 + while [[ $number -le 100 ]] ; do + echo ">--> checking dockerregistry status #$number" + local STATUS=$(kubectl get dockerregistry -n kyma-system default -o jsonpath='{.status.state}') + echo "dockerregistry status: ${STATUS:='UNKNOWN'}" + [[ "$STATUS" == "Ready" ]] && return 0 + sleep 5 + ((number = number + 1)) + done + + kubectl get all --all-namespaces + exit 1 +} + +get_dockerregistry_status diff --git a/components/operator/internal/annotation/disclaimer.go b/components/operator/internal/annotation/disclaimer.go new file mode 100644 index 00000000..1ed584fc --- /dev/null +++ b/components/operator/internal/annotation/disclaimer.go @@ -0,0 +1,22 @@ +package annotation + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +const ( + annotation = "dockerregistry-manager.kyma-project.io/managed-by-dockerregistry-manager-disclaimer" + message = "DO NOT EDIT - This resource is managed by DockerRegistry-Manager.\nAny modifications are discarded and the resource is reverted to the original state." +) + +func AddDoNotEditDisclaimer(obj unstructured.Unstructured) unstructured.Unstructured { + annotations := obj.GetAnnotations() + if annotations == nil { + annotations = map[string]string{} + } + + annotations[annotation] = message + obj.SetAnnotations(annotations) + + return obj +} diff --git a/components/operator/internal/annotation/disclaimer_test.go b/components/operator/internal/annotation/disclaimer_test.go new file mode 100644 index 00000000..26f013f1 --- /dev/null +++ b/components/operator/internal/annotation/disclaimer_test.go @@ -0,0 +1,17 @@ +package annotation + +import ( + "testing" + + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func TestAddDoNotEditDisclaimer(t *testing.T) { + t.Run("add disclaimer", func(t *testing.T) { + obj := unstructured.Unstructured{} + obj = AddDoNotEditDisclaimer(obj) + + require.Equal(t, message, obj.GetAnnotations()[annotation]) + }) +} diff --git a/components/operator/internal/chart/cache.go b/components/operator/internal/chart/cache.go new file mode 100644 index 00000000..3582308f --- /dev/null +++ b/components/operator/internal/chart/cache.go @@ -0,0 +1,142 @@ +package chart + +import ( + "context" + "sync" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/json" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var ( + _ ManifestCache = (*inMemoryManifestCache)(nil) + _ ManifestCache = (*secretManifestCache)(nil) +) + +var ( + emptySpecManifest = DockerRegistrySpecManifest{} +) + +type ManifestCache interface { + Set(context.Context, client.ObjectKey, DockerRegistrySpecManifest) error + Get(context.Context, client.ObjectKey) (DockerRegistrySpecManifest, error) + Delete(context.Context, client.ObjectKey) error +} + +// inMemoryManifestCache provides an in-memory processor to store dockerregistry Spec and rendered chart manifest. By using sync.Map for caching, +// concurrent operations to the processor from diverse reconciliations are considered safe. +// +// Inside the processor is stored chart manifest with used custom flags by client.ObjectKey key. +type inMemoryManifestCache struct { + processor sync.Map +} + +// NewInMemoryManifestCache returns a new instance of inMemoryManifestCache. +func NewInMemoryManifestCache() *inMemoryManifestCache { + return &inMemoryManifestCache{ + processor: sync.Map{}, + } +} + +// Get loads the DockerRegistrySpecManifest from inMemoryManifestCache for the passed client.ObjectKey. +func (r *inMemoryManifestCache) Get(_ context.Context, key client.ObjectKey) (DockerRegistrySpecManifest, error) { + value, ok := r.processor.Load(key) + if !ok { + return emptySpecManifest, nil + } + + return *value.(*DockerRegistrySpecManifest), nil +} + +// Set saves the passed flags and manifest into inMemoryManifestCache for the client.ObjectKey. +func (r *inMemoryManifestCache) Set(_ context.Context, key client.ObjectKey, spec DockerRegistrySpecManifest) error { + r.processor.Store(key, &spec) + + return nil +} + +// Delete deletes flags and manifest from inMemoryManifestCache for the passed client.ObjectKey. +func (r *inMemoryManifestCache) Delete(_ context.Context, key client.ObjectKey) error { + r.processor.Delete(key) + return nil +} + +// secretManifestCache - provides a Secret based processor to store dockerregistry Spec and rendered chart manifest. +// +// Inside the secret we store manifest and flags used to render it. +type secretManifestCache struct { + client client.Client +} + +type DockerRegistrySpecManifest struct { + ManagerUID string + CustomFlags map[string]interface{} + Manifest string +} + +// NewSecretManifestCache - returns a new instance of SecretManifestCache. +func NewSecretManifestCache(client client.Client) *secretManifestCache { + return &secretManifestCache{ + client: client, + } +} + +// Delete - removes Secret cache based on the passed client.ObjectKey. +func (m *secretManifestCache) Delete(ctx context.Context, key client.ObjectKey) error { + err := m.client.Delete(ctx, &corev1.Secret{ + ObjectMeta: v1.ObjectMeta{ + Name: key.Name, + Namespace: key.Namespace, + }, + }) + + return client.IgnoreNotFound(err) +} + +// Get - loads the DockerRegistrySpecManifest from SecretManifestCache based on the passed client.ObjectKey. +func (m *secretManifestCache) Get(ctx context.Context, key client.ObjectKey) (DockerRegistrySpecManifest, error) { + secret := corev1.Secret{} + err := m.client.Get(ctx, key, &secret) + if errors.IsNotFound(err) { + return emptySpecManifest, nil + } + if err != nil { + return emptySpecManifest, err + } + + spec := DockerRegistrySpecManifest{} + err = json.Unmarshal(secret.Data["spec"], &spec) + if err != nil { + return emptySpecManifest, err + } + + return spec, nil +} + +// Set - saves the passed flags and manifest into Secret based on the client.ObjectKey. +func (m *secretManifestCache) Set(ctx context.Context, key client.ObjectKey, spec DockerRegistrySpecManifest) error { + byteSpec, err := json.Marshal(&spec) + if err != nil { + return err + } + + secret := corev1.Secret{ + ObjectMeta: v1.ObjectMeta{ + Name: key.Name, + Namespace: key.Namespace, + }, + Data: map[string][]byte{ + "spec": byteSpec, + }, + } + + err = m.client.Update(ctx, &secret) + if !errors.IsNotFound(err) { + return err + } + + return m.client.Create(ctx, &secret) +} diff --git a/components/operator/internal/chart/cache_test.go b/components/operator/internal/chart/cache_test.go new file mode 100644 index 00000000..7b182399 --- /dev/null +++ b/components/operator/internal/chart/cache_test.go @@ -0,0 +1,262 @@ +package chart + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + apiextensionsscheme "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme" + "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/json" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +const testSecretNamespace = "kyma-system" + +func TestManifestCache_Delete(t *testing.T) { + t.Run("delete secret", func(t *testing.T) { + key := types.NamespacedName{ + Name: "test-name", + Namespace: testSecretNamespace, + } + ctx := context.TODO() + client := fake.NewClientBuilder().WithRuntimeObjects( + fixSecretCache(t, key, emptySpecManifest), + ).Build() + + cache := NewSecretManifestCache(client) + + err := cache.Delete(ctx, key) + require.NoError(t, err) + + var secret corev1.Secret + err = client.Get(ctx, key, &secret) + require.True(t, errors.IsNotFound(err), fmt.Sprintf("got error: %v", err)) + }) + + t.Run("delete error", func(t *testing.T) { + scheme := runtime.NewScheme() + // apiextensionscheme does not contains v1.Secret scheme + require.NoError(t, apiextensionsscheme.AddToScheme(scheme)) + + key := types.NamespacedName{ + Name: "test-name", + Namespace: testSecretNamespace, + } + ctx := context.TODO() + client := fake.NewClientBuilder().WithScheme(scheme).Build() + + cache := NewSecretManifestCache(client) + + err := cache.Delete(ctx, key) + require.Error(t, err) + }) + + t.Run("do nothing when cache is not found", func(t *testing.T) { + key := types.NamespacedName{ + Name: "test-name", + Namespace: testSecretNamespace, + } + ctx := context.TODO() + client := fake.NewClientBuilder().Build() + + cache := NewSecretManifestCache(client) + + err := cache.Delete(ctx, key) + require.NoError(t, err) + }) +} + +func TestManifestCache_Get(t *testing.T) { + t.Run("get secret value", func(t *testing.T) { + key := types.NamespacedName{ + Name: "test-name", + Namespace: testSecretNamespace, + } + ctx := context.TODO() + client := fake.NewClientBuilder().WithRuntimeObjects( + fixSecretCache(t, key, DockerRegistrySpecManifest{ + CustomFlags: map[string]interface{}{ + "flag1": "val1", + "flag2": "val2", + }, + Manifest: "schmetterling", + }), + ).Build() + + cache := NewSecretManifestCache(client) + + result, err := cache.Get(ctx, key) + require.NoError(t, err) + + expectedResult := DockerRegistrySpecManifest{ + CustomFlags: map[string]interface{}{ + "flag1": "val1", + "flag2": "val2", + }, + Manifest: "schmetterling", + } + require.Equal(t, expectedResult, result) + }) + + t.Run("client error", func(t *testing.T) { + scheme := runtime.NewScheme() + // apiextensionscheme does not contains v1.Secret scheme + require.NoError(t, apiextensionsscheme.AddToScheme(scheme)) + + key := types.NamespacedName{ + Name: "test-name", + Namespace: testSecretNamespace, + } + ctx := context.TODO() + client := fake.NewClientBuilder().WithScheme(scheme).Build() + + cache := NewSecretManifestCache(client) + + result, err := cache.Get(ctx, key) + require.Error(t, err) + require.Equal(t, emptySpecManifest, result) + }) + + t.Run("secret not found", func(t *testing.T) { + key := types.NamespacedName{ + Name: "test-name", + Namespace: testSecretNamespace, + } + ctx := context.TODO() + client := fake.NewClientBuilder().Build() + + cache := NewSecretManifestCache(client) + + result, err := cache.Get(ctx, key) + require.NoError(t, err) + require.Equal(t, emptySpecManifest, result) + }) + + t.Run("conversion error", func(t *testing.T) { + key := types.NamespacedName{ + Name: "test-name", + Namespace: testSecretNamespace, + } + ctx := context.TODO() + client := fake.NewClientBuilder().WithRuntimeObjects( + &corev1.Secret{ + ObjectMeta: v1.ObjectMeta{ + Name: key.Name, + Namespace: key.Namespace, + }, + Data: map[string][]byte{ + "spec": []byte("{UNEXPECTED}"), + }, + }).Build() + + cache := NewSecretManifestCache(client) + + result, err := cache.Get(ctx, key) + require.Error(t, err) + require.Equal(t, emptySpecManifest, result) + }) +} + +func TestManifestCache_Set(t *testing.T) { + t.Run("create secret", func(t *testing.T) { + key := types.NamespacedName{ + Name: "test-name", + Namespace: testSecretNamespace, + } + ctx := context.TODO() + client := fake.NewClientBuilder().Build() + + cache := NewSecretManifestCache(client) + expectedSpec := DockerRegistrySpecManifest{ + Manifest: "schmetterling", + CustomFlags: map[string]interface{}{ + "flag1": "val1", + "flag2": "val2", + }, + } + + err := cache.Set(ctx, key, expectedSpec) + require.NoError(t, err) + + var secret corev1.Secret + require.NoError(t, client.Get(ctx, key, &secret)) + + actualSpec := DockerRegistrySpecManifest{} + err = json.Unmarshal(secret.Data["spec"], &actualSpec) + require.NoError(t, err) + + require.Equal(t, expectedSpec, actualSpec) + }) + + t.Run("update secret", func(t *testing.T) { + key := types.NamespacedName{ + Name: "test-name", + Namespace: testSecretNamespace, + } + ctx := context.TODO() + client := fake.NewClientBuilder().WithRuntimeObjects( + fixSecretCache(t, key, emptySpecManifest), + ).Build() + + cache := NewSecretManifestCache(client) + expectedSpec := DockerRegistrySpecManifest{ + Manifest: "schmetterling", + CustomFlags: map[string]interface{}{ + "flag1": "val1", + "flag2": "val2", + }, + } + err := cache.Set(ctx, key, expectedSpec) + require.NoError(t, err) + + var secret corev1.Secret + require.NoError(t, client.Get(ctx, key, &secret)) + + actualSpec := DockerRegistrySpecManifest{} + err = json.Unmarshal(secret.Data["spec"], &actualSpec) + require.NoError(t, err) + + require.Equal(t, expectedSpec, actualSpec) + }) + + t.Run("marshal error", func(t *testing.T) { + key := types.NamespacedName{ + Name: "test-name", + Namespace: testSecretNamespace, + } + ctx := context.TODO() + client := fake.NewClientBuilder().Build() + wrongFlags := map[string]interface{}{ + "flag1": func() {}, + } + + cache := NewSecretManifestCache(client) + + err := cache.Set(ctx, key, DockerRegistrySpecManifest{ + Manifest: "", + CustomFlags: wrongFlags, + }) + require.Error(t, err) + }) +} + +func fixSecretCache(t *testing.T, key types.NamespacedName, spec DockerRegistrySpecManifest) *corev1.Secret { + byteSpec, err := json.Marshal(&spec) + require.NoError(t, err) + + return &corev1.Secret{ + ObjectMeta: v1.ObjectMeta{ + Name: key.Name, + Namespace: key.Namespace, + }, + Data: map[string][]byte{ + "spec": byteSpec, + }, + } +} diff --git a/components/operator/internal/chart/chart.go b/components/operator/internal/chart/chart.go new file mode 100644 index 00000000..330d4d92 --- /dev/null +++ b/components/operator/internal/chart/chart.go @@ -0,0 +1,143 @@ +package chart + +import ( + "context" + "fmt" + "io" + "reflect" + "strings" + + "go.uber.org/zap" + "gopkg.in/yaml.v3" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/kube" + "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v3/pkg/storage" + "helm.sh/helm/v3/pkg/storage/driver" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type Config struct { + Ctx context.Context + Log *zap.SugaredLogger + Cache ManifestCache + CacheKey types.NamespacedName + ManagerUID string + Cluster Cluster + Release Release +} + +type Release struct { + ChartPath string + Name string + Namespace string +} + +type Cluster struct { + Client client.Client + Config *rest.Config +} + +func parseManifest(manifest string) ([]unstructured.Unstructured, error) { + results := make([]unstructured.Unstructured, 0) + decoder := yaml.NewDecoder(strings.NewReader(manifest)) + + for { + var obj map[string]interface{} + err := decoder.Decode(&obj) + + if err == io.EOF { + break + } + + if err != nil { + return nil, err + } + + // no obj between separators + if len(obj) == 0 { + continue + } + + u := unstructured.Unstructured{Object: obj} + // some resources need to be applied first (before workloads) + // if this statement gets bigger then extract it to the separated place + if u.GetObjectKind().GroupVersionKind().Kind == "CustomResourceDefinition" || + u.GetObjectKind().GroupVersionKind().Kind == "PriorityClass" { + results = append([]unstructured.Unstructured{u}, results...) + continue + } + results = append(results, u) + } + + return results, nil +} + +func getCachedAndCurrentManifest(config *Config, customFlags map[string]interface{}, renderChartFunc func(config *Config, customFlags map[string]interface{}) (*release.Release, error)) (string, string, error) { + cachedSpecManifest, err := config.Cache.Get(config.Ctx, config.CacheKey) + if err != nil { + return "", "", fmt.Errorf("could not get manifest from cache : %s", err.Error()) + } + + if !shouldRenderAgain(cachedSpecManifest, config, customFlags) { + return cachedSpecManifest.Manifest, cachedSpecManifest.Manifest, nil + } + + currentRelease, err := renderChartFunc(config, customFlags) + if err != nil { + return cachedSpecManifest.Manifest, "", fmt.Errorf("could not render manifest : %s", err.Error()) + } + + return cachedSpecManifest.Manifest, currentRelease.Manifest, nil +} + +func shouldRenderAgain(cachedSpec DockerRegistrySpecManifest, config *Config, customFlags map[string]interface{}) bool { + // cachedSpec is up-to-date only if flags used to render and manager is the same one who rendered it before + equalFlags := reflect.DeepEqual(cachedSpec.CustomFlags, customFlags) + return !(cachedSpec.ManagerUID == config.ManagerUID && equalFlags) +} + +func renderChart(config *Config, customFlags map[string]interface{}) (*release.Release, error) { + chart, err := loader.Load(config.Release.ChartPath) + if err != nil { + return nil, fmt.Errorf("while loading chart from path '%s': %s", config.Release.ChartPath, err.Error()) + } + + installAction := newInstallAction(config) + + rel, err := installAction.Run(chart, customFlags) + if err != nil { + return nil, fmt.Errorf("while templating chart: %s", err.Error()) + } + + return rel, nil +} + +func newInstallAction(config *Config) *action.Install { + helmRESTGetter := &clientGetter{ + config: config.Cluster.Config, + } + + helmClient := kube.New(helmRESTGetter) + helmClient.Log = config.Log.Debugf + + actionConfig := new(action.Configuration) + actionConfig.KubeClient = helmClient + actionConfig.Log = helmClient.Log + + actionConfig.Releases = storage.Init(driver.NewMemory()) + actionConfig.RESTClientGetter = helmRESTGetter + + action := action.NewInstall(actionConfig) + action.ReleaseName = config.Release.Name + action.Namespace = config.Release.Namespace + action.Replace = true + action.IsUpgrade = true + action.DryRun = true + + return action +} diff --git a/components/operator/internal/chart/chart_test.go b/components/operator/internal/chart/chart_test.go new file mode 100644 index 00000000..0d155da6 --- /dev/null +++ b/components/operator/internal/chart/chart_test.go @@ -0,0 +1,94 @@ +package chart + +import ( + "context" + "testing" + + "helm.sh/helm/v3/pkg/release" + "k8s.io/apimachinery/pkg/types" +) + +func Test_getOrRenderManifestWithRenderer(t *testing.T) { + noCRDManifestKey := types.NamespacedName{ + Name: "no", Namespace: "crd", + } + + cache := NewInMemoryManifestCache() + _ = cache.Set(context.Background(), noCRDManifestKey, + DockerRegistrySpecManifest{Manifest: testDeploy}) + + type args struct { + config *Config + customFlags map[string]interface{} + renderChartFunc func(config *Config, customFlags map[string]interface{}) (*release.Release, error) + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "return manifest when flags and managerUID are not changed", + args: args{ + config: &Config{ + Ctx: context.Background(), + Cache: cache, + CacheKey: noCRDManifestKey, + }, + }, + want: testDeploy, + wantErr: false, + }, + { + name: "render manifest when flags are changed", + args: args{ + renderChartFunc: fixManifestRenderFunc("test-new-manifest"), + customFlags: map[string]interface{}{ + "flag1": "val1", + }, + config: &Config{ + Ctx: context.Background(), + Cache: cache, + CacheKey: noCRDManifestKey, + }, + }, + want: "test-new-manifest", + wantErr: false, + }, + { + name: "render manifest when managerUID is changed", + args: args{ + renderChartFunc: fixManifestRenderFunc("test-new-manifest-2"), + config: &Config{ + Ctx: context.Background(), + Cache: cache, + CacheKey: noCRDManifestKey, + ManagerUID: "new-UID", + }, + }, + want: "test-new-manifest-2", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, gotCurrent, err := getCachedAndCurrentManifest(tt.args.config, tt.args.customFlags, tt.args.renderChartFunc) + if (err != nil) != tt.wantErr { + t.Errorf("getCachedAndCurrentManifest() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotCurrent != tt.want { + t.Errorf("getCachedAndCurrentManifest() = %v, want %v", gotCurrent, tt.want) + } + }) + } +} + +func fixManifestRenderFunc(manifest string) func(config *Config, customFlags map[string]interface{}) (*release.Release, error) { + return func(config *Config, customFlags map[string]interface{}) (*release.Release, error) { + return &release.Release{ + Manifest: manifest, + }, nil + } +} diff --git a/components/operator/internal/chart/check.go b/components/operator/internal/chart/check.go new file mode 100644 index 00000000..77436174 --- /dev/null +++ b/components/operator/internal/chart/check.go @@ -0,0 +1,94 @@ +package chart + +import ( + "fmt" + + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func CheckCRDOrphanResources(config *Config) error { + spec, err := config.Cache.Get(config.Ctx, config.CacheKey) + if err != nil { + return fmt.Errorf("could not render manifest from chart: %s", err.Error()) + } + + objs, err := parseManifest(spec.Manifest) + if err != nil { + return fmt.Errorf("could not parse chart manifest: %s", err.Error()) + } + + for _, obj := range objs { + // continue if obj is not crd + if !isCRD(obj) { + continue + } + + // check if crd exist on the cluster + objCopy := unstructured.Unstructured{Object: obj.Object} + err := config.Cluster.Client.Get(config.Ctx, types.NamespacedName{ + Name: obj.GetName(), + Namespace: obj.GetNamespace(), + }, &objCopy) + if errors.IsNotFound(err) { + continue + } + if err != nil { + return err + } + + // check if CRs exist on the cluster + crList, err := buildResourceListFromCRD(obj) + if err != nil { + return err + } + + err = config.Cluster.Client.List(config.Ctx, &crList) + if client.IgnoreNotFound(err) != nil { + return err + } + + if len(crList.Items) > 0 { + return fmt.Errorf("found %d items with VersionKind %s", len(crList.Items), crList.GetAPIVersion()) + } + } + + return nil +} + +func isCRD(u unstructured.Unstructured) bool { + return u.GroupVersionKind().GroupKind() == apiextensionsv1.Kind("CustomResourceDefinition") +} + +func buildResourceListFromCRD(u unstructured.Unstructured) (unstructured.UnstructuredList, error) { + crd := apiextensionsv1.CustomResourceDefinition{} + crdList := unstructured.UnstructuredList{} + + err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &crd) + if err != nil { + return crdList, err + } + + crdList.SetGroupVersionKind(schema.GroupVersionKind{ + Group: crd.Spec.Group, + Version: getCRDStoredVersion(crd), + Kind: crd.Spec.Names.Kind, + }) + + return crdList, nil +} + +func getCRDStoredVersion(crd apiextensionsv1.CustomResourceDefinition) string { + for _, version := range crd.Spec.Versions { + if version.Storage { + return version.Name + } + } + + return "" +} diff --git a/components/operator/internal/chart/check_test.go b/components/operator/internal/chart/check_test.go new file mode 100644 index 00000000..6b0e10d9 --- /dev/null +++ b/components/operator/internal/chart/check_test.go @@ -0,0 +1,179 @@ +package chart + +import ( + "context" + "fmt" + "github.com/stretchr/testify/require" + "testing" + + apiextensionsscheme "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +const ( + testOrphanCR = ` +apiVersion: test.group/v1alpha2 +kind: TestKind +metadata: + name: test-deploy + namespace: default +` +) + +var ( + testOrphanObj = unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "test.group/v1alpha2", + "kind": "TestKind", + "metadata": map[string]interface{}{ + "name": "test", + "namespace": "namespace", + }, + }, + } +) + +func TestCheckCRDOrphanResources(t *testing.T) { + noCRDManifestKey := types.NamespacedName{ + Name: "no", Namespace: "crd", + } + noOrphanManifestKey := types.NamespacedName{ + Name: "no", Namespace: "orphan", + } + oneOrphanManifestKey := types.NamespacedName{ + Name: "one", Namespace: "orphan", + } + emptyManifestKey := types.NamespacedName{ + Name: "empty", Namespace: "manifest", + } + wrongManifestKey := types.NamespacedName{ + Name: "wrong", Namespace: "manifest", + } + + cache := NewInMemoryManifestCache() + _ = cache.Set(context.Background(), noCRDManifestKey, + DockerRegistrySpecManifest{Manifest: fmt.Sprint(testDeploy)}) + _ = cache.Set(context.Background(), noOrphanManifestKey, + DockerRegistrySpecManifest{Manifest: fmt.Sprint(testCRD, separator, testDeploy)}) + _ = cache.Set(context.Background(), oneOrphanManifestKey, + DockerRegistrySpecManifest{Manifest: fmt.Sprint(testCRD, separator, testOrphanCR)}) + _ = cache.Set(context.Background(), emptyManifestKey, + DockerRegistrySpecManifest{Manifest: ""}) + _ = cache.Set(context.Background(), wrongManifestKey, + DockerRegistrySpecManifest{Manifest: "api: test\n\tversion: test"}) + + type args struct { + config *Config + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty manifest", + args: args{ + config: &Config{ + Cache: cache, + CacheKey: emptyManifestKey, + }, + }, + wantErr: false, + }, + { + name: "parse manifest error", + args: args{ + config: &Config{ + Cache: cache, + CacheKey: wrongManifestKey, + }, + }, + wantErr: true, + }, + { + name: "no CRDs in manifest", + args: args{ + config: &Config{ + Cache: cache, + CacheKey: noCRDManifestKey, + }, + }, + wantErr: false, + }, + { + name: "no orphan for CRD", + args: args{ + config: &Config{ + Cache: cache, + CacheKey: noOrphanManifestKey, + Ctx: context.Background(), + Cluster: Cluster{ + Client: fake.NewClientBuilder(). + WithScheme(apiextensionsscheme.Scheme). + WithObjects(testCRDObj). + Build(), + }, + }, + }, + wantErr: false, + }, + { + name: "one orphan for CRD", + args: args{ + config: &Config{ + Cache: cache, + CacheKey: oneOrphanManifestKey, + Ctx: context.Background(), + Cluster: Cluster{ + Client: func() client.Client { + scheme := runtime.NewScheme() + scheme.AddKnownTypes(schema.GroupVersion{ + Group: "test.group", + Version: "v1alpha2", + }, &testOrphanObj) + require.NoError(t, apiextensionsscheme.AddToScheme(scheme)) + c := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(&testOrphanObj). + WithObjects(testCRDObj). + Build() + return c + }(), + }, + }, + }, + wantErr: true, + }, + { + name: "missing CRD on cluster", + args: args{ + config: &Config{ + Cache: cache, + CacheKey: oneOrphanManifestKey, + Ctx: context.Background(), + Cluster: Cluster{ + Client: func() client.Client { + scheme := runtime.NewScheme() + require.NoError(t, apiextensionsscheme.AddToScheme(scheme)) + c := fake.NewClientBuilder().WithScheme(scheme).Build() + return c + }(), + }, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := CheckCRDOrphanResources(tt.args.config); (err != nil) != tt.wantErr { + t.Errorf("CheckCRDOrphanResources() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/components/operator/internal/chart/client_getter.go b/components/operator/internal/chart/client_getter.go new file mode 100644 index 00000000..969ae132 --- /dev/null +++ b/components/operator/internal/chart/client_getter.go @@ -0,0 +1,44 @@ +package chart + +import ( + "helm.sh/helm/v3/pkg/action" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/client-go/discovery" + "k8s.io/client-go/discovery/cached/memory" + "k8s.io/client-go/rest" + "k8s.io/client-go/restmapper" + "k8s.io/client-go/tools/clientcmd" +) + +var _ action.RESTClientGetter = &clientGetter{} + +type clientGetter struct { + config *rest.Config +} + +func (cg *clientGetter) ToRESTConfig() (*rest.Config, error) { + return cg.config, nil +} + +func (cg *clientGetter) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) { + discoveryClient, _ := discovery.NewDiscoveryClientForConfig(cg.config) + return memory.NewMemCacheClient(discoveryClient), nil +} + +func (cg *clientGetter) ToRESTMapper() (meta.RESTMapper, error) { + discoveryClient, err := cg.ToDiscoveryClient() + if err != nil { + return nil, err + } + + mapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient) + expander := restmapper.NewShortcutExpander(mapper, discoveryClient, nil) + return expander, nil +} + +func (cg *clientGetter) ToRawKubeConfigLoader() clientcmd.ClientConfig { + loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig + overrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmd.ClusterDefaults} + return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides) +} diff --git a/components/operator/internal/chart/flags.go b/components/operator/internal/chart/flags.go new file mode 100644 index 00000000..02b6d9bc --- /dev/null +++ b/components/operator/internal/chart/flags.go @@ -0,0 +1,94 @@ +package chart + +import ( + "fmt" + "strings" +) + +type FlagsBuilder interface { + Build() map[string]interface{} + WithControllerConfiguration(healthzLivenessTimeout string) *flagsBuilder + WithRegistryCredentials(username string, password string) *flagsBuilder + WithRegistryHttpSecret(httpSecret string) *flagsBuilder + WithNodePort(nodePort int64) *flagsBuilder +} + +type flagsBuilder struct { + flags map[string]interface{} +} + +func NewFlagsBuilder() FlagsBuilder { + return &flagsBuilder{ + flags: map[string]interface{}{}, + } +} + +func (fb *flagsBuilder) Build() map[string]interface{} { + flags := map[string]interface{}{} + for key, value := range fb.flags { + flagPath := strings.Split(key, ".") + appendFlag(flags, flagPath, value) + } + return flags +} + +func appendFlag(flags map[string]interface{}, flagPath []string, value interface{}) { + currentFlag := flags + for i, pathPart := range flagPath { + createIfEmpty(currentFlag, pathPart) + if lastElement(flagPath, i) { + currentFlag[pathPart] = value + } else { + currentFlag = nextDeeperFlag(currentFlag, pathPart) + } + } +} + +func createIfEmpty(flags map[string]interface{}, key string) { + if _, ok := flags[key]; !ok { + flags[key] = map[string]interface{}{} + } +} + +func lastElement(values []string, i int) bool { + return i == len(values)-1 +} + +func nextDeeperFlag(currentFlag map[string]interface{}, path string) map[string]interface{} { + return currentFlag[path].(map[string]interface{}) +} + +func (fb *flagsBuilder) WithControllerConfiguration(healthzLivenessTimeout string) *flagsBuilder { + optionalFlags := []struct { + key string + value string + }{ + {"healthzLivenessTimeout", healthzLivenessTimeout}, + } + + for _, flag := range optionalFlags { + if flag.value != "" { + fullPath := fmt.Sprintf("containers.manager.configuration.data.%s", flag.key) + fb.flags[fullPath] = flag.value + } + } + + return fb +} + +func (fb *flagsBuilder) WithRegistryCredentials(username, password string) *flagsBuilder { + fb.flags["dockerRegistry.username"] = username + fb.flags["dockerRegistry.password"] = password + return fb +} + +func (fb *flagsBuilder) WithRegistryHttpSecret(httpSecret string) *flagsBuilder { + fb.flags["docker-registry.rollme"] = "dontrollplease" + fb.flags["docker-registry.registryHTTPSecret"] = httpSecret + return fb +} + +func (fb *flagsBuilder) WithNodePort(nodePort int64) *flagsBuilder { + fb.flags["global.registryNodePort"] = nodePort + return fb +} diff --git a/components/operator/internal/chart/flags_test.go b/components/operator/internal/chart/flags_test.go new file mode 100644 index 00000000..bb8f5599 --- /dev/null +++ b/components/operator/internal/chart/flags_test.go @@ -0,0 +1,85 @@ +package chart + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_flagsBuilder_Build(t *testing.T) { + t.Run("build empty flags", func(t *testing.T) { + flags := NewFlagsBuilder().Build() + require.Equal(t, map[string]interface{}{}, flags) + }) + + t.Run("build flags", func(t *testing.T) { + expectedFlags := map[string]interface{}{ + "containers": map[string]interface{}{ + "manager": map[string]interface{}{ + "configuration": map[string]interface{}{ + "data": map[string]interface{}{ + "healthzLivenessTimeout": "testHealthzLivenessTimeout", + }, + }, + }, + }, + "docker-registry": map[string]interface{}{ + "registryHTTPSecret": "testHttpSecret", + "rollme": "dontrollplease", + }, + "dockerRegistry": map[string]interface{}{ + "password": "testPassword", + "username": "testUsername", + }, + "global": map[string]interface{}{ + "registryNodePort": int64(1234), + }, + } + + flags := NewFlagsBuilder(). + WithNodePort(1234). + WithRegistryCredentials("testUsername", "testPassword"). + WithRegistryHttpSecret("testHttpSecret"). + WithControllerConfiguration( + "testHealthzLivenessTimeout", + ).Build() + + require.Equal(t, expectedFlags, flags) + }) + + t.Run("build registry flags only", func(t *testing.T) { + expectedFlags := map[string]interface{}{ + "dockerRegistry": map[string]interface{}{ + "password": "testPassword", + "username": "testUsername", + }, + } + + flags := NewFlagsBuilder(). + WithRegistryCredentials("testUsername", "testPassword"). + Build() + + require.Equal(t, expectedFlags, flags) + }) + + t.Run("build not empty controller configuration flags only", func(t *testing.T) { + expectedFlags := map[string]interface{}{ + "containers": map[string]interface{}{ + "manager": map[string]interface{}{ + "configuration": map[string]interface{}{ + "data": map[string]interface{}{ + "healthzLivenessTimeout": "testHealthzLivenessTimeout", + }, + }, + }, + }, + } + + flags := NewFlagsBuilder(). + WithControllerConfiguration( + "testHealthzLivenessTimeout", + ).Build() + + require.Equal(t, expectedFlags, flags) + }) +} diff --git a/components/operator/internal/chart/install.go b/components/operator/internal/chart/install.go new file mode 100644 index 00000000..ee396dd4 --- /dev/null +++ b/components/operator/internal/chart/install.go @@ -0,0 +1,102 @@ +package chart + +import ( + "fmt" + + "github.com/kyma-project/docker-registry/components/operator/internal/annotation" + "github.com/pkg/errors" + "helm.sh/helm/v3/pkg/release" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func Install(config *Config, customFlags map[string]interface{}) error { + return install(config, customFlags, renderChart) +} + +func install(config *Config, customFlags map[string]interface{}, renderChartFunc func(config *Config, customFlags map[string]interface{}) (*release.Release, error)) error { + cachedManifest, currentManifest, err := getCachedAndCurrentManifest(config, customFlags, renderChartFunc) + if err != nil { + return err + } + + objs, unusedObjs, err := getObjectsToInstallAndRemove(cachedManifest, currentManifest) + if err != nil { + return err + } + + err = updateObjects(config, objs) + if err != nil { + return err + } + + err = uninstallObjects(config, unusedObjs) + if err != nil { + return err + } + + return config.Cache.Set(config.Ctx, config.CacheKey, DockerRegistrySpecManifest{ + ManagerUID: config.ManagerUID, + CustomFlags: customFlags, + Manifest: currentManifest, + }) +} + +func getObjectsToInstallAndRemove(cachedManifest string, currentManifest string) ([]unstructured.Unstructured, []unstructured.Unstructured, error) { + objs, err := parseManifest(currentManifest) + if err != nil { + return nil, nil, fmt.Errorf("could not parse chart manifest: %s", err.Error()) + } + + oldObjs, err := parseManifest(cachedManifest) + if err != nil { + return nil, nil, fmt.Errorf("could not parse chart manifest: %s", err.Error()) + } + + unusedObjs := unusedOldObjects(oldObjs, objs) + return objs, unusedObjs, nil +} + +func updateObjects(config *Config, objs []unstructured.Unstructured) error { + for i := range objs { + u := objs[i] + config.Log.Debugf("creating %s %s/%s", u.GetKind(), u.GetNamespace(), u.GetName()) + + u = annotation.AddDoNotEditDisclaimer(u) + if IsPVC(u.GroupVersionKind()) { + modifiedObj, err := AdjustDockerRegToClusterPVCSize(config.Ctx, config.Cluster.Client, u) + if err != nil { + return errors.Wrap(err, "while adjusting pvc size") + } + u = modifiedObj + } + + // TODO: what if Path returns error in the middle of manifest? + // maybe we should in this case translate applied objs into manifest and set it into cache? + err := config.Cluster.Client.Patch(config.Ctx, &u, client.Apply, &client.PatchOptions{ + Force: ptr.To[bool](true), + FieldManager: "dockerregistry-operator", + }) + if err != nil { + return fmt.Errorf("could not install object %s/%s: %s", u.GetNamespace(), u.GetName(), err.Error()) + } + } + return nil +} + +func unusedOldObjects(previousObjs []unstructured.Unstructured, currentObjs []unstructured.Unstructured) []unstructured.Unstructured { + currentNames := make(map[string]struct{}, len(currentObjs)) + for _, obj := range currentObjs { + objFullName := fmt.Sprintf("%s/%s/%s", obj.GetKind(), obj.GetNamespace(), obj.GetName()) + currentNames[objFullName] = struct{}{} + } + result := []unstructured.Unstructured{} + for _, obj := range previousObjs { + objFullName := fmt.Sprintf("%s/%s/%s", obj.GetKind(), obj.GetNamespace(), obj.GetName()) + if _, found := currentNames[objFullName]; !found { + result = append(result, obj) + } + } + return result +} diff --git a/components/operator/internal/chart/install_test.go b/components/operator/internal/chart/install_test.go new file mode 100644 index 00000000..377def8a --- /dev/null +++ b/components/operator/internal/chart/install_test.go @@ -0,0 +1,247 @@ +package chart + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/zap" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextensionsscheme "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +const ( + separator = `---` + testCRD = ` +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: test-crd +spec: + group: test.group + names: + kind: TestKind + versions: + - storage: false + name: v1alpha1 + - storage: true + name: v1alpha2 +` + testDeploy = ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-deploy + namespace: default +` + testServiceAccount = ` +apiVersion: v1 +kind: ServiceAccount +metadata: + name: test-service-account + namespace: test-namespace + labels: + label-key: 'label-val' +` +) + +var ( + testDeployCR = &appsv1.Deployment{ + ObjectMeta: v1.ObjectMeta{ + Name: "test-deploy", + Namespace: "default", + }, + Status: appsv1.DeploymentStatus{ + Conditions: []appsv1.DeploymentCondition{ + { + Type: appsv1.DeploymentAvailable, + Status: corev1.ConditionStatus(v1.ConditionTrue), + }, + }, + }, + } + testCRDObj = &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: v1.ObjectMeta{ + Name: "test-crd", + }, + } +) + +func Test_install_delete(t *testing.T) { + t.Run("should delete all unused resources", func(t *testing.T) { + testManifestKey := types.NamespacedName{ + Name: "test", Namespace: "testnamespace", + } + cache := NewInMemoryManifestCache() + _ = cache.Set(context.Background(), testManifestKey, + DockerRegistrySpecManifest{Manifest: fmt.Sprint(testCRD, separator, testDeploy)}) + client := fake.NewClientBuilder().WithObjects(testDeployCR).WithObjects(testCRDObj).Build() + customFlags := map[string]interface{}{ + "flag1": "val1", + } + config := &Config{ + Cache: cache, + CacheKey: testManifestKey, + Cluster: Cluster{ + Client: client, + }, + Log: zap.NewNop().Sugar(), + } + err := install(config, customFlags, fixManifestRenderFunc("")) + require.NoError(t, err) + + deploymentList := appsv1.DeploymentList{} + err = client.List(context.Background(), &deploymentList) + require.NoError(t, err) + require.Empty(t, deploymentList.Items) + + crdList := apiextensionsv1.CustomResourceDefinitionList{} + err = client.List(context.Background(), &crdList) + require.NoError(t, err) + require.Empty(t, crdList.Items) + }) +} + +func Test_install(t *testing.T) { + log := zap.NewNop().Sugar() + + testManifestKey := types.NamespacedName{ + Name: "test", Namespace: "testnamespace", + } + emptyManifestKey := types.NamespacedName{ + Name: "empty", Namespace: "manifest", + } + wrongManifestKey := types.NamespacedName{ + Name: "wrong", Namespace: "manifest", + } + + cache := NewInMemoryManifestCache() + _ = cache.Set(context.Background(), testManifestKey, + DockerRegistrySpecManifest{Manifest: fmt.Sprint(testCRD, separator, testDeploy)}) + _ = cache.Set(context.Background(), emptyManifestKey, + DockerRegistrySpecManifest{Manifest: ""}) + _ = cache.Set(context.Background(), wrongManifestKey, + DockerRegistrySpecManifest{Manifest: "api: test\n\tversion: test"}) + + type args struct { + config *Config + customFlags map[string]interface{} + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty manifest", + args: args{ + config: &Config{ + Cache: cache, + CacheKey: emptyManifestKey, + }, + }, + wantErr: false, + }, + { + name: "parse manifest error", + args: args{ + config: &Config{ + Cache: cache, + CacheKey: wrongManifestKey, + }, + }, + wantErr: true, + }, + { + name: "installation error", + args: args{ + config: &Config{ + Ctx: context.Background(), + Log: log, + Cache: cache, + CacheKey: testManifestKey, + Cluster: Cluster{ + Client: fake.NewClientBuilder().WithScheme(apiextensionsscheme.Scheme).Build(), + }, + }, + }, + wantErr: true, + }, + // we can't simply test succeded installation here because it uses + // tha Patch method which is not fully supported by the fake client. This case is tested in controllers pkg + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := Install(tt.args.config, tt.args.customFlags); (err != nil) != tt.wantErr { + t.Errorf("install() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_unusedOldObjects(t *testing.T) { + firstManifest := fmt.Sprint(testCRD, separator, testDeploy) + firstObjs, _ := parseManifest(firstManifest) + differentManifest := fmt.Sprint(testServiceAccount) + differentObjs, _ := parseManifest(differentManifest) + withCommonPartManifest := fmt.Sprint(testServiceAccount, separator, testDeploy) + withCommonPartObjs, _ := parseManifest(withCommonPartManifest) + firstWithoutCommonPartManifest := fmt.Sprint(testCRD) + firstWithoutCommonPartObjs, _ := parseManifest(firstWithoutCommonPartManifest) + + type args struct { + old []unstructured.Unstructured + new []unstructured.Unstructured + } + tests := []struct { + name string + args args + want []unstructured.Unstructured + }{ + { + name: "empty minus empty should be empty", + args: args{ + old: []unstructured.Unstructured{}, + new: []unstructured.Unstructured{}, + }, + want: []unstructured.Unstructured{}, + }, + { + name: "list minus empty should return the same list", + args: args{ + old: firstObjs, + new: []unstructured.Unstructured{}, + }, + want: firstObjs, + }, + { + name: "list minus list with different elements should return first list", + args: args{ + old: firstObjs, + new: differentObjs, + }, + want: firstObjs, + }, + { + name: "list minus list with common part should return first list without common part", + args: args{ + old: firstObjs, + new: withCommonPartObjs, + }, + want: firstWithoutCommonPartObjs, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := unusedOldObjects(tt.args.old, tt.args.new) + require.Equal(t, tt.want, got) + }) + } +} diff --git a/components/operator/internal/chart/pvc.go b/components/operator/internal/chart/pvc.go new file mode 100644 index 00000000..f178c9d8 --- /dev/null +++ b/components/operator/internal/chart/pvc.go @@ -0,0 +1,64 @@ +package chart + +import ( + "context" + + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + dockerRegistryPVCName = "internal-docker-registry" + pvcKind = "PersistentVolumeClaim" + pvcVersion = "v1" + pvcGroup = "" +) + +func AdjustDockerRegToClusterPVCSize(ctx context.Context, c client.Client, obj unstructured.Unstructured) (unstructured.Unstructured, error) { + if obj.GetName() != dockerRegistryPVCName { + return obj, nil + } + clusterPVC := corev1.PersistentVolumeClaim{} + objKey := client.ObjectKey{ + Namespace: obj.GetNamespace(), + Name: obj.GetName(), + } + if err := c.Get(ctx, objKey, &clusterPVC); err != nil { + if k8serrors.IsNotFound(err) { + return obj, nil + } + return obj, errors.Wrap(err, "while getting pvc from cluster") + } + objPVC := corev1.PersistentVolumeClaim{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &objPVC); err != nil { + return obj, errors.Wrap(err, "while converting unstructured to pvc") + } + storage := clusterPVC.Spec.Resources.Requests.Storage() + if storage.Equal(*objPVC.Spec.Resources.Requests.Storage()) { + return obj, nil + } + objPVCcopy := objPVC.DeepCopy() + objPVCcopy.Spec.Resources.Requests[corev1.ResourceStorage] = *clusterPVC.Spec.Resources.Requests.Storage() + + out, err := runtime.DefaultUnstructuredConverter.ToUnstructured(objPVCcopy) + if err != nil { + return obj, errors.Wrap(err, "while converting copied pvc object to unstructured") + } + + return unstructured.Unstructured{Object: out}, nil +} + +func IsPVC(objKind schema.GroupVersionKind) bool { + expected := schema.GroupVersionKind{ + Group: pvcGroup, + Version: pvcVersion, + Kind: pvcKind, + } + + return expected.Group == objKind.Group && expected.Kind == objKind.Kind && expected.Version == objKind.Version +} diff --git a/components/operator/internal/chart/pvc_test.go b/components/operator/internal/chart/pvc_test.go new file mode 100644 index 00000000..526d5a53 --- /dev/null +++ b/components/operator/internal/chart/pvc_test.go @@ -0,0 +1,133 @@ +package chart + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func TestAdjustToClusterSize(t *testing.T) { + testCases := map[string]struct { + rawPVCToInstall *corev1.PersistentVolumeClaim + clusterPVC []client.Object + expectedPVC *corev1.PersistentVolumeClaim + }{ + "pvc not exists in cluster": { + rawPVCToInstall: fixPVC(dockerRegistryPVCName, 20), + expectedPVC: fixPVC(dockerRegistryPVCName, 20), + }, + "pvc is not docker registry": { + rawPVCToInstall: fixPVC("random-pvc", 20), + expectedPVC: fixPVC("random-pvc", 20), + }, + "pvc exists with the same size": { + rawPVCToInstall: fixPVC(dockerRegistryPVCName, 20), + clusterPVC: []client.Object{fixPVC(dockerRegistryPVCName, 20)}, + expectedPVC: fixPVC(dockerRegistryPVCName, 20), + }, + "pvc exists with bigger size": { + rawPVCToInstall: fixPVC(dockerRegistryPVCName, 20), + clusterPVC: []client.Object{fixPVC(dockerRegistryPVCName, 30)}, + expectedPVC: fixPVC(dockerRegistryPVCName, 30), + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + //GIVEN + out, err := runtime.DefaultUnstructuredConverter.ToUnstructured(testCase.rawPVCToInstall) + require.NoError(t, err) + obj := unstructured.Unstructured{Object: out} + + c := fake.NewClientBuilder().WithObjects(testCase.clusterPVC...).Build() + + //WHEN + finalObj, err := AdjustDockerRegToClusterPVCSize(context.TODO(), c, obj) + + //THEN + require.NoError(t, err) + + expected, err := runtime.DefaultUnstructuredConverter.ToUnstructured(testCase.expectedPVC) + + require.NoError(t, err) + require.EqualValues(t, expected, finalObj.Object) + }) + } +} + +func fixPVC(name string, size int) *corev1.PersistentVolumeClaim { + return &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "kyma-system", + }, + Spec: corev1.PersistentVolumeClaimSpec{ + Resources: corev1.VolumeResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: resource.MustParse(fmt.Sprintf("%dGi", size)), + }, + }, + }, + } +} + +func TestIsPVC(t *testing.T) { + testCases := map[string]struct { + input schema.GroupVersionKind + expected bool + }{ + "Equal": { + input: schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "PersistentVolumeClaim", + }, + expected: true, + }, + "Different kind": { + input: schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "Pod", + }, + expected: false, + }, + "Different version": { + input: schema.GroupVersionKind{ + Group: "", + Version: "v2alpha1", + Kind: "PersistentVolumeClaim", + }, + expected: false, + }, + "Different group": { + input: schema.GroupVersionKind{ + Group: "networking", + Version: "v1", + Kind: "NetworkPolicy", + }, + expected: false, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + //GIVEN + + //WHEN + equal := IsPVC(testCase.input) + //THEN + require.Equal(t, testCase.expected, equal) + }) + } +} diff --git a/components/operator/internal/chart/uninstall.go b/components/operator/internal/chart/uninstall.go new file mode 100644 index 00000000..270694df --- /dev/null +++ b/components/operator/internal/chart/uninstall.go @@ -0,0 +1,160 @@ +package chart + +import ( + "fmt" + + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type FilterFunc func(unstructured.Unstructured) bool + +func Uninstall(config *Config, filterFunc ...FilterFunc) error { + spec, err := config.Cache.Get(config.Ctx, config.CacheKey) + if err != nil { + return fmt.Errorf("could not render manifest from chart: %s", err.Error()) + } + + objs, err := parseManifest(spec.Manifest) + if err != nil { + return fmt.Errorf("could not parse chart manifest: %s", err.Error()) + } + + err2 := uninstallObjects(config, objs, filterFunc...) + if err2 != nil { + return err2 + } + + err3 := uninstallOrphanedResources(config) + if err3 != nil { + return err3 + } + + return config.Cache.Delete(config.Ctx, config.CacheKey) +} + +func uninstallObjects(config *Config, objs []unstructured.Unstructured, filterFunc ...FilterFunc) error { + for i := range objs { + u := objs[i] + if !fitToFilters(u, filterFunc...) { + continue + } + + config.Log.Debugf("deleting %s %s", u.GetKind(), u.GetName()) + err := config.Cluster.Client.Delete(config.Ctx, &u) + if k8serrors.IsNotFound(err) { + config.Log.Debugf("deletion skipped for %s %s", u.GetKind(), u.GetName()) + continue + } + if err != nil { + return fmt.Errorf("could not uninstall object %s/%s: %s", u.GetNamespace(), u.GetName(), err.Error()) + } + } + return nil +} + +func UninstallSecrets(config *Config, filterFunc ...FilterFunc) (error, bool) { + spec, err := config.Cache.Get(config.Ctx, config.CacheKey) + if err != nil { + return fmt.Errorf("could not render manifest from chart: %s", err.Error()), false + } + + objs, err := parseManifest(spec.Manifest) + if err != nil { + return fmt.Errorf("could not parse chart manifest: %s", err.Error()), false + } + + err2, done := uninstallSecrets(config, objs, filterFunc...) + if err2 != nil { + return err2, false + } + + return nil, done +} + +func uninstallSecrets(config *Config, objs []unstructured.Unstructured, filterFunc ...FilterFunc) (error, bool) { + done := true + for i := range objs { + u := objs[i] + if !fitToFilters(u, filterFunc...) { + continue + } + if u.GetKind() != "Secret" { + continue + } + + config.Log.Debugf("deleting %s %s", u.GetKind(), u.GetName()) + err := config.Cluster.Client.Delete(config.Ctx, &u) + if k8serrors.IsNotFound(err) { + config.Log.Debugf("deletion skipped for %s %s", u.GetKind(), u.GetName()) + continue + } + if err != nil { + return fmt.Errorf("could not uninstall object %s/%s: %s", u.GetNamespace(), u.GetName(), err.Error()), false + } + done = false + } + return nil, done +} + +func WithoutCRDFilter(u unstructured.Unstructured) bool { + return !isCRD(u) +} + +func fitToFilters(u unstructured.Unstructured, filterFunc ...FilterFunc) bool { + for _, fn := range filterFunc { + if !fn(u) { + return false + } + } + + return true +} + +func uninstallOrphanedResources(config *Config) error { + //TODO: move this to finalizers logic in controller + var namespaces corev1.NamespaceList + if err := config.Cluster.Client.List(config.Ctx, &namespaces); err != nil { + return errors.Wrap(err, "couldn't get namespaces during Docker Registry uninstallation") + } + + if err := uninstallOrphanedConfigMaps(config, namespaces); err != nil { + return err + } + if err := uninstallOrphanedServiceAccounts(config, namespaces); err != nil { + return err + } + + return nil +} + +func uninstallOrphanedServiceAccounts(config *Config, namespaces corev1.NamespaceList) error { + for _, namespace := range namespaces.Items { + err := config.Cluster.Client.DeleteAllOf(config.Ctx, &corev1.ServiceAccount{}, + client.InNamespace(namespace.GetName()), + client.MatchingLabels{"dockerregistry.kyma-project.io/config": "service-account"}) + if err != nil { + return errors.Wrapf(err, + "couldn't delete ServiceAccount from namespace \"%s\" during DockerRegistry uninstallation", + namespace.GetName()) + } + } + return nil +} + +func uninstallOrphanedConfigMaps(config *Config, namespaces corev1.NamespaceList) error { + for _, namespace := range namespaces.Items { + err := config.Cluster.Client.DeleteAllOf(config.Ctx, &corev1.ConfigMap{}, + client.InNamespace(namespace.GetName()), + client.MatchingLabels{"dockerregistry.kyma-project.io/config": "runtime"}) + if err != nil { + return errors.Wrapf(err, + "couldn't delete ConfigMap from namespace \"%s\" during Docker Registry uninstallation", + namespace.GetName()) + } + } + return nil +} diff --git a/components/operator/internal/chart/uninstall_test.go b/components/operator/internal/chart/uninstall_test.go new file mode 100644 index 00000000..17b3c827 --- /dev/null +++ b/components/operator/internal/chart/uninstall_test.go @@ -0,0 +1,99 @@ +package chart + +import ( + "context" + "fmt" + "testing" + + "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func Test_Uninstall(t *testing.T) { + log := zap.NewNop().Sugar() + + testManifestKey := types.NamespacedName{ + Name: "test", Namespace: "testnamespace", + } + emptyManifestKey := types.NamespacedName{ + Name: "empty", Namespace: "manifest", + } + wrongManifestKey := types.NamespacedName{ + Name: "wrong", Namespace: "manifest", + } + + cache := NewInMemoryManifestCache() + _ = cache.Set(context.Background(), testManifestKey, + DockerRegistrySpecManifest{Manifest: fmt.Sprint(testCRD, separator, testDeploy)}) + _ = cache.Set(context.Background(), emptyManifestKey, + DockerRegistrySpecManifest{Manifest: ""}) + _ = cache.Set(context.Background(), wrongManifestKey, + DockerRegistrySpecManifest{Manifest: "api: test\n\tversion: test"}) + + ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace"}} + + type args struct { + config *Config + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty manifest", + args: args{ + config: &Config{ + Cache: cache, + CacheKey: emptyManifestKey, + Cluster: Cluster{ + Client: fake.NewClientBuilder(). + WithScheme(scheme.Scheme). + WithObjects(&ns). + Build(), + }, + }, + }, + wantErr: false, + }, + { + name: "parse manifest error", + args: args{ + config: &Config{ + Cache: cache, + CacheKey: wrongManifestKey, + }, + }, + wantErr: true, + }, + { + name: "installation error", + args: args{ + config: &Config{ + Ctx: context.Background(), + Log: log, + Cache: cache, + CacheKey: testManifestKey, + Cluster: Cluster{ + Client: fake.NewClientBuilder(). + WithScheme(scheme.Scheme). + WithObjects(&ns). + Build(), + }, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := Uninstall(tt.args.config); (err != nil) != tt.wantErr { + t.Errorf("uninstall() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/components/operator/internal/chart/verify.go b/components/operator/internal/chart/verify.go new file mode 100644 index 00000000..bf92f7c4 --- /dev/null +++ b/components/operator/internal/chart/verify.go @@ -0,0 +1,72 @@ +package chart + +import ( + "fmt" + + appsv1 "k8s.io/api/apps/v1" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/types" +) + +func Verify(config *Config) (bool, error) { + spec, err := config.Cache.Get(config.Ctx, config.CacheKey) + if err != nil { + return false, fmt.Errorf("could not render manifest from chart: %s", err.Error()) + } + // sometimes cache is not created yet + if len(spec.Manifest) == 0 { + return false, nil + } + + objs, err := parseManifest(spec.Manifest) + if err != nil { + return false, fmt.Errorf("could not parse chart manifest: %s", err.Error()) + } + + for i := range objs { + u := objs[i] + + var verifyFunc verifyFunc + switch u.GetKind() { + case "Deployment": + verifyFunc = verifyDeployment + case "DaemonSet": + // TODO: right now we don't support internal docker registry + default: + continue + } + + ready, err := verifyFunc(config, u) + if err != nil { + return false, fmt.Errorf("could not verify object %s/%s: %s", u.GetNamespace(), u.GetName(), err.Error()) + } + + if !ready { + return false, nil + } + } + + return true, nil +} + +type verifyFunc func(*Config, unstructured.Unstructured) (bool, error) + +func verifyDeployment(config *Config, u unstructured.Unstructured) (bool, error) { + var deployment appsv1.Deployment + err := config.Cluster.Client.Get(config.Ctx, types.NamespacedName{ + Name: u.GetName(), + Namespace: u.GetNamespace(), + }, &deployment) + if err != nil { + return false, err + } + + for _, cond := range deployment.Status.Conditions { + if cond.Type == appsv1.DeploymentAvailable && cond.Status == v1.ConditionTrue { + return true, nil + } + } + + return false, nil +} diff --git a/components/operator/internal/chart/verify_test.go b/components/operator/internal/chart/verify_test.go new file mode 100644 index 00000000..bd54fd37 --- /dev/null +++ b/components/operator/internal/chart/verify_test.go @@ -0,0 +1,146 @@ +package chart + +import ( + "context" + "fmt" + "testing" + + "go.uber.org/zap" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +var ( + testDeployNotReadyCR = &appsv1.Deployment{ + ObjectMeta: v1.ObjectMeta{ + Name: "test-deploy", + Namespace: "default", + }, + Status: appsv1.DeploymentStatus{ + Conditions: []appsv1.DeploymentCondition{ + { + Type: appsv1.DeploymentAvailable, + Status: corev1.ConditionStatus(v1.ConditionFalse), + }, + }, + }, + } +) + +func Test_verify(t *testing.T) { + log := zap.NewNop().Sugar() + + testManifestKey := types.NamespacedName{ + Name: "test", Namespace: "testnamespace", + } + emptyManifestKey := types.NamespacedName{ + Name: "empty", Namespace: "manifest", + } + wrongManifestKey := types.NamespacedName{ + Name: "wrong", Namespace: "manifest", + } + + cache := NewInMemoryManifestCache() + _ = cache.Set(context.Background(), testManifestKey, + DockerRegistrySpecManifest{Manifest: fmt.Sprint(testCRD, separator, testDeploy)}) + _ = cache.Set(context.Background(), emptyManifestKey, + DockerRegistrySpecManifest{Manifest: "---"}) + _ = cache.Set(context.Background(), wrongManifestKey, + DockerRegistrySpecManifest{Manifest: "api: test\n\tversion: test"}) + + type args struct { + config *Config + } + tests := []struct { + name string + args args + want bool + wantErr bool + }{ + { + name: "empty manifest", + args: args{ + config: &Config{ + Cache: cache, + CacheKey: emptyManifestKey, + }, + }, + want: true, + wantErr: false, + }, + { + name: "parse manifest error", + args: args{ + config: &Config{ + Cache: cache, + CacheKey: wrongManifestKey, + }, + }, + want: false, + wantErr: true, + }, + { + name: "verify", + args: args{ + config: &Config{ + Ctx: context.Background(), + Log: log, + Cache: cache, + CacheKey: testManifestKey, + Cluster: Cluster{ + Client: fake.NewClientBuilder().WithObjects(testDeployCR).Build(), + }, + }, + }, + want: true, + wantErr: false, + }, + { + name: "obj not ready", + args: args{ + config: &Config{ + Ctx: context.Background(), + Log: log, + Cache: cache, + CacheKey: testManifestKey, + Cluster: Cluster{ + Client: fake.NewClientBuilder().WithObjects(testDeployNotReadyCR).Build(), + }, + }, + }, + want: false, + wantErr: false, + }, + { + name: "obj not found", + args: args{ + config: &Config{ + Ctx: context.Background(), + Log: log, + Cache: cache, + CacheKey: testManifestKey, + Cluster: Cluster{ + Client: fake.NewClientBuilder().Build(), + }, + }, + }, + want: false, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Verify(tt.args.config) + if (err != nil) != tt.wantErr { + t.Errorf("verify() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("verify() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/components/operator/internal/config/config.go b/components/operator/internal/config/config.go new file mode 100644 index 00000000..f298d373 --- /dev/null +++ b/components/operator/internal/config/config.go @@ -0,0 +1,14 @@ +package config + +import "github.com/vrischmann/envconfig" + +type Config struct { + ChartPath string `envconfig:"default=/module-chart"` +} + +func GetConfig(prefix string) (Config, error) { + cfg := Config{} + err := envconfig.InitWithPrefix(&cfg, prefix) + return cfg, err + +} diff --git a/components/operator/internal/controllers/kubernetes/configmap_service.go b/components/operator/internal/controllers/kubernetes/configmap_service.go new file mode 100644 index 00000000..36acf569 --- /dev/null +++ b/components/operator/internal/controllers/kubernetes/configmap_service.go @@ -0,0 +1,98 @@ +package kubernetes + +import ( + "context" + "fmt" + + "go.uber.org/zap" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/kyma-project/docker-registry/components/operator/internal/resource" +) + +type ConfigMapService interface { + IsBase(configMap *corev1.ConfigMap) bool + ListBase(ctx context.Context) ([]corev1.ConfigMap, error) + UpdateNamespace(ctx context.Context, logger *zap.SugaredLogger, namespace string, baseInstance *corev1.ConfigMap) error +} + +var _ ConfigMapService = &configMapService{} + +type configMapService struct { + client resource.Client + config Config +} + +func NewConfigMapService(client resource.Client, config Config) ConfigMapService { + return &configMapService{ + client: client, + config: config, + } +} + +func (r *configMapService) ListBase(ctx context.Context) ([]corev1.ConfigMap, error) { + configMaps := corev1.ConfigMapList{} + if err := r.client.ListByLabel(ctx, r.config.BaseNamespace, map[string]string{ConfigLabel: RuntimeLabelValue}, &configMaps); err != nil { + return nil, err + } + + return configMaps.Items, nil +} + +func (r *configMapService) IsBase(configMap *corev1.ConfigMap) bool { + return configMap.Namespace == r.config.BaseNamespace && configMap.Labels[ConfigLabel] == RuntimeLabelValue +} + +func (r *configMapService) UpdateNamespace(ctx context.Context, logger *zap.SugaredLogger, namespace string, baseInstance *corev1.ConfigMap) error { + logger.Debug(fmt.Sprintf("Updating ConfigMap '%s/%s'", namespace, baseInstance.GetName())) + instance := &corev1.ConfigMap{} + if err := r.client.Get(ctx, client.ObjectKey{Namespace: namespace, Name: baseInstance.GetName()}, instance); err != nil { + if errors.IsNotFound(err) { + return r.createConfigMap(ctx, logger, namespace, baseInstance) + } + logger.Error(err, fmt.Sprintf("Gathering existing ConfigMap '%s/%s' failed", namespace, baseInstance.GetName())) + return err + } + + return r.updateConfigMap(ctx, logger, instance, baseInstance) +} + +func (r *configMapService) createConfigMap(ctx context.Context, logger *zap.SugaredLogger, namespace string, baseInstance *corev1.ConfigMap) error { + configMap := corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: baseInstance.GetName(), + Namespace: namespace, + Labels: baseInstance.Labels, + Annotations: baseInstance.Annotations, + }, + Data: baseInstance.Data, + BinaryData: baseInstance.BinaryData, + } + + logger.Debug(fmt.Sprintf("Creating ConfigMap '%s/%s'", configMap.GetNamespace(), configMap.GetName())) + if err := r.client.Create(ctx, &configMap); err != nil { + logger.Error(err, fmt.Sprintf("Creating ConfigMap '%s/%s' failed", configMap.GetNamespace(), configMap.GetName())) + return err + } + + return nil +} + +func (r *configMapService) updateConfigMap(ctx context.Context, logger *zap.SugaredLogger, instance, baseInstance *corev1.ConfigMap) error { + copy := instance.DeepCopy() + copy.Annotations = baseInstance.GetAnnotations() + copy.Labels = baseInstance.GetLabels() + copy.Data = baseInstance.Data + copy.BinaryData = baseInstance.BinaryData + + if err := r.client.Update(ctx, copy); err != nil { + logger.Error(err, fmt.Sprintf("Updating ConfigMap '%s/%s' failed", copy.GetNamespace(), copy.GetName())) + return err + } + + return nil +} diff --git a/components/operator/internal/controllers/kubernetes/namespace_controller.go b/components/operator/internal/controllers/kubernetes/namespace_controller.go new file mode 100644 index 00000000..2a53941f --- /dev/null +++ b/components/operator/internal/controllers/kubernetes/namespace_controller.go @@ -0,0 +1,116 @@ +package kubernetes + +import ( + "context" + "fmt" + + "go.uber.org/zap" + + corev1 "k8s.io/api/core/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +type NamespaceReconciler struct { + Log *zap.SugaredLogger + client client.Client + config Config + configMapSvc ConfigMapService + secretSvc SecretService + serviceAccountSvc ServiceAccountService +} + +func NewNamespace(client client.Client, log *zap.SugaredLogger, config Config, + configMapSvc ConfigMapService, secretSvc SecretService, serviceAccountSvc ServiceAccountService) *NamespaceReconciler { + return &NamespaceReconciler{ + client: client, + Log: log, + config: config, + configMapSvc: configMapSvc, + secretSvc: secretSvc, + serviceAccountSvc: serviceAccountSvc, + } +} + +func (r *NamespaceReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + Named("namespace-controller"). + For(&corev1.Namespace{}). + WithEventFilter(r.predicate()). + Complete(r) +} + +func (r *NamespaceReconciler) predicate() predicate.Predicate { + return predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + namespace, ok := e.Object.(*corev1.Namespace) + if !ok { + return false + } + return !isExcludedNamespace(namespace.Name, r.config.BaseNamespace, r.config.ExcludedNamespaces) + }, + GenericFunc: func(genericEvent event.GenericEvent) bool { + return false + }, + UpdateFunc: func(e event.UpdateEvent) bool { + return false + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return false + }, + } +} + +// Reconcile reads that state of the cluster for a Namespace object and updates other resources based on it +// +kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch +// +kubebuilder:rbac:groups="",resources=configmaps;secrets;serviceaccounts,verbs=get;list;watch;create;update;patch;delete + +func (r *NamespaceReconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) { + instance := &corev1.Namespace{} + if err := r.client.Get(ctx, request.NamespacedName, instance); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + logger := r.Log.With("name", instance.GetName()) + + logger.Debug(fmt.Sprintf("Updating ConfigMaps in namespace '%s'", instance.GetName())) + configMaps, err := r.configMapSvc.ListBase(ctx) + if err != nil { + logger.Error(err, "Listing base ConfigMaps failed") + return ctrl.Result{}, err + } + for _, configMap := range configMaps { + c := configMap + if err := r.configMapSvc.UpdateNamespace(ctx, logger, instance.GetName(), &c); err != nil { + return ctrl.Result{}, err + } + } + + logger.Debug(fmt.Sprintf("Updating Secret in namespace '%s'", instance.GetName())) + secret, err := r.secretSvc.GetBase(ctx) + if err != nil { + logger.Error(err, "Listing base Secrets failed") + return ctrl.Result{}, err + } + + if err := r.secretSvc.UpdateNamespace(ctx, logger, instance.GetName(), secret); err != nil { + return ctrl.Result{}, err + } + + logger.Debug(fmt.Sprintf("Updating ServiceAccounts in namespace '%s'", instance.GetName())) + serviceAccounts, err := r.serviceAccountSvc.ListBase(ctx) + if err != nil { + logger.Error(err, "Listing base ServiceAccounts failed") + return ctrl.Result{}, err + } + for _, serviceAccount := range serviceAccounts { + sa := serviceAccount + if err := r.serviceAccountSvc.UpdateNamespace(ctx, logger, instance.GetName(), &sa); err != nil { + return ctrl.Result{}, err + } + } + + return ctrl.Result{}, nil +} diff --git a/components/operator/internal/controllers/kubernetes/secret_controller.go b/components/operator/internal/controllers/kubernetes/secret_controller.go new file mode 100644 index 00000000..006eddfd --- /dev/null +++ b/components/operator/internal/controllers/kubernetes/secret_controller.go @@ -0,0 +1,103 @@ +package kubernetes + +import ( + "context" + + "go.uber.org/zap" + + corev1 "k8s.io/api/core/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +type SecretReconciler struct { + Log *zap.SugaredLogger + client client.Client + config Config + svc SecretService +} + +func NewSecret(client client.Client, log *zap.SugaredLogger, config Config, secretSvc SecretService) *SecretReconciler { + return &SecretReconciler{ + client: client, + Log: log, + config: config, + svc: secretSvc, + } +} + +func (r *SecretReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + Named("secret-controller"). + For(&corev1.Secret{}). + WithEventFilter(r.predicate()). + Complete(r) +} + +func (r *SecretReconciler) predicate() predicate.Predicate { + return predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + runtime, ok := e.Object.(*corev1.Secret) + if !ok { + return false + } + return r.svc.IsBase(runtime) + }, + UpdateFunc: func(e event.UpdateEvent) bool { + runtime, ok := e.ObjectNew.(*corev1.Secret) + if !ok { + return false + } + return r.svc.IsBase(runtime) + }, + GenericFunc: func(e event.GenericEvent) bool { + runtime, ok := e.Object.(*corev1.Secret) + if !ok { + return false + } + return r.svc.IsBase(runtime) + }, + DeleteFunc: func(e event.DeleteEvent) bool { + runtime, ok := e.Object.(*corev1.Secret) + if !ok { + return false + } + return r.svc.IsBase(runtime) + }, + } +} + +// Reconcile reads that state of the cluster for a Secret object and makes changes based +// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch + +func (r *SecretReconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) { + instance := &corev1.Secret{} + if err := r.client.Get(ctx, request.NamespacedName, instance); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + logger := r.Log.With("namespace", instance.GetNamespace(), "name", instance.GetName()) + + namespaces, err := getNamespaces(ctx, r.client, r.config.BaseNamespace, r.config.ExcludedNamespaces) + if err != nil { + return ctrl.Result{}, err + } + + if err := r.svc.HandleFinalizer(ctx, logger, instance, namespaces); err != nil { + return ctrl.Result{}, err + } + if !instance.ObjectMeta.DeletionTimestamp.IsZero() { + return ctrl.Result{}, nil + } + + for _, namespace := range namespaces { + if err = r.svc.UpdateNamespace(ctx, logger, namespace, instance); err != nil { + return ctrl.Result{}, err + } + } + + return ctrl.Result{RequeueAfter: r.config.SecretRequeueDuration}, nil +} diff --git a/components/operator/internal/controllers/kubernetes/secret_service.go b/components/operator/internal/controllers/kubernetes/secret_service.go new file mode 100644 index 00000000..af4e347d --- /dev/null +++ b/components/operator/internal/controllers/kubernetes/secret_service.go @@ -0,0 +1,175 @@ +package kubernetes + +import ( + "context" + "fmt" + "go.uber.org/zap" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/kyma-project/docker-registry/components/operator/internal/resource" +) + +const ( + FunctionManagedByLabel = "dockerregistry.kyma-project.io/managed-by" + cfgSecretFinalizerName = "dockerregistry.kyma-project.io/finalizer-registry-config" + FunctionResourceLabelUserValue = "user" +) + +type SecretService interface { + IsBase(secret *corev1.Secret) bool + GetBase(ctx context.Context) (*corev1.Secret, error) + UpdateNamespace(ctx context.Context, logger *zap.SugaredLogger, namespace string, baseInstance *corev1.Secret) error + HandleFinalizer(ctx context.Context, logger *zap.SugaredLogger, secret *corev1.Secret, namespaces []string) error +} + +var _ SecretService = &secretService{} + +type secretService struct { + client resource.Client + config Config +} + +func NewSecretService(client resource.Client, config Config) SecretService { + return &secretService{ + client: client, + config: config, + } +} + +func (r *secretService) GetBase(ctx context.Context) (*corev1.Secret, error) { + secret := &corev1.Secret{} + err := r.client.Get(ctx, types.NamespacedName{ + Namespace: r.config.BaseNamespace, + Name: r.config.BaseDefaultSecretName, + }, secret) + + return secret, err +} + +func (r *secretService) IsBase(secret *corev1.Secret) bool { + return secret.Namespace == r.config.BaseNamespace && + secret.Name == r.config.BaseDefaultSecretName && + secret.Labels[ConfigLabel] == CredentialsLabelValue +} + +func (r *secretService) UpdateNamespace(ctx context.Context, logger *zap.SugaredLogger, namespace string, baseInstance *corev1.Secret) error { + logger.Debug(fmt.Sprintf("Updating Secret '%s/%s'", namespace, baseInstance.GetName())) + instance := &corev1.Secret{} + if err := r.client.Get(ctx, client.ObjectKey{Namespace: namespace, Name: baseInstance.GetName()}, instance); err != nil { + if errors.IsNotFound(err) { + return r.createSecret(ctx, logger, namespace, baseInstance) + } + logger.Error(err, fmt.Sprintf("Gathering existing Secret '%s/%s' failed", namespace, baseInstance.GetName())) + return err + } + if instance.Labels[FunctionManagedByLabel] == FunctionResourceLabelUserValue { + return nil + } + return r.updateSecret(ctx, logger, instance, baseInstance) +} + +func (r *secretService) HandleFinalizer(ctx context.Context, logger *zap.SugaredLogger, instance *corev1.Secret, namespaces []string) error { + if instance.ObjectMeta.DeletionTimestamp.IsZero() { + if containsString(instance.ObjectMeta.Finalizers, cfgSecretFinalizerName) { + return nil + } + instance.ObjectMeta.Finalizers = append(instance.ObjectMeta.Finalizers, cfgSecretFinalizerName) + if err := r.client.Update(context.Background(), instance); err != nil { + return err + } + } else { + if !containsString(instance.ObjectMeta.Finalizers, cfgSecretFinalizerName) { + return nil + } + for _, namespace := range namespaces { + logger.Debug(fmt.Sprintf("Deleting Secret '%s/%s'", namespace, instance.Name)) + if err := r.deleteSecret(ctx, logger, namespace, instance.Name); err != nil { + return err + } + } + instance.ObjectMeta.Finalizers = removeString(instance.ObjectMeta.Finalizers, cfgSecretFinalizerName) + if err := r.client.Update(context.Background(), instance); err != nil { + return err + } + } + return nil +} + +func (r *secretService) createSecret(ctx context.Context, logger *zap.SugaredLogger, namespace string, baseInstance *corev1.Secret) error { + secret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: baseInstance.GetName(), + Namespace: namespace, + Labels: baseInstance.Labels, + Annotations: baseInstance.Annotations, + }, + Data: baseInstance.Data, + StringData: baseInstance.StringData, + Type: baseInstance.Type, + } + + logger.Debug(fmt.Sprintf("Creating Secret '%s/%s'", secret.GetNamespace(), secret.GetName())) + if err := r.client.Create(ctx, &secret); err != nil { + logger.Error(err, fmt.Sprintf("Creating Secret '%s/%s' failed", secret.GetNamespace(), secret.GetName())) + return err + } + + return nil +} + +func (r *secretService) updateSecret(ctx context.Context, logger *zap.SugaredLogger, instance, baseInstance *corev1.Secret) error { + copy := instance.DeepCopy() + copy.Annotations = baseInstance.GetAnnotations() + copy.Labels = baseInstance.GetLabels() + copy.Data = baseInstance.Data + copy.StringData = baseInstance.StringData + copy.Type = baseInstance.Type + + if err := r.client.Update(ctx, copy); err != nil { + logger.Error(err, fmt.Sprintf("Updating Secret '%s/%s' failed", copy.GetNamespace(), copy.GetName())) + return err + } + + return nil +} + +func (r *secretService) deleteSecret(ctx context.Context, logger *zap.SugaredLogger, namespace, baseInstanceName string) error { + instance := &corev1.Secret{} + if err := r.client.Get(ctx, client.ObjectKey{Namespace: namespace, Name: baseInstanceName}, instance); err != nil { + return client.IgnoreNotFound(err) + } + if instance.Labels[FunctionManagedByLabel] == FunctionResourceLabelUserValue { + return nil + } + if err := r.client.Delete(ctx, instance); err != nil { + logger.Error(err, fmt.Sprintf("Deleting Secret '%s/%s' failed", namespace, baseInstanceName)) + return err + } + + return nil +} + +// Helper functions to check and remove string from a slice of strings. +func containsString(slice []string, s string) bool { + for _, item := range slice { + if item == s { + return true + } + } + return false +} + +func removeString(slice []string, s string) (result []string) { + for _, item := range slice { + if item == s { + continue + } + result = append(result, item) + } + return +} diff --git a/components/operator/internal/controllers/kubernetes/serviceaccount_service.go b/components/operator/internal/controllers/kubernetes/serviceaccount_service.go new file mode 100644 index 00000000..de7e143f --- /dev/null +++ b/components/operator/internal/controllers/kubernetes/serviceaccount_service.go @@ -0,0 +1,130 @@ +package kubernetes + +import ( + "context" + "fmt" + "strings" + + "go.uber.org/zap" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/kyma-project/docker-registry/components/operator/internal/resource" +) + +type ServiceAccountService interface { + IsBase(serviceAccount *corev1.ServiceAccount) bool + ListBase(ctx context.Context) ([]corev1.ServiceAccount, error) + UpdateNamespace(ctx context.Context, logger *zap.SugaredLogger, namespace string, baseInstance *corev1.ServiceAccount) error +} + +type serviceAccountService struct { + client resource.Client + config Config +} + +func NewServiceAccountService(client resource.Client, config Config) ServiceAccountService { + return &serviceAccountService{ + client: client, + config: config, + } +} + +func (r *serviceAccountService) ListBase(ctx context.Context) ([]corev1.ServiceAccount, error) { + serviceAccounts := &corev1.ServiceAccountList{} + if err := r.client.ListByLabel(ctx, r.config.BaseNamespace, map[string]string{ConfigLabel: ServiceAccountLabelValue}, serviceAccounts); err != nil { + return nil, err + } + + return serviceAccounts.Items, nil +} + +func (r *serviceAccountService) IsBase(serviceAccount *corev1.ServiceAccount) bool { + return serviceAccount.Namespace == r.config.BaseNamespace && serviceAccount.Labels[ConfigLabel] == ServiceAccountLabelValue +} + +func (r *serviceAccountService) UpdateNamespace(ctx context.Context, logger *zap.SugaredLogger, namespace string, baseInstance *corev1.ServiceAccount) error { + logger.Debug(fmt.Sprintf("Updating ServiceAccount '%s/%s'", namespace, baseInstance.GetName())) + serviceAccount := &corev1.ServiceAccount{} + if err := r.client.Get(ctx, client.ObjectKey{Namespace: namespace, Name: baseInstance.GetName()}, serviceAccount); err != nil { + if errors.IsNotFound(err) { + return r.createServiceAccount(ctx, logger, namespace, baseInstance) + } + logger.Error(err, fmt.Sprintf("Gathering existing ServiceAccount '%s/%s' failed", namespace, baseInstance.GetName())) + return err + } + + return r.updateServiceAccount(ctx, logger, serviceAccount, baseInstance) +} + +func (r *serviceAccountService) createServiceAccount(ctx context.Context, logger *zap.SugaredLogger, namespace string, baseInstance *corev1.ServiceAccount) error { + secrets := r.shiftSecretTokens(baseInstance) + serviceAccount := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: baseInstance.GetName(), + Namespace: namespace, + Labels: baseInstance.Labels, + Annotations: baseInstance.Annotations, + }, + Secrets: secrets, + ImagePullSecrets: baseInstance.ImagePullSecrets, + AutomountServiceAccountToken: baseInstance.AutomountServiceAccountToken, + } + + logger.Debug(fmt.Sprintf("Creating ServiceAccount '%s/%s'", serviceAccount.GetNamespace(), serviceAccount.GetName())) + if err := r.client.Create(ctx, &serviceAccount); err != nil { + logger.Error(err, fmt.Sprintf("Creating ServiceAccount '%s/%s'", serviceAccount.GetNamespace(), serviceAccount.GetName())) + return err + } + + return nil +} + +func (r *serviceAccountService) updateServiceAccount(ctx context.Context, logger *zap.SugaredLogger, instance, baseInstance *corev1.ServiceAccount) error { + tokens := r.extractSecretTokens(instance) + secrets := r.shiftSecretTokens(baseInstance) + secrets = append(secrets, tokens...) + + copy := instance.DeepCopy() + copy.Annotations = baseInstance.GetAnnotations() + copy.Labels = baseInstance.GetLabels() + copy.ImagePullSecrets = baseInstance.ImagePullSecrets + copy.AutomountServiceAccountToken = baseInstance.AutomountServiceAccountToken + copy.Secrets = secrets + + if err := r.client.Update(ctx, copy); err != nil { + logger.Error(err, fmt.Sprintf("Updating ServiceAccount '%s/%s' failed", copy.GetNamespace(), copy.GetName())) + return err + } + + return nil +} + +func (*serviceAccountService) shiftSecretTokens(baseInstance *corev1.ServiceAccount) []corev1.ObjectReference { + prefix := fmt.Sprintf("%s-token", baseInstance.Name) + + secrets := make([]corev1.ObjectReference, 0) + for _, secret := range baseInstance.Secrets { + if !strings.HasPrefix(secret.Name, prefix) { + secrets = append(secrets, secret) + } + } + + return secrets +} + +func (*serviceAccountService) extractSecretTokens(serviceAccount *corev1.ServiceAccount) []corev1.ObjectReference { + prefix := fmt.Sprintf("%s-token", serviceAccount.Name) + + secrets := make([]corev1.ObjectReference, 0) + for _, secret := range serviceAccount.Secrets { + if strings.HasPrefix(secret.Name, prefix) { + secrets = append(secrets, secret) + } + } + + return secrets +} diff --git a/components/operator/internal/controllers/kubernetes/shared.go b/components/operator/internal/controllers/kubernetes/shared.go new file mode 100644 index 00000000..10eb59ad --- /dev/null +++ b/components/operator/internal/controllers/kubernetes/shared.go @@ -0,0 +1,55 @@ +package kubernetes + +import ( + "context" + "time" + + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + ConfigLabel = "dockerregistry.kyma-project.io/config" + CredentialsLabelValue = "credentials" + ServiceAccountLabelValue = "service-account" + RuntimeLabelValue = "runtime" +) + +type Config struct { + BaseNamespace string `envconfig:"default=kyma-system"` + BaseDefaultSecretName string `envconfig:"default=internal-dockerregistry-config"` + ExcludedNamespaces []string `envconfig:"default=kyma-system"` + ConfigMapRequeueDuration time.Duration `envconfig:"default=1m"` + SecretRequeueDuration time.Duration `envconfig:"default=1m"` + ServiceAccountRequeueDuration time.Duration `envconfig:"default=1m"` +} + +func getNamespaces(ctx context.Context, client client.Client, base string, excluded []string) ([]string, error) { + var namespaces corev1.NamespaceList + if err := client.List(ctx, &namespaces); err != nil { + return nil, err + } + + names := make([]string, 0) + for _, namespace := range namespaces.Items { + if !isExcludedNamespace(namespace.GetName(), base, excluded) && namespace.Status.Phase != corev1.NamespaceTerminating { + names = append(names, namespace.GetName()) + } + } + + return names, nil +} + +func isExcludedNamespace(name, base string, excluded []string) bool { + if name == base { + return true + } + + for _, namespace := range excluded { + if name == namespace { + return true + } + } + + return false +} diff --git a/components/operator/internal/gitrepository/cleanup.go b/components/operator/internal/gitrepository/cleanup.go new file mode 100644 index 00000000..6a41405a --- /dev/null +++ b/components/operator/internal/gitrepository/cleanup.go @@ -0,0 +1,31 @@ +package gitrepository + +import ( + "context" + + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + gitRepoCRDName = "gitrepositories.dockerregistry.kyma-project.io" +) + +// Cleanup removes gitrepository CRD and its resources +func Cleanup(ctx context.Context, c client.Client) error { + crd, err := getCRD(ctx, c) + if err != nil { + return client.IgnoreNotFound(err) + } + + return c.Delete(ctx, crd, &client.DeleteOptions{}) +} + +func getCRD(ctx context.Context, client client.Client) (*apiextensionsv1.CustomResourceDefinition, error) { + var crd apiextensionsv1.CustomResourceDefinition + err := client.Get(ctx, types.NamespacedName{ + Name: gitRepoCRDName, + }, &crd) + return &crd, err +} diff --git a/components/operator/internal/gitrepository/cleanup_test.go b/components/operator/internal/gitrepository/cleanup_test.go new file mode 100644 index 00000000..3fa1ab7d --- /dev/null +++ b/components/operator/internal/gitrepository/cleanup_test.go @@ -0,0 +1,61 @@ +package gitrepository + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextensionsscheme "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme" + "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func TestCleanup(t *testing.T) { + t.Run("remove crd", func(t *testing.T) { + ctx := context.Background() + c := fake.NewClientBuilder(). + WithScheme(apiextensionsscheme.Scheme). + WithObjects(fixGitRepoCRD()). + Build() + + err := Cleanup(ctx, c) + + require.NoError(t, err) + + err = c.Get(ctx, types.NamespacedName{ + Name: gitRepoCRDName, + }, fixGitRepoCRD()) + require.True(t, errors.IsNotFound(err)) + }) + + t.Run("crd not found", func(t *testing.T) { + ctx := context.Background() + c := fake.NewClientBuilder(). + WithScheme(apiextensionsscheme.Scheme). + Build() + + err := Cleanup(ctx, c) + + require.NoError(t, err) + }) + + t.Run("client get error", func(t *testing.T) { + ctx := context.Background() + c := fake.NewClientBuilder().Build() + + err := Cleanup(ctx, c) + + require.Error(t, err) + }) +} + +func fixGitRepoCRD() *apiextensionsv1.CustomResourceDefinition { + return &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: v1.ObjectMeta{ + Name: gitRepoCRDName, + }, + } +} diff --git a/components/operator/internal/predicate/predicate.go b/components/operator/internal/predicate/predicate.go new file mode 100644 index 00000000..21b0d09c --- /dev/null +++ b/components/operator/internal/predicate/predicate.go @@ -0,0 +1,33 @@ +package predicate + +import ( + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +// this predicate allows not reacting on status changes +type NoStatusChangePredicate struct { + predicate.Funcs +} + +func (p NoStatusChangePredicate) Update(e event.UpdateEvent) bool { + if e.ObjectNew == nil || e.ObjectOld == nil { + return false + } + + // first resource version (after apply) + if e.ObjectOld.GetResourceVersion() == e.ObjectNew.GetResourceVersion() { + return true + } + + return !isStatusUpdate(e) +} + +func isStatusUpdate(e event.UpdateEvent) bool { + if e.ObjectOld.GetGeneration() == e.ObjectNew.GetGeneration() && + e.ObjectOld.GetResourceVersion() != e.ObjectNew.GetResourceVersion() { + return true + } + + return false +} diff --git a/components/operator/internal/predicate/predicate_test.go b/components/operator/internal/predicate/predicate_test.go new file mode 100644 index 00000000..e9119b88 --- /dev/null +++ b/components/operator/internal/predicate/predicate_test.go @@ -0,0 +1,98 @@ +package predicate + +import ( + "testing" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/event" +) + +func TestNoStatusChangePredicate_Update(t *testing.T) { + type args struct { + e event.UpdateEvent + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "nil objs", + args: args{ + e: event.UpdateEvent{ + ObjectOld: nil, + ObjectNew: nil, + }, + }, + want: false, + }, + { + name: "first obj iteration", + args: args{ + e: event.UpdateEvent{ + ObjectOld: func() *unstructured.Unstructured { + u := &unstructured.Unstructured{} + u.SetGeneration(1) + u.SetResourceVersion("560") + return u + }(), + ObjectNew: func() *unstructured.Unstructured { + u := &unstructured.Unstructured{} + u.SetGeneration(1) + u.SetResourceVersion("560") + return u + }(), + }, + }, + want: true, + }, + { + name: "status update", + args: args{ + e: event.UpdateEvent{ + ObjectOld: func() *unstructured.Unstructured { + u := &unstructured.Unstructured{} + u.SetGeneration(1) + u.SetResourceVersion("560") + return u + }(), + ObjectNew: func() *unstructured.Unstructured { + u := &unstructured.Unstructured{} + u.SetGeneration(1) + u.SetResourceVersion("600") + return u + }(), + }, + }, + want: false, + }, + { + name: "spec update", + args: args{ + e: event.UpdateEvent{ + ObjectOld: func() *unstructured.Unstructured { + u := &unstructured.Unstructured{} + u.SetGeneration(1) + u.SetResourceVersion("560") + return u + }(), + ObjectNew: func() *unstructured.Unstructured { + u := &unstructured.Unstructured{} + u.SetGeneration(2) + u.SetResourceVersion("600") + return u + }(), + }, + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := NoStatusChangePredicate{} + if got := p.Update(tt.args.e); got != tt.want { + t.Errorf("NoStatusChangePredicate.Update() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/components/operator/internal/registry/node_port.go b/components/operator/internal/registry/node_port.go new file mode 100644 index 00000000..f7c84e4e --- /dev/null +++ b/components/operator/internal/registry/node_port.go @@ -0,0 +1,158 @@ +package registry + +import ( + "context" + "fmt" + "math/rand" + + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + dockerRegistryNodePort = 32_137 + + //Available ports according to documentation https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + maxNodePort = 32_767 + minNodePort = 30_000 +) + +const ( + dockerRegistryService = "internal-docker-registry" + dockerRegistryPortName = "http-registry" + + allNamespaces = "" +) + +type nodePortFinder func() int32 + +type NodePortResolver struct { + nodePortFinder +} + +func NewNodePortResolver(finder nodePortFinder) *NodePortResolver { + return &NodePortResolver{nodePortFinder: finder} +} + +func (npr *NodePortResolver) ResolveDockerRegistryNodePortFn(ctx context.Context, k8sClient client.Client, namespace string) (int32, error) { + svc, err := getService(ctx, k8sClient, namespace, dockerRegistryService) + if err != nil { + return 0, errors.Wrap(err, fmt.Sprintf("while checking if %s service is installed on cluster", dockerRegistryService)) + } + + if svc != nil && svc.Spec.Type == corev1.ServiceTypeNodePort { + if isDefaultNodePortValue(svc) { + return dockerRegistryNodePort, nil + } + currentNodePort := getNodePort(svc) + return currentNodePort, nil + } + + svcs, err := getAllNodePortServices(ctx, k8sClient) + if err != nil { + return 0, errors.Wrap(err, "while fetching all services from cluster") + } + + if possibleConflict(svcs) { + newPort, err := npr.drawEmptyPortNumber(svcs) + if err != nil { + return 0, errors.Wrap(err, "while drawing available port number") + } + return newPort, nil + } + return dockerRegistryNodePort, nil +} + +func (npr *NodePortResolver) drawEmptyPortNumber(svcs *corev1.ServiceList) (int32, error) { + nodePorts := map[int32]struct{}{} + for _, svc := range svcs.Items { + for _, port := range svc.Spec.Ports { + nodePorts[port.NodePort] = struct{}{} + } + } + + retries := 100 + var emptyPort int32 + for i := 0; i < retries; i++ { + possibleEmptyPort := npr.nodePortFinder() + if _, ok := nodePorts[possibleEmptyPort]; !ok { + emptyPort = possibleEmptyPort + break + } + } + if emptyPort == 0 { + return 0, errors.New("couldn't draw available port number, try again") + } + return emptyPort, nil +} + +func getNodePort(svc *corev1.Service) int32 { + for _, port := range svc.Spec.Ports { + if port.Name == dockerRegistryPortName { + return port.NodePort + } + } + return dockerRegistryNodePort +} + +func getService(ctx context.Context, k8sClient client.Client, namespace, name string) (*corev1.Service, error) { + svc := corev1.Service{} + err := k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: name}, &svc) + if client.IgnoreNotFound(err) != nil { + return nil, errors.Wrap(err, fmt.Sprintf("while getting %s servicce", name)) + } + return &svc, nil +} + +func isDefaultNodePortValue(svc *corev1.Service) bool { + ports := svc.Spec.Ports + for _, port := range ports { + if port.NodePort == dockerRegistryNodePort { + return true + } + } + return false +} + +func getAllNodePortServices(ctx context.Context, k8sClient client.Client) (*corev1.ServiceList, error) { + svcs := corev1.ServiceList{} + err := k8sClient.List(ctx, &svcs, &client.ListOptions{Namespace: allNamespaces}) + if err != nil { + return nil, errors.Wrap(err, "while getting list of all services") + } + nodePortSvcs := &corev1.ServiceList{} + for _, svc := range svcs.Items { + if svc.Spec.Type == corev1.ServiceTypeNodePort { + nodePortSvcs.Items = append(nodePortSvcs.Items, svc) + } + if svc.Spec.Type == corev1.ServiceTypeLoadBalancer { + for _, port := range svc.Spec.Ports { + if port.NodePort != 0 { + nodePortSvcs.Items = append(nodePortSvcs.Items, svc) + break + } + } + } + } + return nodePortSvcs, nil +} + +func possibleConflict(svcs *corev1.ServiceList) bool { + for _, svc := range svcs.Items { + ports := svc.Spec.Ports + for _, port := range ports { + if port.NodePort == dockerRegistryNodePort { + return true + } + } + } + return false +} + +var _ nodePortFinder = RandomNodePort + +func RandomNodePort() int32 { + number := rand.Int31n(maxNodePort - minNodePort) + return minNodePort + number +} diff --git a/components/operator/internal/registry/node_port_test.go b/components/operator/internal/registry/node_port_test.go new file mode 100644 index 00000000..9965b8d3 --- /dev/null +++ b/components/operator/internal/registry/node_port_test.go @@ -0,0 +1,144 @@ +package registry + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +const nonConflictPort int32 = 32238 + +const kymaNamespace = "kyma-system" + +type assertFn func(t *testing.T, overrides map[string]interface{}) + +func TestNodePortAction(t *testing.T) { + testCases := map[string]struct { + givenService *corev1.Service + expectedPort int32 + assertFn assertFn + }{ + "Return default port new port when nodePort installed on default port": { + givenService: fixtureServiceNodePort(dockerRegistryService, kymaNamespace, dockerRegistryNodePort), + expectedPort: dockerRegistryNodePort, + }, + "Generate new port when nodePort service installed on different port": { + givenService: fixtureServiceNodePort(dockerRegistryService, kymaNamespace, nonConflictPort), + expectedPort: nonConflictPort, + }, + "Return default port new port when nodePort not installed, without port conflict": { + expectedPort: dockerRegistryNodePort, + }, + "Generate new port when nodePort not installed, with port conflict": { + givenService: fixtureServiceNodePort("conflicting-svc", kymaNamespace, dockerRegistryNodePort), + expectedPort: nonConflictPort, + }, + "Return default port new port when service is ClusterIP before upgrade without port conflict": { + givenService: fixtureServiceClusterIP(dockerRegistryService, kymaNamespace), + expectedPort: dockerRegistryNodePort, + }, + "Generate new port when cluster has NodePort service in different namespace with port conflict": { + givenService: fixtureServiceNodePort(dockerRegistryService, "different-ns", dockerRegistryNodePort), + expectedPort: nonConflictPort, + }, + "Generate new port when cluster has LoadBalancer service in different namespace with port conflict": { + givenService: fixtureLoadBalancer(), + expectedPort: nonConflictPort, + }, + } + + for testName, testCase := range testCases { + t.Run(testName, func(t *testing.T) { + //GIVEN + ctx := context.TODO() + k8sClient := fake.NewClientBuilder(). + WithRuntimeObjects(fixtureServices()...). + Build() + resolver := NewNodePortResolver(fixedNodePort(nonConflictPort)) + if testCase.givenService != nil { + err := k8sClient.Create(ctx, testCase.givenService, &client.CreateOptions{}) + require.NoError(t, err) + } + + //WHEN + port, err := resolver.ResolveDockerRegistryNodePortFn(ctx, k8sClient, kymaNamespace) + + //THEN + require.NoError(t, err) + require.Equal(t, testCase.expectedPort, port) + }) + } +} + +func fixtureServiceNodePort(name, namespace string, nodePort int32) *corev1.Service { + return &corev1.Service{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeNodePort, + Ports: []corev1.ServicePort{ + {Name: dockerRegistryPortName, NodePort: nodePort}}, + }, + } +} + +func fixtureServiceClusterIP(name, namespace string) *corev1.Service { + return &corev1.Service{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeClusterIP, + Ports: []corev1.ServicePort{ + {Name: dockerRegistryPortName, Port: 5000}}, + }, + } +} + +func fixtureServices() []runtime.Object { + l := []runtime.Object{ + fixtureServiceNodePort("other-node-port", kymaNamespace, dockerRegistryNodePort-1), + fixtureServiceNodePort("many-ports", kymaNamespace, dockerRegistryNodePort+2), + } + return l +} + +func fixedNodePort(expectedPort int32) func() int32 { + return func() int32 { + return expectedPort + } +} + +func fixtureLoadBalancer() *corev1.Service { + return &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "istio-ingressgateway", + Namespace: "istio-system", + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Ports: []corev1.ServicePort{ + { + NodePort: dockerRegistryNodePort, + Name: "http2", + }, + { + NodePort: 30857, + Name: "https", + }, + }, + }, + Status: corev1.ServiceStatus{}, + } +} diff --git a/components/operator/internal/registry/secret.go b/components/operator/internal/registry/secret.go new file mode 100644 index 00000000..63b44319 --- /dev/null +++ b/components/operator/internal/registry/secret.go @@ -0,0 +1,61 @@ +package registry + +import ( + "context" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + SecretName = "internal-dockerregistry-config" + LabelConfigKey = "dockerregistry.kyma-project.io/config" + LabelConfigVal = "credentials" + IsInternalKey = "isInternal" + DeploymentName = "internal-docker-registry" + HttpEnvKey = "REGISTRY_HTTP_SECRET" +) + +func GetDockerRegistryInternalRegistrySecret(ctx context.Context, c client.Client, namespace string) (*corev1.Secret, error) { + secret := corev1.Secret{} + key := client.ObjectKey{ + Namespace: namespace, + Name: SecretName, + } + err := c.Get(ctx, key, &secret) + if err != nil { + return nil, client.IgnoreNotFound(err) + } + + if val, ok := secret.GetLabels()[LabelConfigKey]; !ok || val != LabelConfigVal { + return nil, nil + } + + if val := string(secret.Data[IsInternalKey]); val != "true" { + return nil, nil + } + + return &secret, nil +} + +func GetRegistryHTTPSecretEnvValue(ctx context.Context, c client.Client, namespace string) (string, error) { + deployment := appsv1.Deployment{} + key := client.ObjectKey{ + Namespace: namespace, + Name: DeploymentName, + } + err := c.Get(ctx, key, &deployment) + if err != nil { + return "", client.IgnoreNotFound(err) + } + + envs := deployment.Spec.Template.Spec.Containers[0].Env + for _, v := range envs { + if v.Name == HttpEnvKey && v.Value != "" { + return v.Value, nil + } + } + + return "", nil +} diff --git a/components/operator/internal/resource/resource.go b/components/operator/internal/resource/resource.go new file mode 100644 index 00000000..afb88f91 --- /dev/null +++ b/components/operator/internal/resource/resource.go @@ -0,0 +1,107 @@ +package resource + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + apilabels "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +//go:generate mockery --name=Client --output=automock --outpkg=automock --case=underscore +type Client interface { + Create(ctx context.Context, object Object) error + CreateWithReference(ctx context.Context, parent Object, object Object) error + Update(ctx context.Context, object Object) error + Get(ctx context.Context, key ctrlclient.ObjectKey, object Object) error + ListByLabel(ctx context.Context, namespace string, labels map[string]string, object ctrlclient.ObjectList) error + DeleteAllBySelector(ctx context.Context, resourceType Object, namespace string, selector apilabels.Selector) error + Delete(ctx context.Context, resourceType Object) error + Status() ctrlclient.StatusWriter +} + +//go:generate mockery --name=K8sClient --output=automock --outpkg=automock --case=underscore +type K8sClient interface { + Create(context.Context, ctrlclient.Object, ...ctrlclient.CreateOption) error + Update(ctx context.Context, obj ctrlclient.Object, opts ...ctrlclient.UpdateOption) error + Get(ctx context.Context, key ctrlclient.ObjectKey, obj ctrlclient.Object, opts ...ctrlclient.GetOption) error + List(context.Context, ctrlclient.ObjectList, ...ctrlclient.ListOption) error + DeleteAllOf(context.Context, ctrlclient.Object, ...ctrlclient.DeleteAllOfOption) error + Status() ctrlclient.StatusWriter + Delete(ctx context.Context, obj ctrlclient.Object, opts ...ctrlclient.DeleteOption) error +} + +type Object interface { + runtime.Object + metav1.Object +} + +var _ Client = &client{} + +type client struct { + k8sClient K8sClient + schema *runtime.Scheme +} + +func (c *client) Delete(ctx context.Context, obj Object) error { + propagationPolicy := metav1.DeletePropagationBackground + return c.k8sClient.Delete(ctx, obj, &ctrlclient.DeleteOptions{ + PropagationPolicy: &propagationPolicy, + }) +} + +func New(k8sClient K8sClient, schema *runtime.Scheme) Client { + return &client{ + k8sClient: k8sClient, + schema: schema, + } +} + +func (c *client) Create(ctx context.Context, object Object) error { + return c.CreateWithReference(ctx, nil, object) +} + +func (c *client) CreateWithReference(ctx context.Context, parent, object Object) error { + if parent != nil { + if err := controllerutil.SetControllerReference(parent, object, c.schema); err != nil { + return err + } + } + + return c.k8sClient.Create(ctx, object) +} + +func (c *client) Update(ctx context.Context, object Object) error { + return c.k8sClient.Update(ctx, object) +} + +func (c *client) Get(ctx context.Context, key ctrlclient.ObjectKey, object Object) error { + return c.k8sClient.Get(ctx, key, object) +} + +func (c *client) ListByLabel(ctx context.Context, namespace string, labels map[string]string, list ctrlclient.ObjectList) error { + return c.k8sClient.List(ctx, list, &ctrlclient.ListOptions{ + LabelSelector: apilabels.SelectorFromSet(labels), + Namespace: namespace, + }) +} + +func (c *client) DeleteAllBySelector(ctx context.Context, resourceType Object, namespace string, selector apilabels.Selector) error { + propagationPolicy := metav1.DeletePropagationBackground + + return c.k8sClient.DeleteAllOf(ctx, resourceType, &ctrlclient.DeleteAllOfOptions{ + ListOptions: ctrlclient.ListOptions{ + LabelSelector: selector, + Namespace: namespace, + }, + DeleteOptions: ctrlclient.DeleteOptions{ + PropagationPolicy: &propagationPolicy, + }, + }) +} + +func (c *client) Status() ctrlclient.StatusWriter { + return c.k8sClient.Status() +} diff --git a/components/operator/internal/state/add_finalizer.go b/components/operator/internal/state/add_finalizer.go new file mode 100644 index 00000000..9ed07295 --- /dev/null +++ b/components/operator/internal/state/add_finalizer.go @@ -0,0 +1,32 @@ +package state + +import ( + "context" + + controllerruntime "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +func sFnAddFinalizer(ctx context.Context, r *reconciler, s *systemState) (stateFn, *controllerruntime.Result, error) { + instanceIsBeingDeleted := !s.instance.GetDeletionTimestamp().IsZero() + instanceHasFinalizer := controllerutil.ContainsFinalizer(&s.instance, r.finalizer) + if !instanceHasFinalizer { + // in case instance has no finalizer and instance is being deleted - end reconciliation + if instanceIsBeingDeleted { + // stop state machine + return stop() + } + + if err := addFinalizer(ctx, r, s); err != nil { + // stop state machine with potential error + return stopWithEventualError(err) + } + } + return nextState(sFnInitialize) +} + +func addFinalizer(ctx context.Context, r *reconciler, s *systemState) error { + // in case instance does not have finalizer - add it and update instance + controllerutil.AddFinalizer(&s.instance, r.finalizer) + return updateDockerRegistryWithoutStatus(ctx, r, s) +} diff --git a/components/operator/internal/state/add_finalizer_test.go b/components/operator/internal/state/add_finalizer_test.go new file mode 100644 index 00000000..9771e856 --- /dev/null +++ b/components/operator/internal/state/add_finalizer_test.go @@ -0,0 +1,84 @@ +package state + +import ( + "context" + "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "testing" +) + +func Test_sFnAddFinalizer(t *testing.T) { + t.Run("set finalizer", func(t *testing.T) { + scheme := runtime.NewScheme() + require.NoError(t, v1alpha1.AddToScheme(scheme)) + + dockerRegistry := v1alpha1.DockerRegistry{ + ObjectMeta: v1.ObjectMeta{ + Name: "test-name", + Namespace: "test-namespace", + ResourceVersion: "123", + }, + } + s := &systemState{ + instance: dockerRegistry, + } + r := &reconciler{ + cfg: cfg{ + finalizer: v1alpha1.Finalizer, + }, + k8s: k8s{ + client: fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(&dockerRegistry). + Build(), + }, + } + + // set finalizer + next, result, err := sFnAddFinalizer(context.Background(), r, s) + require.NoError(t, err) + require.Nil(t, result) + requireEqualFunc(t, sFnInitialize, next) + + // check finalizer in systemState + require.Contains(t, s.instance.GetFinalizers(), r.cfg.finalizer) + + // check finalizer in k8s + obj := v1alpha1.DockerRegistry{} + err = r.k8s.client.Get(context.Background(), + client.ObjectKey{ + Namespace: dockerRegistry.Namespace, + Name: dockerRegistry.Name, + }, + &obj) + require.NoError(t, err) + require.Contains(t, obj.GetFinalizers(), r.cfg.finalizer) + }) + + t.Run("stop when no finalizer and instance is being deleted", func(t *testing.T) { + r := &reconciler{ + cfg: cfg{ + finalizer: v1alpha1.Finalizer, + }, + } + + metaTimeNow := v1.Now() + s := &systemState{ + instance: v1alpha1.DockerRegistry{ + ObjectMeta: v1.ObjectMeta{ + DeletionTimestamp: &metaTimeNow, + }, + }, + } + + // stop + next, result, err := sFnAddFinalizer(context.Background(), r, s) + require.Nil(t, err) + require.Nil(t, result) + require.Nil(t, next) + }) +} diff --git a/components/operator/internal/state/apply.go b/components/operator/internal/state/apply.go new file mode 100644 index 00000000..12a5fb96 --- /dev/null +++ b/components/operator/internal/state/apply.go @@ -0,0 +1,37 @@ +package state + +import ( + "context" + + "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + "github.com/kyma-project/docker-registry/components/operator/internal/chart" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// run dockerregistry chart installation +func sFnApplyResources(_ context.Context, r *reconciler, s *systemState) (stateFn, *ctrl.Result, error) { + // set condition Installed if it does not exist + if !s.instance.IsCondition(v1alpha1.ConditionTypeInstalled) { + s.setState(v1alpha1.StateProcessing) + s.instance.UpdateConditionUnknown(v1alpha1.ConditionTypeInstalled, v1alpha1.ConditionReasonInstallation, + "Installing for configuration") + } + + // install component + err := chart.Install(s.chartConfig, s.flagsBuilder.Build()) + if err != nil { + r.log.Warnf("error while installing resource %s: %s", + client.ObjectKeyFromObject(&s.instance), err.Error()) + s.setState(v1alpha1.StateError) + s.instance.UpdateConditionFalse( + v1alpha1.ConditionTypeInstalled, + v1alpha1.ConditionReasonInstallationErr, + err, + ) + return stopWithEventualError(err) + } + + // switch state verify + return nextState(sFnVerifyResources) +} diff --git a/components/operator/internal/state/apply_test.go b/components/operator/internal/state/apply_test.go new file mode 100644 index 00000000..016e35b3 --- /dev/null +++ b/components/operator/internal/state/apply_test.go @@ -0,0 +1,104 @@ +package state + +import ( + "context" + "testing" + + "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + "github.com/kyma-project/docker-registry/components/operator/internal/chart" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +func Test_buildSFnApplyResources(t *testing.T) { + t.Run("switch state and add condition when condition is missing", func(t *testing.T) { + s := &systemState{ + instance: v1alpha1.DockerRegistry{}, + chartConfig: &chart.Config{ + Cache: fixEmptyManifestCache(), + CacheKey: types.NamespacedName{ + Name: testInstalledDockerRegistry.GetName(), + Namespace: testInstalledDockerRegistry.GetNamespace(), + }, + Release: chart.Release{ + Name: testInstalledDockerRegistry.GetName(), + Namespace: testInstalledDockerRegistry.GetNamespace(), + }, + }, + flagsBuilder: chart.NewFlagsBuilder(), + } + + next, result, err := sFnApplyResources(context.Background(), nil, s) + require.Nil(t, err) + require.Nil(t, result) + requireEqualFunc(t, sFnVerifyResources, next) + + status := s.instance.Status + require.Equal(t, v1alpha1.StateProcessing, status.State) + requireContainsCondition(t, status, + v1alpha1.ConditionTypeInstalled, + metav1.ConditionUnknown, + v1alpha1.ConditionReasonInstallation, + "Installing for configuration", + ) + }) + + t.Run("apply resources", func(t *testing.T) { + s := &systemState{ + instance: *testInstalledDockerRegistry.DeepCopy(), + chartConfig: &chart.Config{ + Cache: fixEmptyManifestCache(), + CacheKey: types.NamespacedName{ + Name: testInstalledDockerRegistry.GetName(), + Namespace: testInstalledDockerRegistry.GetNamespace(), + }, + Release: chart.Release{ + Name: testInstalledDockerRegistry.GetName(), + Namespace: testInstalledDockerRegistry.GetNamespace(), + }, + }, + flagsBuilder: chart.NewFlagsBuilder(), + } + r := &reconciler{} + + // run installation process and return verificating state + next, result, err := sFnApplyResources(context.Background(), r, s) + require.Nil(t, err) + require.Nil(t, result) + requireEqualFunc(t, sFnVerifyResources, next) + }) + + t.Run("install chart error", func(t *testing.T) { + s := &systemState{ + instance: *testInstalledDockerRegistry.DeepCopy(), + chartConfig: &chart.Config{ + Cache: fixManifestCache("\t"), + CacheKey: types.NamespacedName{ + Name: testInstalledDockerRegistry.GetName(), + Namespace: testInstalledDockerRegistry.GetNamespace(), + }, + }, + flagsBuilder: chart.NewFlagsBuilder(), + } + r := &reconciler{ + log: zap.NewNop().Sugar(), + } + + // handle error and return update condition state + next, result, err := sFnApplyResources(context.Background(), r, s) + require.EqualError(t, err, "could not parse chart manifest: yaml: found character that cannot start any token") + require.Nil(t, result) + require.Nil(t, next) + + status := s.instance.Status + require.Equal(t, v1alpha1.StateError, status.State) + requireContainsCondition(t, status, + v1alpha1.ConditionTypeInstalled, + metav1.ConditionFalse, + v1alpha1.ConditionReasonInstallationErr, + "could not parse chart manifest: yaml: found character that cannot start any token", + ) + }) +} diff --git a/components/operator/internal/state/controller_configuration.go b/components/operator/internal/state/controller_configuration.go new file mode 100644 index 00000000..b6170ede --- /dev/null +++ b/components/operator/internal/state/controller_configuration.go @@ -0,0 +1,45 @@ +package state + +import ( + "context" + "github.com/kyma-project/docker-registry/components/operator/internal/registry" + + "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + controllerruntime "sigs.k8s.io/controller-runtime" +) + +func sFnControllerConfiguration(_ context.Context, r *reconciler, s *systemState) (stateFn, *controllerruntime.Result, error) { + err := updateControllerConfigurationStatus(r, &s.instance) + if err != nil { + return stopWithEventualError(err) + } + + configureControllerConfigurationFlags(s) + + s.setState(v1alpha1.StateProcessing) + s.instance.UpdateConditionTrue( + v1alpha1.ConditionTypeConfigured, + v1alpha1.ConditionReasonConfigured, + "Configuration ready", + ) + + return nextState(sFnApplyResources) +} + +func updateControllerConfigurationStatus(r *reconciler, instance *v1alpha1.DockerRegistry) error { + spec := instance.Spec + fields := fieldsToUpdate{ + {spec.HealthzLivenessTimeout, &instance.Status.HealthzLivenessTimeout, "Duration of health check", ""}, + {registry.SecretName, &instance.Status.SecretName, "Name of secret with registry access data", ""}, + } + + updateStatusFields(r.k8s, instance, fields) + return nil +} + +func configureControllerConfigurationFlags(s *systemState) { + s.flagsBuilder. + WithControllerConfiguration( + s.instance.Status.HealthzLivenessTimeout, + ) +} diff --git a/components/operator/internal/state/controller_configuration_test.go b/components/operator/internal/state/controller_configuration_test.go new file mode 100644 index 00000000..431231b2 --- /dev/null +++ b/components/operator/internal/state/controller_configuration_test.go @@ -0,0 +1,110 @@ +package state + +import ( + "context" + "github.com/kyma-project/docker-registry/components/operator/internal/registry" + "testing" + + "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + "github.com/kyma-project/docker-registry/components/operator/internal/chart" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +const ( + healthzLivenessTimeoutTest = "test-healthz-liveness-timeout" +) + +func Test_sFnControllerConfiguration(t *testing.T) { + configurationReadyMsg := "Configuration ready" + + t.Run("update status additional configuration overrides", func(t *testing.T) { + s := &systemState{ + instance: v1alpha1.DockerRegistry{ + Spec: v1alpha1.DockerRegistrySpec{ + HealthzLivenessTimeout: healthzLivenessTimeoutTest, + }, + }, + flagsBuilder: chart.NewFlagsBuilder(), + } + + c := fake.NewClientBuilder().Build() + eventRecorder := record.NewFakeRecorder(10) + r := &reconciler{log: zap.NewNop().Sugar(), k8s: k8s{client: c, EventRecorder: eventRecorder}} + next, result, err := sFnControllerConfiguration(context.TODO(), r, s) + require.Nil(t, err) + require.Nil(t, result) + requireEqualFunc(t, sFnApplyResources, next) + + status := s.instance.Status + require.Equal(t, healthzLivenessTimeoutTest, status.HealthzLivenessTimeout) + require.Equal(t, registry.SecretName, status.SecretName) + + require.Equal(t, v1alpha1.StateProcessing, status.State) + requireContainsCondition(t, status, + v1alpha1.ConditionTypeConfigured, + metav1.ConditionTrue, + v1alpha1.ConditionReasonConfigured, + configurationReadyMsg, + ) + + expectedEvents := []string{ + "Normal Configuration Duration of health check set from '' to 'test-healthz-liveness-timeout'", + } + + for _, expectedEvent := range expectedEvents { + require.Equal(t, expectedEvent, <-eventRecorder.Events) + } + }) + + t.Run("reconcile from configurationError", func(t *testing.T) { + s := &systemState{ + instance: v1alpha1.DockerRegistry{ + Status: v1alpha1.DockerRegistryStatus{ + Conditions: []metav1.Condition{ + { + Type: string(v1alpha1.ConditionTypeConfigured), + Status: metav1.ConditionFalse, + Reason: string(v1alpha1.ConditionReasonConfigurationErr), + }, + { + Type: string(v1alpha1.ConditionTypeInstalled), + Status: metav1.ConditionTrue, + Reason: string(v1alpha1.ConditionReasonInstallation), + }, + }, + State: v1alpha1.StateError, + }, + }, + statusSnapshot: v1alpha1.DockerRegistryStatus{}, + flagsBuilder: chart.NewFlagsBuilder(), + } + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "boo", + }, + } + r := &reconciler{ + log: zap.NewNop().Sugar(), + k8s: k8s{ + client: fake.NewClientBuilder().WithObjects(secret).Build(), + EventRecorder: record.NewFakeRecorder(2), + }, + } + + next, result, err := sFnControllerConfiguration(context.Background(), r, s) + require.NoError(t, err) + require.Nil(t, result) + requireEqualFunc(t, sFnApplyResources, next) + requireContainsCondition(t, s.instance.Status, + v1alpha1.ConditionTypeConfigured, + metav1.ConditionTrue, + v1alpha1.ConditionReasonConfigured, + configurationReadyMsg) + require.Equal(t, v1alpha1.StateProcessing, s.instance.Status.State) + }) +} diff --git a/components/operator/internal/state/delete.go b/components/operator/internal/state/delete.go new file mode 100644 index 00000000..09332081 --- /dev/null +++ b/components/operator/internal/state/delete.go @@ -0,0 +1,99 @@ +package state + +import ( + "context" + "time" + + "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + "github.com/kyma-project/docker-registry/components/operator/internal/chart" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// delete dockerregistry based on previously installed resources +func sFnDeleteResources(_ context.Context, _ *reconciler, s *systemState) (stateFn, *ctrl.Result, error) { + s.setState(v1alpha1.StateDeleting) + s.instance.UpdateConditionUnknown( + v1alpha1.ConditionTypeDeleted, + v1alpha1.ConditionReasonDeletion, + "Uninstalling", + ) + + return nextState(sFnSafeDeletionState) +} + +func sFnSafeDeletionState(_ context.Context, r *reconciler, s *systemState) (stateFn, *ctrl.Result, error) { + if err := chart.CheckCRDOrphanResources(s.chartConfig); err != nil { + // stop state machine with a warning and requeue reconciliation in 1min + // warning state indicates that user intervention would fix it. It's not reconciliation error. + s.setState(v1alpha1.StateWarning) + s.instance.UpdateConditionFalse( + v1alpha1.ConditionTypeDeleted, + v1alpha1.ConditionReasonDeletionErr, + err, + ) + return stopWithEventualError(err) + } + + return deleteResourcesWithFilter(r, s) +} + +func deleteResourcesWithFilter(r *reconciler, s *systemState, filterFuncs ...chart.FilterFunc) (stateFn, *ctrl.Result, error) { + err, done := chart.UninstallSecrets(s.chartConfig, filterFuncs...) + if err != nil { + return uninstallSecretsError(r, s, err) + } + if !done { + return awaitingSecretsRemoval(s) + } + + if err := chart.Uninstall(s.chartConfig, filterFuncs...); err != nil { + return uninstallResourcesError(r, s, err) + } + + s.setState(v1alpha1.StateDeleting) + s.instance.UpdateConditionTrue( + v1alpha1.ConditionTypeDeleted, + v1alpha1.ConditionReasonDeleted, + "DockerRegistry module deleted", + ) + + // if resources are ready to be deleted, remove finalizer + return nextState(sFnRemoveFinalizer) +} + +func uninstallResourcesError(r *reconciler, s *systemState, err error) (stateFn, *ctrl.Result, error) { + r.log.Warnf("error while uninstalling resource %s: %s", + client.ObjectKeyFromObject(&s.instance), err.Error()) + s.setState(v1alpha1.StateError) + s.instance.UpdateConditionFalse( + v1alpha1.ConditionTypeDeleted, + v1alpha1.ConditionReasonDeletionErr, + err, + ) + return stopWithEventualError(err) +} + +func awaitingSecretsRemoval(s *systemState) (stateFn, *ctrl.Result, error) { + s.setState(v1alpha1.StateDeleting) + s.instance.UpdateConditionTrue( + v1alpha1.ConditionTypeDeleted, + v1alpha1.ConditionReasonDeletion, + "Deleting secrets", + ) + + // wait one sec until ctrl-mngr remove finalizers from secrets + return requeueAfter(time.Second) +} + +func uninstallSecretsError(r *reconciler, s *systemState, err error) (stateFn, *ctrl.Result, error) { + r.log.Warnf("error while uninstalling secrets %s: %s", + client.ObjectKeyFromObject(&s.instance), err.Error()) + s.setState(v1alpha1.StateError) + s.instance.UpdateConditionFalse( + v1alpha1.ConditionTypeDeleted, + v1alpha1.ConditionReasonDeletionErr, + err, + ) + return stopWithEventualError(err) +} diff --git a/components/operator/internal/state/delete_test.go b/components/operator/internal/state/delete_test.go new file mode 100644 index 00000000..8f0fb16d --- /dev/null +++ b/components/operator/internal/state/delete_test.go @@ -0,0 +1,121 @@ +package state + +import ( + "context" + "testing" + + "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + "github.com/kyma-project/docker-registry/components/operator/internal/chart" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +var ( + testDeletingDockerRegistry = func() v1alpha1.DockerRegistry { + dockerRegistry := testInstalledDockerRegistry + dockerRegistry.Status.State = v1alpha1.StateDeleting + dockerRegistry.Status.Conditions = []metav1.Condition{ + { + Type: string(v1alpha1.ConditionTypeDeleted), + Reason: string(v1alpha1.ConditionReasonDeletion), + Status: metav1.ConditionUnknown, + }, + } + return dockerRegistry + }() +) + +func Test_sFnDeleteResources(t *testing.T) { + ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace"}} + + t.Run("update condition", func(t *testing.T) { + s := &systemState{ + instance: v1alpha1.DockerRegistry{}, + } + + next, result, err := sFnDeleteResources(context.Background(), nil, s) + require.Nil(t, err) + require.Nil(t, result) + requireEqualFunc(t, sFnSafeDeletionState, next) + + status := s.instance.Status + require.Equal(t, v1alpha1.StateDeleting, status.State) + requireContainsCondition(t, status, + v1alpha1.ConditionTypeDeleted, + metav1.ConditionUnknown, + v1alpha1.ConditionReasonDeletion, + "Uninstalling", + ) + }) + + t.Run("deletion error while checking orphan resources", func(t *testing.T) { + s := &systemState{ + instance: *testDeletingDockerRegistry.DeepCopy(), + chartConfig: &chart.Config{ + Cache: fixManifestCache("\t"), + CacheKey: types.NamespacedName{ + Name: testInstalledDockerRegistry.GetName(), + Namespace: testInstalledDockerRegistry.GetNamespace(), + }, + }, + } + r := &reconciler{ + log: zap.NewNop().Sugar(), + } + + next, result, err := sFnSafeDeletionState(context.TODO(), r, s) + require.EqualError(t, err, "could not parse chart manifest: yaml: found character that cannot start any token") + require.Nil(t, result) + require.Nil(t, next) + + status := s.instance.Status + require.Equal(t, v1alpha1.StateWarning, status.State) + requireContainsCondition(t, status, + v1alpha1.ConditionTypeDeleted, + metav1.ConditionFalse, + v1alpha1.ConditionReasonDeletionErr, + "could not parse chart manifest: yaml: found character that cannot start any token", + ) + }) + + t.Run("deletion", func(t *testing.T) { + s := &systemState{ + instance: *testDeletingDockerRegistry.DeepCopy(), + chartConfig: &chart.Config{ + Cache: fixEmptyManifestCache(), + CacheKey: types.NamespacedName{ + Name: testDeletingDockerRegistry.GetName(), + Namespace: testDeletingDockerRegistry.GetNamespace(), + }, + Cluster: chart.Cluster{ + Client: fake.NewClientBuilder(). + WithScheme(scheme.Scheme). + WithObjects(&ns). + Build(), + }, + }, + } + r := &reconciler{ + log: zap.NewNop().Sugar(), + } + + next, result, err := sFnSafeDeletionState(context.TODO(), r, s) + require.Nil(t, err) + require.Nil(t, result) + requireEqualFunc(t, sFnRemoveFinalizer, next) + + status := s.instance.Status + require.Equal(t, v1alpha1.StateDeleting, status.State) + requireContainsCondition(t, status, + v1alpha1.ConditionTypeDeleted, + metav1.ConditionTrue, + v1alpha1.ConditionReasonDeleted, + "DockerRegistry module deleted", + ) + }) +} diff --git a/components/operator/internal/state/emit_event.go b/components/operator/internal/state/emit_event.go new file mode 100644 index 00000000..65a1febb --- /dev/null +++ b/components/operator/internal/state/emit_event.go @@ -0,0 +1,41 @@ +package state + +import ( + "strings" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + warningMessagePrefix = "Warning" +) + +func emitEvent(m *reconciler, s *systemState) { + // compare if any condition change + for _, condition := range s.instance.Status.Conditions { + // check if condition exists in memento status + memorizedCondition := meta.FindStatusCondition(s.statusSnapshot.Conditions, condition.Type) + // ignore unchanged conditions + if memorizedCondition != nil && + memorizedCondition.Status == condition.Status && + memorizedCondition.Reason == condition.Reason && + memorizedCondition.Message == condition.Message { + continue + } + m.Event( + &s.instance, + eventType(condition, condition.Message), + condition.Reason, + condition.Message, + ) + } +} + +func eventType(condition metav1.Condition, message string) string { + eventType := "Normal" + if condition.Status == metav1.ConditionFalse || strings.HasPrefix(message, warningMessagePrefix) { + eventType = "Warning" + } + return eventType +} diff --git a/components/operator/internal/state/emit_event_test.go b/components/operator/internal/state/emit_event_test.go new file mode 100644 index 00000000..23b9e3a9 --- /dev/null +++ b/components/operator/internal/state/emit_event_test.go @@ -0,0 +1,94 @@ +package state + +import ( + "testing" + + "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/record" +) + +var ( + testDockerRegistryConditions1 = v1alpha1.DockerRegistry{ + Status: v1alpha1.DockerRegistryStatus{ + Conditions: []metav1.Condition{ + { + Status: metav1.ConditionUnknown, + Reason: "test-reason", + Message: "test message 1", + Type: "test-type-1", + }, + { + Status: metav1.ConditionUnknown, + Reason: "test-reason", + Message: "test message 1", + Type: "test-type-2", + }, + }, + }, + } + testDockerRegistryConditions2 = v1alpha1.DockerRegistry{ + Status: v1alpha1.DockerRegistryStatus{ + Conditions: []metav1.Condition{ + { + Status: metav1.ConditionFalse, + Reason: "test-reason", + Message: "test message 2", + Type: "test-type-1", + }, + { + Status: metav1.ConditionTrue, + Reason: "test-reason", + Message: "test message 2", + Type: "test-type-2", + }, + }, + }, + } +) + +func Test_emitEvent(t *testing.T) { + t.Run("don't emit event", func(t *testing.T) { + eventRecorder := record.NewFakeRecorder(5) + s := &systemState{ + instance: *testDockerRegistryConditions1.DeepCopy(), + statusSnapshot: *testDockerRegistryConditions1.Status.DeepCopy(), + } + r := &reconciler{ + k8s: k8s{ + EventRecorder: eventRecorder, + }, + } + + emitEvent(r, s) + + // check conditions, don't emit event + require.Len(t, eventRecorder.Events, 0) + }) + + t.Run("emit events", func(t *testing.T) { + eventRecorder := record.NewFakeRecorder(5) + s := &systemState{ + instance: *testDockerRegistryConditions2.DeepCopy(), + statusSnapshot: *testDockerRegistryConditions1.Status.DeepCopy(), + } + r := &reconciler{ + k8s: k8s{ + EventRecorder: eventRecorder, + }, + } + + // build emitEventFunc + emitEvent(r, s) + + // check conditions, don't emit event + require.Len(t, eventRecorder.Events, 2) + + expectedEvents := []string{"Warning test-reason test message 2", "Normal test-reason test message 2"} + close(eventRecorder.Events) + for v := range eventRecorder.Events { + require.Contains(t, expectedEvents, v) + } + }) +} diff --git a/components/operator/internal/state/fsm.go b/components/operator/internal/state/fsm.go new file mode 100644 index 00000000..a5b636ba --- /dev/null +++ b/components/operator/internal/state/fsm.go @@ -0,0 +1,143 @@ +package state + +import ( + "context" + "fmt" + "reflect" + "runtime" + "strings" + + "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + "github.com/kyma-project/docker-registry/components/operator/internal/chart" + "github.com/kyma-project/docker-registry/components/operator/internal/warning" + "go.uber.org/zap" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var ( + defaultResult = ctrl.Result{} + secretCacheKey = types.NamespacedName{ + Name: "dockerregistry-manifest-cache", + Namespace: "kyma-system", + } +) + +type stateFn func(context.Context, *reconciler, *systemState) (stateFn, *ctrl.Result, error) + +type cfg struct { + finalizer string + chartPath string + managerPodUID string +} + +type systemState struct { + instance v1alpha1.DockerRegistry + statusSnapshot v1alpha1.DockerRegistryStatus + chartConfig *chart.Config + warningBuilder *warning.Builder + flagsBuilder chart.FlagsBuilder +} + +func (s *systemState) saveStatusSnapshot() { + result := s.instance.Status.DeepCopy() + if result == nil { + result = &v1alpha1.DockerRegistryStatus{} + } + s.statusSnapshot = *result +} + +func (s *systemState) setState(state v1alpha1.State) { + s.instance.Status.State = state +} + +func (s *systemState) setServed(served v1alpha1.Served) { + s.instance.Status.Served = served +} + +func chartConfig(ctx context.Context, r *reconciler, namespace string) *chart.Config { + return &chart.Config{ + Ctx: ctx, + Log: r.log, + Cache: r.cache, + CacheKey: secretCacheKey, + ManagerUID: r.managerPodUID, + Cluster: chart.Cluster{ + Client: r.client, + Config: r.config, + }, + Release: chart.Release{ + ChartPath: r.chartPath, + Namespace: namespace, + Name: "dockerregistry", + }, + } +} + +type k8s struct { + client client.Client + config *rest.Config + record.EventRecorder +} + +type reconciler struct { + fn stateFn + log *zap.SugaredLogger + cache chart.ManifestCache + result ctrl.Result + k8s + cfg +} + +func (m *reconciler) stateFnName() string { + fullName := runtime.FuncForPC(reflect.ValueOf(m.fn).Pointer()).Name() + splitFullName := strings.Split(fullName, ".") + + if len(splitFullName) < 3 { + return fullName + } + + shortName := splitFullName[2] + return shortName +} + +func (m *reconciler) Reconcile(ctx context.Context, v v1alpha1.DockerRegistry) (ctrl.Result, error) { + state := systemState{ + instance: v, + warningBuilder: warning.NewBuilder(), + flagsBuilder: chart.NewFlagsBuilder(), + chartConfig: chartConfig(ctx, m, v.Namespace), + } + state.saveStatusSnapshot() + var err error + var result *ctrl.Result +loop: + for m.fn != nil && err == nil { + select { + case <-ctx.Done(): + err = ctx.Err() + break loop + + default: + m.log.Info(fmt.Sprintf("switching state: %s", m.stateFnName())) + m.fn, result, err = m.fn(ctx, m, &state) + if updateErr := updateDockerRegistryStatus(ctx, m, &state); updateErr != nil { + err = updateErr + } + } + } + + if result == nil { + result = &defaultResult + } + + m.log. + With("error", err). + With("result", result). + Info("reconciliation done") + + return *result, err +} diff --git a/components/operator/internal/state/fsm_test.go b/components/operator/internal/state/fsm_test.go new file mode 100644 index 00000000..b05f42fe --- /dev/null +++ b/components/operator/internal/state/fsm_test.go @@ -0,0 +1,145 @@ +package state + +import ( + "context" + "reflect" + "testing" + + "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + "github.com/kyma-project/docker-registry/components/operator/internal/chart" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +var ( + testStateFn = func(ctx context.Context, r *reconciler, ss *systemState) (stateFn, *ctrl.Result, error) { + return nil, &testResult, nil + } + + testWrappedStateFn = func(ctx context.Context, r *reconciler, ss *systemState) (stateFn, *ctrl.Result, error) { + return testStateFn, nil, nil + } + + testResult = ctrl.Result{ + Requeue: true, + } + + canceledCtx = func() context.Context { + ctx, done := context.WithCancel(context.Background()) + done() + return ctx + }() +) + +func Test_reconciler_Reconcile(t *testing.T) { + type fields struct { + fn stateFn + log *zap.SugaredLogger + cache chart.ManifestCache + result ctrl.Result + k8s k8s + cfg cfg + } + type args struct { + ctx context.Context + v v1alpha1.DockerRegistry + } + tests := []struct { + name string + fields fields + args args + want ctrl.Result + wantErr bool + }{ + { + name: "empty fn", + fields: fields{ + log: zap.NewNop().Sugar(), + }, + want: defaultResult, + wantErr: false, + }, + { + name: "with ctx done", + fields: fields{ + log: zap.NewNop().Sugar(), + fn: testStateFn, + }, + args: args{ + ctx: canceledCtx, + }, + want: defaultResult, + wantErr: true, + }, + { + name: "with many fns", + fields: fields{ + log: zap.NewNop().Sugar(), + fn: testWrappedStateFn, + }, + args: args{ + ctx: context.Background(), + }, + want: testResult, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &reconciler{ + fn: tt.fields.fn, + log: tt.fields.log, + cache: tt.fields.cache, + result: tt.fields.result, + k8s: tt.fields.k8s, + cfg: tt.fields.cfg, + } + got, err := m.Reconcile(tt.args.ctx, tt.args.v) + if (err != nil) != tt.wantErr { + t.Errorf("reconciler.Reconcile() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("reconciler.Reconcile() = %v, want %v", got, tt.want) + } + }) + } + + t.Run("take status snapshot", func(t *testing.T) { + fn := func(_ context.Context, _ *reconciler, s *systemState) (stateFn, *ctrl.Result, error) { + // check status + require.Equal(t, s.instance.Status, s.statusSnapshot) + return nil, nil, nil + } + r := &reconciler{ + fn: fn, + cfg: cfg{ + finalizer: v1alpha1.Finalizer, + }, + k8s: k8s{ + client: fake.NewClientBuilder().Build(), + }, + log: zap.NewNop().Sugar(), + } + dockerRegistry := v1alpha1.DockerRegistry{ + Status: v1alpha1.DockerRegistryStatus{ + Conditions: []metav1.Condition{ + { + Type: "test-type", + Status: "test-status", + Reason: "test-reason", + Message: "test-message", + ObservedGeneration: 1, + LastTransitionTime: metav1.Now(), + }, + }, + State: v1alpha1.StateError, + }, + } + _, err := r.Reconcile(context.Background(), dockerRegistry) + require.NoError(t, err) + }) +} diff --git a/components/operator/internal/state/initialize.go b/components/operator/internal/state/initialize.go new file mode 100644 index 00000000..afd12a61 --- /dev/null +++ b/components/operator/internal/state/initialize.go @@ -0,0 +1,18 @@ +package state + +import ( + "context" + + ctrl "sigs.k8s.io/controller-runtime" +) + +// choose right scenario to start (installation/deletion) +func sFnInitialize(_ context.Context, _ *reconciler, s *systemState) (stateFn, *ctrl.Result, error) { + // in case instance is being deleted and has finalizer - delete all resources + instanceIsBeingDeleted := !s.instance.GetDeletionTimestamp().IsZero() + if instanceIsBeingDeleted { + return nextState(sFnDeleteResources) + } + + return nextState(sFnRegistryConfiguration) +} diff --git a/components/operator/internal/state/initialize_test.go b/components/operator/internal/state/initialize_test.go new file mode 100644 index 00000000..e6fd0e25 --- /dev/null +++ b/components/operator/internal/state/initialize_test.go @@ -0,0 +1,67 @@ +package state + +import ( + "context" + "testing" + + "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func Test_sFnInitialize(t *testing.T) { + t.Run("setup and return next step sFnRegistryConfiguration", func(t *testing.T) { + r := &reconciler{ + cfg: cfg{ + finalizer: v1alpha1.Finalizer, + }, + k8s: k8s{ + client: fake.NewClientBuilder().Build(), + }, + } + s := &systemState{ + instance: v1alpha1.DockerRegistry{ + ObjectMeta: metav1.ObjectMeta{ + Finalizers: []string{ + r.cfg.finalizer, + }, + }, + }, + } + + // setup and return buildSFnPrerequisites + next, result, err := sFnInitialize(context.Background(), r, s) + require.Nil(t, err) + require.Nil(t, result) + requireEqualFunc(t, sFnRegistryConfiguration, next) + }) + + t.Run("setup and return next step sFnDeleteResources", func(t *testing.T) { + r := &reconciler{ + cfg: cfg{ + finalizer: v1alpha1.Finalizer, + }, + k8s: k8s{ + client: fake.NewClientBuilder().Build(), + }, + } + metaTime := metav1.Now() + s := &systemState{ + instance: v1alpha1.DockerRegistry{ + ObjectMeta: metav1.ObjectMeta{ + Finalizers: []string{ + r.cfg.finalizer, + }, + DeletionTimestamp: &metaTime, + }, + }, + } + + // setup and return buildSFnDeleteResources + next, result, err := sFnInitialize(context.Background(), r, s) + require.Nil(t, err) + require.Nil(t, result) + requireEqualFunc(t, sFnDeleteResources, next) + }) +} diff --git a/components/operator/internal/state/new.go b/components/operator/internal/state/new.go new file mode 100644 index 00000000..54e00edf --- /dev/null +++ b/components/operator/internal/state/new.go @@ -0,0 +1,36 @@ +package state + +import ( + "context" + "os" + + "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + "github.com/kyma-project/docker-registry/components/operator/internal/chart" + "go.uber.org/zap" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type StateReconciler interface { + Reconcile(ctx context.Context, v v1alpha1.DockerRegistry) (ctrl.Result, error) +} + +func NewMachine(client client.Client, config *rest.Config, recorder record.EventRecorder, log *zap.SugaredLogger, cache chart.ManifestCache, chartPath string) StateReconciler { + return &reconciler{ + fn: sFnServedFilter, + cache: cache, + log: log, + cfg: cfg{ + finalizer: v1alpha1.Finalizer, + chartPath: chartPath, + managerPodUID: os.Getenv("DOCKERREGISTRY_MANAGER_UID"), + }, + k8s: k8s{ + client: client, + config: config, + EventRecorder: recorder, + }, + } +} diff --git a/components/operator/internal/state/registry.go b/components/operator/internal/state/registry.go new file mode 100644 index 00000000..c39d56c6 --- /dev/null +++ b/components/operator/internal/state/registry.go @@ -0,0 +1,66 @@ +package state + +import ( + "context" + "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + "github.com/kyma-project/docker-registry/components/operator/internal/registry" + "github.com/pkg/errors" + ctrl "sigs.k8s.io/controller-runtime" +) + +func sFnRegistryConfiguration(ctx context.Context, r *reconciler, s *systemState) (stateFn, *ctrl.Result, error) { + s.setState(v1alpha1.StateProcessing) + // setup status.dockerRegistry and set possible warnings + err := configureRegistry(ctx, r, s) + if err != nil { + s.setState(v1alpha1.StateError) + s.instance.UpdateConditionFalse( + v1alpha1.ConditionTypeConfigured, + v1alpha1.ConditionReasonConfigurationErr, + err, + ) + return stopWithEventualError(err) + } + + return nextState(sFnControllerConfiguration) +} + +func configureRegistry(ctx context.Context, r *reconciler, s *systemState) error { + err := setInternalRegistryConfig(ctx, r, s) + if err != nil { + return err + } + + return nil +} + +func setInternalRegistryConfig(ctx context.Context, r *reconciler, s *systemState) error { + existingIntRegSecret, err := registry.GetDockerRegistryInternalRegistrySecret(ctx, r.client, s.instance.Namespace) + if err != nil { + return errors.Wrap(err, "while fetching existing internal docker registry secret") + } + if existingIntRegSecret != nil { + r.log.Debugf("reusing existing credentials for internal docker registry to avoiding docker registry rollout") + registryHttpSecretEnvValue, getErr := registry.GetRegistryHTTPSecretEnvValue(ctx, r.client, s.instance.Namespace) + if getErr != nil { + return errors.Wrap(getErr, "while reading env value registryHttpSecret from internal docker registry deployment") + } + s.flagsBuilder. + WithRegistryCredentials( + string(existingIntRegSecret.Data["username"]), + string(existingIntRegSecret.Data["password"]), + ). + WithRegistryHttpSecret( + registryHttpSecretEnvValue, + ) + } + + resolver := registry.NewNodePortResolver(registry.RandomNodePort) + nodePort, err := resolver.ResolveDockerRegistryNodePortFn(ctx, r.client, s.instance.Namespace) + if err != nil { + return errors.Wrap(err, "while resolving registry node port") + } + r.log.Debugf("docker registry node port: %d", nodePort) + s.flagsBuilder.WithNodePort(int64(nodePort)) + return nil +} diff --git a/components/operator/internal/state/registry_test.go b/components/operator/internal/state/registry_test.go new file mode 100644 index 00000000..514dae5f --- /dev/null +++ b/components/operator/internal/state/registry_test.go @@ -0,0 +1,39 @@ +package state + +import ( + "context" + "testing" + + "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + "github.com/kyma-project/docker-registry/components/operator/internal/chart" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func Test_sFnRegistryConfiguration(t *testing.T) { + t.Run("internal registry and update", func(t *testing.T) { + s := &systemState{ + instance: v1alpha1.DockerRegistry{}, + statusSnapshot: v1alpha1.DockerRegistryStatus{}, + flagsBuilder: chart.NewFlagsBuilder(), + } + r := &reconciler{ + k8s: k8s{client: fake.NewClientBuilder().Build()}, + log: zap.NewNop().Sugar(), + } + expectedFlags := map[string]interface{}{ + "global": map[string]interface{}{ + "registryNodePort": int64(32_137), + }, + } + + next, result, err := sFnRegistryConfiguration(context.Background(), r, s) + require.NoError(t, err) + require.Nil(t, result) + requireEqualFunc(t, sFnControllerConfiguration, next) + + require.EqualValues(t, expectedFlags, s.flagsBuilder.Build()) + require.Equal(t, v1alpha1.StateProcessing, s.instance.Status.State) + }) +} diff --git a/components/operator/internal/state/remove_finalizer.go b/components/operator/internal/state/remove_finalizer.go new file mode 100644 index 00000000..e5e9c406 --- /dev/null +++ b/components/operator/internal/state/remove_finalizer.go @@ -0,0 +1,17 @@ +package state + +import ( + "context" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +func sFnRemoveFinalizer(ctx context.Context, r *reconciler, s *systemState) (stateFn, *ctrl.Result, error) { + if !controllerutil.RemoveFinalizer(&s.instance, r.finalizer) { + return requeue() + } + + err := updateDockerRegistryWithoutStatus(ctx, r, s) + return stopWithEventualError(err) +} diff --git a/components/operator/internal/state/remove_finalizer_test.go b/components/operator/internal/state/remove_finalizer_test.go new file mode 100644 index 00000000..e2da8273 --- /dev/null +++ b/components/operator/internal/state/remove_finalizer_test.go @@ -0,0 +1,68 @@ +package state + +import ( + "context" + "testing" + + "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func Test_sFnRemoveFinalizer(t *testing.T) { + t.Run("remove finalizer", func(t *testing.T) { + scheme := scheme.Scheme + require.NoError(t, v1alpha1.AddToScheme(scheme)) + instance := v1alpha1.DockerRegistry{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + Finalizers: []string{ + v1alpha1.Finalizer, + }, + }, + } + r := &reconciler{ + cfg: cfg{ + finalizer: v1alpha1.Finalizer, + }, + k8s: k8s{ + client: fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(&instance). + Build(), + }, + } + s := &systemState{ + instance: instance, + } + + // remove finalizer + next, result, err := sFnRemoveFinalizer(context.Background(), r, s) + require.Nil(t, err) + require.Nil(t, result) + require.Nil(t, next) + }) + + t.Run("requeue when is no finalizer", func(t *testing.T) { + r := &reconciler{ + cfg: cfg{ + finalizer: v1alpha1.Finalizer, + }, + } + s := &systemState{ + instance: v1alpha1.DockerRegistry{ + ObjectMeta: metav1.ObjectMeta{}, + }, + } + + // remove finalizer + next, result, err := sFnRemoveFinalizer(context.Background(), r, s) + require.Nil(t, err) + require.Equal(t, &ctrl.Result{Requeue: true}, result) + require.Nil(t, next) + }) +} diff --git a/components/operator/internal/state/served_filter.go b/components/operator/internal/state/served_filter.go new file mode 100644 index 00000000..6b74e7fa --- /dev/null +++ b/components/operator/internal/state/served_filter.go @@ -0,0 +1,50 @@ +package state + +import ( + "context" + "fmt" + + "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + ctrl "sigs.k8s.io/controller-runtime" +) + +func sFnServedFilter(ctx context.Context, r *reconciler, s *systemState) (stateFn, *ctrl.Result, error) { + if s.instance.IsServedEmpty() { + if err := setInitialServed(ctx, r, s); err != nil { + return stopWithEventualError(err) + } + } + + if s.instance.Status.Served == v1alpha1.ServedFalse { + return stop() + } + return nextState(sFnAddFinalizer) +} + +func setInitialServed(ctx context.Context, r *reconciler, s *systemState) error { + servedDockerRegistry, err := GetServedDockerRegistry(ctx, r.k8s.client) + if err != nil { + return err + } + + return setServed(servedDockerRegistry, s) +} + +func setServed(servedDockerRegistry *v1alpha1.DockerRegistry, s *systemState) error { + if servedDockerRegistry == nil { + s.setServed(v1alpha1.ServedTrue) + return nil + } + + s.setServed(v1alpha1.ServedFalse) + s.setState(v1alpha1.StateWarning) + err := fmt.Errorf( + "Only one instance of DockerRegistry is allowed (current served instance: %s/%s). This DockerRegistry CR is redundant. Remove it to fix the problem.", + servedDockerRegistry.GetNamespace(), servedDockerRegistry.GetName()) + s.instance.UpdateConditionFalse( + v1alpha1.ConditionTypeConfigured, + v1alpha1.ConditionReasonDuplicated, + err, + ) + return err +} diff --git a/components/operator/internal/state/served_filter_test.go b/components/operator/internal/state/served_filter_test.go new file mode 100644 index 00000000..7b9585de --- /dev/null +++ b/components/operator/internal/state/served_filter_test.go @@ -0,0 +1,136 @@ +package state + +import ( + "context" + "testing" + + "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + apiruntime "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func Test_sFnServedFilter(t *testing.T) { + t.Run("skip processing when served is false", func(t *testing.T) { + s := &systemState{ + instance: v1alpha1.DockerRegistry{ + Status: v1alpha1.DockerRegistryStatus{ + Served: v1alpha1.ServedFalse, + }, + }, + } + + nextFn, result, err := sFnServedFilter(context.TODO(), nil, s) + require.Nil(t, err) + require.Nil(t, result) + require.Nil(t, nextFn) + }) + + t.Run("do next step when served is true", func(t *testing.T) { + s := &systemState{ + instance: v1alpha1.DockerRegistry{ + Status: v1alpha1.DockerRegistryStatus{ + Served: v1alpha1.ServedTrue, + }, + }, + } + + nextFn, result, err := sFnServedFilter(context.TODO(), nil, s) + require.Nil(t, err) + require.Nil(t, result) + requireEqualFunc(t, sFnAddFinalizer, nextFn) + }) + + t.Run("set served value from nil to true when there is no served dockerregistry on cluster", func(t *testing.T) { + s := &systemState{ + instance: v1alpha1.DockerRegistry{ + Status: v1alpha1.DockerRegistryStatus{}, + }, + } + + r := &reconciler{ + k8s: k8s{ + client: func() client.Client { + scheme := apiruntime.NewScheme() + require.NoError(t, v1alpha1.AddToScheme(scheme)) + + client := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects( + fixServedDockerRegistry("test-1", "default", ""), + fixServedDockerRegistry("test-2", "dockerregistry-test", v1alpha1.ServedFalse), + fixServedDockerRegistry("test-3", "dockerregistry-test-2", ""), + fixServedDockerRegistry("test-4", "default", v1alpha1.ServedFalse), + ).Build() + + return client + }(), + }, + } + + nextFn, result, err := sFnServedFilter(context.TODO(), r, s) + require.Nil(t, err) + require.Nil(t, result) + requireEqualFunc(t, sFnAddFinalizer, nextFn) + require.Equal(t, v1alpha1.ServedTrue, s.instance.Status.Served) + }) + + t.Run("set served value from nil to false and set condition to error when there is at lease one served dockerregistry on cluster", func(t *testing.T) { + s := &systemState{ + instance: v1alpha1.DockerRegistry{ + Status: v1alpha1.DockerRegistryStatus{}, + }, + } + + r := &reconciler{ + k8s: k8s{ + client: func() client.Client { + scheme := apiruntime.NewScheme() + require.NoError(t, v1alpha1.AddToScheme(scheme)) + + client := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects( + fixServedDockerRegistry("test-1", "default", v1alpha1.ServedFalse), + fixServedDockerRegistry("test-2", "dockerregistry-test", v1alpha1.ServedTrue), + fixServedDockerRegistry("test-3", "dockerregistry-test-2", ""), + fixServedDockerRegistry("test-4", "default", v1alpha1.ServedFalse), + ).Build() + + return client + }(), + }, + } + + nextFn, result, err := sFnServedFilter(context.TODO(), r, s) + + expectedErrorMessage := "Only one instance of DockerRegistry is allowed (current served instance: dockerregistry-test/test-2). This DockerRegistry CR is redundant. Remove it to fix the problem." + require.EqualError(t, err, expectedErrorMessage) + require.Nil(t, result) + require.Nil(t, nextFn) + require.Equal(t, v1alpha1.ServedFalse, s.instance.Status.Served) + + status := s.instance.Status + require.Equal(t, v1alpha1.StateWarning, status.State) + requireContainsCondition(t, status, + v1alpha1.ConditionTypeConfigured, + metav1.ConditionFalse, + v1alpha1.ConditionReasonDuplicated, + expectedErrorMessage, + ) + }) +} + +func fixServedDockerRegistry(name, namespace string, served v1alpha1.Served) *v1alpha1.DockerRegistry { + return &v1alpha1.DockerRegistry{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Status: v1alpha1.DockerRegistryStatus{ + Served: served, + }, + } +} diff --git a/components/operator/internal/state/state.go b/components/operator/internal/state/state.go new file mode 100644 index 00000000..fdbf8a99 --- /dev/null +++ b/components/operator/internal/state/state.go @@ -0,0 +1,65 @@ +package state + +import ( + "time" + + "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" +) + +var requeueResult = &ctrl.Result{ + Requeue: true, +} + +func nextState(next stateFn) (stateFn, *ctrl.Result, error) { + return next, nil, nil +} + +func stopWithEventualError(err error) (stateFn, *ctrl.Result, error) { + return nil, nil, err +} + +func stop() (stateFn, *ctrl.Result, error) { + return nil, nil, nil +} + +func requeue() (stateFn, *ctrl.Result, error) { + return nil, requeueResult, nil +} + +func requeueAfter(duration time.Duration) (stateFn, *ctrl.Result, error) { + return nil, &ctrl.Result{ + RequeueAfter: duration, + }, nil +} + +type fieldsToUpdate []struct { + specField string + statusField *string + fieldName string + defaultValue string +} + +func updateStatusFields(eventRecorder record.EventRecorder, instance *v1alpha1.DockerRegistry, fields fieldsToUpdate) { + for _, field := range fields { + // set default value if spec field is empty + if field.specField == "" { + field.specField = field.defaultValue + } + + if field.specField != *field.statusField { + oldStatusValue := *field.statusField + *field.statusField = field.specField + eventRecorder.Eventf( + instance, + "Normal", + string(v1alpha1.ConditionReasonConfiguration), + "%s set from '%s' to '%s'", + field.fieldName, + oldStatusValue, + field.specField, + ) + } + } +} diff --git a/components/operator/internal/state/state_test.go b/components/operator/internal/state/state_test.go new file mode 100644 index 00000000..b957ac67 --- /dev/null +++ b/components/operator/internal/state/state_test.go @@ -0,0 +1,110 @@ +package state + +import ( + "context" + "reflect" + "runtime" + "strings" + "testing" + + "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + "github.com/kyma-project/docker-registry/components/operator/internal/chart" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +var ( + testInstalledDockerRegistry = v1alpha1.DockerRegistry{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + Status: v1alpha1.DockerRegistryStatus{ + Conditions: []metav1.Condition{ + { + Type: string(v1alpha1.ConditionTypeConfigured), + Status: metav1.ConditionTrue, + Reason: string(v1alpha1.ConditionReasonConfiguration), + }, + { + Type: string(v1alpha1.ConditionTypeInstalled), + Status: metav1.ConditionTrue, + Reason: string(v1alpha1.ConditionReasonInstallation), + }, + }, + State: v1alpha1.StateReady, + }, + } +) + +func fixEmptyManifestCache() chart.ManifestCache { + return fixManifestCache("---") +} + +func fixManifestCache(manifest string) chart.ManifestCache { + cache := chart.NewInMemoryManifestCache() + _ = cache.Set(context.Background(), types.NamespacedName{ + Name: testInstalledDockerRegistry.GetName(), + Namespace: testInstalledDockerRegistry.GetNamespace(), + }, chart.DockerRegistrySpecManifest{Manifest: manifest, CustomFlags: map[string]interface{}{}}) + + return cache +} + +func requireEqualFunc(t *testing.T, expected, actual stateFn) { + require.NotNil(t, actual) + + expectedFnName := getFnName(expected) + actualFnName := getFnName(actual) + + if expectedFnName == actualFnName { + // return if functions are simply same + return + } + + expectedElems := strings.Split(expectedFnName, "/") + actualElems := strings.Split(actualFnName, "/") + + // check package paths (prefix) + require.Equal(t, + strings.Join(expectedElems[0:len(expectedElems)-2], "/"), + strings.Join(actualElems[0:len(actualElems)-2], "/"), + ) + + // check direct fn names (suffix) + require.Equal(t, + getDirectFnName(expectedElems[len(expectedElems)-1]), + getDirectFnName(actualElems[len(actualElems)-1]), + ) +} + +func getDirectFnName(nameSuffix string) string { + elements := strings.Split(nameSuffix, ".") + for i := range elements { + elemI := len(elements) - i - 1 + if !strings.HasPrefix(elements[elemI], "func") { + return elements[elemI] + } + } + + return "" +} + +func getFnName(fn stateFn) string { + return runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name() +} + +func requireContainsCondition(t *testing.T, status v1alpha1.DockerRegistryStatus, + conditionType v1alpha1.ConditionType, conditionStatus metav1.ConditionStatus, conditionReason v1alpha1.ConditionReason, conditionMessage string) { + hasExpectedCondition := false + for _, condition := range status.Conditions { + if condition.Type == string(conditionType) { + require.Equal(t, string(conditionReason), condition.Reason) + require.Equal(t, conditionStatus, condition.Status) + require.Equal(t, conditionMessage, condition.Message) + hasExpectedCondition = true + } + } + require.True(t, hasExpectedCondition) +} diff --git a/components/operator/internal/state/update_status.go b/components/operator/internal/state/update_status.go new file mode 100644 index 00000000..dffea574 --- /dev/null +++ b/components/operator/internal/state/update_status.go @@ -0,0 +1,25 @@ +package state + +import ( + "context" + "reflect" + "time" +) + +var ( + requeueDuration = time.Second * 3 +) + +func updateDockerRegistryWithoutStatus(ctx context.Context, r *reconciler, s *systemState) error { + return r.client.Update(ctx, &s.instance) +} + +func updateDockerRegistryStatus(ctx context.Context, r *reconciler, s *systemState) error { + if !reflect.DeepEqual(s.instance.Status, s.statusSnapshot) { + err := r.client.Status().Update(ctx, &s.instance) + emitEvent(r, s) + s.saveStatusSnapshot() + return err + } + return nil +} diff --git a/components/operator/internal/state/utils.go b/components/operator/internal/state/utils.go new file mode 100644 index 00000000..ee04ab2e --- /dev/null +++ b/components/operator/internal/state/utils.go @@ -0,0 +1,46 @@ +package state + +import ( + "context" + + "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + "github.com/pkg/errors" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func GetDockerRegistryOrServed(ctx context.Context, req ctrl.Request, c client.Client) (*v1alpha1.DockerRegistry, error) { + instance := &v1alpha1.DockerRegistry{} + err := c.Get(ctx, req.NamespacedName, instance) + if err == nil { + return instance, nil + } + if !k8serrors.IsNotFound(err) { + return nil, errors.Wrap(err, "while fetching dockerregistry instance") + } + + instance, err = GetServedDockerRegistry(ctx, c) + if err != nil { + return nil, errors.Wrap(err, "while fetching served dockerregistry instance") + } + return instance, nil +} + +func GetServedDockerRegistry(ctx context.Context, c client.Client) (*v1alpha1.DockerRegistry, error) { + var dockerRegistryList v1alpha1.DockerRegistryList + + err := c.List(ctx, &dockerRegistryList) + + if err != nil { + return nil, err + } + + for _, item := range dockerRegistryList.Items { + if !item.IsServedEmpty() && item.Status.Served == v1alpha1.ServedTrue { + return &item, nil + } + } + + return nil, nil +} diff --git a/components/operator/internal/state/verify.go b/components/operator/internal/state/verify.go new file mode 100644 index 00000000..d326657e --- /dev/null +++ b/components/operator/internal/state/verify.go @@ -0,0 +1,49 @@ +package state + +import ( + "context" + + "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + "github.com/kyma-project/docker-registry/components/operator/internal/chart" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// verify if all workloads are in ready state +func sFnVerifyResources(_ context.Context, r *reconciler, s *systemState) (stateFn, *ctrl.Result, error) { + ready, err := chart.Verify(s.chartConfig) + if err != nil { + r.log.Warnf("error while verifying resource %s: %s", + client.ObjectKeyFromObject(&s.instance), err.Error()) + s.setState(v1alpha1.StateError) + s.instance.UpdateConditionFalse( + v1alpha1.ConditionTypeInstalled, + v1alpha1.ConditionReasonInstallationErr, + err, + ) + return stopWithEventualError(err) + } + + if !ready { + return requeueAfter(requeueDuration) + } + + warning := s.warningBuilder.Build() + if warning != "" { + s.setState(v1alpha1.StateWarning) + s.instance.UpdateConditionTrue( + v1alpha1.ConditionTypeInstalled, + v1alpha1.ConditionReasonInstalled, + warning, + ) + return stop() + } + + s.setState(v1alpha1.StateReady) + s.instance.UpdateConditionTrue( + v1alpha1.ConditionTypeInstalled, + v1alpha1.ConditionReasonInstalled, + "DockerRegistry installed", + ) + return stop() +} diff --git a/components/operator/internal/state/verify_test.go b/components/operator/internal/state/verify_test.go new file mode 100644 index 00000000..f7782896 --- /dev/null +++ b/components/operator/internal/state/verify_test.go @@ -0,0 +1,178 @@ +package state + +import ( + "context" + "testing" + + "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + "github.com/kyma-project/docker-registry/components/operator/internal/chart" + "github.com/kyma-project/docker-registry/components/operator/internal/warning" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +var ( + testDeployCR = &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-deploy", + Namespace: "default", + }, + Status: appsv1.DeploymentStatus{ + Conditions: []appsv1.DeploymentCondition{ + { + Type: appsv1.DeploymentAvailable, + Status: corev1.ConditionUnknown, + }, + }, + }, + } +) + +const ( + testDeployManifest = ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-deploy + namespace: default +` +) + +func Test_sFnVerifyResources(t *testing.T) { + t.Run("ready", func(t *testing.T) { + s := &systemState{ + warningBuilder: warning.NewBuilder(), + instance: *testInstalledDockerRegistry.DeepCopy(), + chartConfig: &chart.Config{ + Cache: fixEmptyManifestCache(), + CacheKey: types.NamespacedName{ + Name: testInstalledDockerRegistry.GetName(), + Namespace: testInstalledDockerRegistry.GetNamespace(), + }, + }, + } + r := &reconciler{ + log: zap.NewNop().Sugar(), + k8s: k8s{ + client: fake.NewClientBuilder().Build(), + }, + } + + // verify and return update condition state + next, result, err := sFnVerifyResources(context.Background(), r, s) + require.Nil(t, err) + require.Nil(t, result) + require.Nil(t, next) + + status := s.instance.Status + require.Equal(t, v1alpha1.StateReady, status.State) + require.Len(t, status.Conditions, 2) + requireContainsCondition(t, status, + v1alpha1.ConditionTypeInstalled, + metav1.ConditionTrue, + v1alpha1.ConditionReasonInstalled, + "DockerRegistry installed", + ) + }) + + t.Run("warning", func(t *testing.T) { + s := &systemState{ + warningBuilder: warning.NewBuilder().With("test warning"), + instance: *testInstalledDockerRegistry.DeepCopy(), + chartConfig: &chart.Config{ + Cache: fixEmptyManifestCache(), + CacheKey: types.NamespacedName{ + Name: testInstalledDockerRegistry.GetName(), + Namespace: testInstalledDockerRegistry.GetNamespace(), + }, + }, + } + r := &reconciler{ + log: zap.NewNop().Sugar(), + } + + // verify and return update condition state + next, result, err := sFnVerifyResources(context.Background(), r, s) + require.Nil(t, err) + require.Nil(t, result) + require.Nil(t, next) + + status := s.instance.Status + require.Equal(t, v1alpha1.StateWarning, status.State) + requireContainsCondition(t, status, + v1alpha1.ConditionTypeInstalled, + metav1.ConditionTrue, + v1alpha1.ConditionReasonInstalled, + s.warningBuilder.Build(), + ) + }) + + t.Run("verify error", func(t *testing.T) { + s := &systemState{ + instance: *testInstalledDockerRegistry.DeepCopy(), + chartConfig: &chart.Config{ + Cache: fixManifestCache("\t"), + CacheKey: types.NamespacedName{ + Name: testInstalledDockerRegistry.GetName(), + Namespace: testInstalledDockerRegistry.GetNamespace(), + }, + }, + } + r := &reconciler{ + log: zap.NewNop().Sugar(), + } + + // handle verify err and update condition with err + next, result, err := sFnVerifyResources(context.Background(), r, s) + require.EqualError(t, err, "could not parse chart manifest: yaml: found character that cannot start any token") + require.Nil(t, result) + require.Nil(t, next) + + status := s.instance.Status + require.Equal(t, v1alpha1.StateError, status.State) + requireContainsCondition(t, status, + v1alpha1.ConditionTypeInstalled, + metav1.ConditionFalse, + v1alpha1.ConditionReasonInstallationErr, + "could not parse chart manifest: yaml: found character that cannot start any token", + ) + }) + + t.Run("requeue when resources are not ready", func(t *testing.T) { + client := fake.NewClientBuilder().WithObjects(testDeployCR).Build() + s := &systemState{ + instance: *testInstalledDockerRegistry.DeepCopy(), + chartConfig: &chart.Config{ + Cache: func() chart.ManifestCache { + cache := chart.NewInMemoryManifestCache() + _ = cache.Set(context.Background(), types.NamespacedName{ + Name: testInstalledDockerRegistry.GetName(), + Namespace: testInstalledDockerRegistry.GetNamespace(), + }, chart.DockerRegistrySpecManifest{Manifest: testDeployManifest}) + return cache + }(), + CacheKey: types.NamespacedName{ + Name: testInstalledDockerRegistry.GetName(), + Namespace: testInstalledDockerRegistry.GetNamespace(), + }, + Cluster: chart.Cluster{ + Client: client, + }, + }, + } + r := &reconciler{} + + // return requeue on verification failed + next, result, err := sFnVerifyResources(context.Background(), r, s) + + _, expectedResult, _ := requeueAfter(requeueDuration) + require.NoError(t, err) + require.Equal(t, expectedResult, result) + require.Nil(t, next) + }) +} diff --git a/components/operator/internal/tracing/watcher.go b/components/operator/internal/tracing/watcher.go new file mode 100644 index 00000000..15397c69 --- /dev/null +++ b/components/operator/internal/tracing/watcher.go @@ -0,0 +1,57 @@ +package tracing + +import ( + "context" + + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/workqueue" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +const ( + tracingOTLPService = "telemetry-otlp-traces" +) + +type eventHandler struct{} + +func (e eventHandler) Create(_ context.Context, event event.CreateEvent, q workqueue.RateLimitingInterface) { + if event.Object == nil { + return + } + svcName := event.Object.GetName() + if svcName != tracingOTLPService { + return + } + q.Add(reconcile.Request{NamespacedName: types.NamespacedName{ + Name: event.Object.GetName(), + Namespace: event.Object.GetNamespace(), + }}) +} + +func (e eventHandler) Update(_ context.Context, _ event.UpdateEvent, _ workqueue.RateLimitingInterface) { +} + +func (e eventHandler) Delete(_ context.Context, event event.DeleteEvent, q workqueue.RateLimitingInterface) { + if event.Object == nil { + return + } + svcName := event.Object.GetName() + if svcName != tracingOTLPService { + return + } + q.Add(reconcile.Request{NamespacedName: types.NamespacedName{ + Name: event.Object.GetName(), + Namespace: event.Object.GetNamespace(), + }}) +} + +func (e eventHandler) Generic(_ context.Context, _ event.GenericEvent, _ workqueue.RateLimitingInterface) { +} + +var _ handler.EventHandler = eventHandler{} + +func ServiceCollectorWatcher() handler.EventHandler { + return &eventHandler{} +} diff --git a/components/operator/internal/warning/warning.go b/components/operator/internal/warning/warning.go new file mode 100644 index 00000000..44a32f76 --- /dev/null +++ b/components/operator/internal/warning/warning.go @@ -0,0 +1,27 @@ +package warning + +import ( + "fmt" + "strings" +) + +type Builder struct { + warnings []string +} + +func NewBuilder() *Builder { + return &Builder{} +} + +func (w *Builder) With(warning string) *Builder { + w.warnings = append(w.warnings, warning) + return w +} + +func (w *Builder) Build() string { + msg := "" + if len(w.warnings) > 0 { + msg = fmt.Sprintf("Warning: %s", strings.Join(w.warnings, "; ")) + } + return msg +} diff --git a/components/operator/internal/warning/warning_test.go b/components/operator/internal/warning/warning_test.go new file mode 100644 index 00000000..c34d0bfa --- /dev/null +++ b/components/operator/internal/warning/warning_test.go @@ -0,0 +1,22 @@ +package warning + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestBuilder_Build(t *testing.T) { + t.Run("build multiple warnings", func(t *testing.T) { + warning := NewBuilder(). + With("warn 1"). + With("warn 2"). + Build() + + require.Equal(t, "Warning: warn 1; warn 2", warning) + }) + t.Run("build empty warning", func(t *testing.T) { + warning := NewBuilder().Build() + require.Equal(t, "", warning) + }) +} diff --git a/components/operator/main.go b/components/operator/main.go new file mode 100644 index 00000000..67b8d80f --- /dev/null +++ b/components/operator/main.go @@ -0,0 +1,218 @@ +/* +Copyright 2022. + +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 main + +import ( + "context" + "flag" + "github.com/kyma-project/docker-registry/components/operator/internal/registry" + "os" + "time" + + "github.com/kyma-project/docker-registry/components/operator/internal/config" + "github.com/kyma-project/docker-registry/components/operator/internal/gitrepository" + "github.com/pkg/errors" + + // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) + // to ensure that exec-entrypoint and run can make use of them. + uberzap "go.uber.org/zap" + uberzapcore "go.uber.org/zap/zapcore" + _ "k8s.io/client-go/plugin/pkg/client/auth" + + k8s "github.com/kyma-project/docker-registry/components/operator/internal/controllers/kubernetes" + internalresource "github.com/kyma-project/docker-registry/components/operator/internal/resource" + corev1 "k8s.io/api/core/v1" + apiextensionsscheme "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme" + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + ctrlcache "sigs.k8s.io/controller-runtime/pkg/cache" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics/server" + + operatorv1alpha1 "github.com/kyma-project/docker-registry/components/operator/api/v1alpha1" + "github.com/kyma-project/docker-registry/components/operator/controllers" + //+kubebuilder:scaffold:imports +) + +var ( + scheme = runtime.NewScheme() + setupLog = ctrl.Log.WithName("setup") + syncPeriod = time.Minute * 30 + cleanupTimeout = time.Second * 10 +) + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + + utilruntime.Must(operatorv1alpha1.AddToScheme(scheme)) + + utilruntime.Must(apiextensionsscheme.AddToScheme(scheme)) + + //+kubebuilder:scaffold:scheme +} + +func main() { + var metricsAddr string + var probeAddr string + flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") + flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + + opts := zap.Options{ + Development: true, + TimeEncoder: uberzapcore.TimeEncoderOfLayout("Jan 02 15:04:05.000000000"), + } + opts.BindFlags(flag.CommandLine) + flag.Parse() + + cfg, err := config.GetConfig("") + if err != nil { + setupLog.Error(err, "while getting config") + os.Exit(1) + } + + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + + ctx, cancel := context.WithTimeout(context.Background(), cleanupTimeout) + defer cancel() + + setupLog.Info("cleaning orphan deprecated resources") + err = cleanupOrphanDeprecatedResources(ctx) + if err != nil { + setupLog.Error(err, "while removing orphan resources") + os.Exit(1) + } + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: scheme, + Metrics: ctrlmetrics.Options{ + BindAddress: metricsAddr, + }, + HealthProbeBindAddress: probeAddr, + Cache: ctrlcache.Options{ + SyncPeriod: &syncPeriod, + }, + Client: ctrlclient.Options{ + Cache: &ctrlclient.CacheOptions{ + DisableFor: []ctrlclient.Object{ + &corev1.Secret{}, + &corev1.ConfigMap{}, + }, + }, + }, + // TODO: use our own logger - now eventing use logger with different message format + }) + if err != nil { + setupLog.Error(err, "unable to start manager") + os.Exit(1) + } + + config := uberzap.NewDevelopmentConfig() + config.EncoderConfig.TimeKey = "timestamp" + config.EncoderConfig.EncodeTime = opts.TimeEncoder + config.DisableCaller = true + + reconcilerLogger, err := config.Build() + if err != nil { + setupLog.Error(err, "unable to setup logger") + os.Exit(1) + } + + reconciler := controllers.NewDockerRegistryReconciler( + mgr.GetClient(), mgr.GetConfig(), + mgr.GetEventRecorderFor("dockerregistry-operator"), + reconcilerLogger.Sugar(), + cfg.ChartPath) + + //TODO: get it from some configuration + configKubernetes := k8s.Config{ + BaseNamespace: "kyma-system", + BaseDefaultSecretName: registry.SecretName, + ExcludedNamespaces: []string{"kyma-system"}, + ConfigMapRequeueDuration: time.Minute, + SecretRequeueDuration: time.Minute, + ServiceAccountRequeueDuration: time.Minute, + } + + resourceClient := internalresource.New(mgr.GetClient(), scheme) + secretSvc := k8s.NewSecretService(resourceClient, configKubernetes) + configMapSvc := k8s.NewConfigMapService(resourceClient, configKubernetes) + serviceAccountSvc := k8s.NewServiceAccountService(resourceClient, configKubernetes) + + if err = reconciler.SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "DockerRegistry") + os.Exit(1) + } + + namespaceLogger, err := config.Build() + if err != nil { + setupLog.Error(err, "unable to setup logger") + os.Exit(1) + } + + if err := k8s.NewNamespace(mgr.GetClient(), namespaceLogger.Sugar(), configKubernetes, configMapSvc, secretSvc, serviceAccountSvc). + SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create Namespace controller") + os.Exit(1) + } + + secretLogger, err := config.Build() + if err != nil { + setupLog.Error(err, "unable to setup logger") + os.Exit(1) + } + + if err := k8s.NewSecret(mgr.GetClient(), secretLogger.Sugar(), configKubernetes, secretSvc). + SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create Secret controller") + os.Exit(1) + } + //+kubebuilder:scaffold:builder + + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up health check") + os.Exit(1) + } + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up ready check") + os.Exit(1) + } + + setupLog.Info("starting manager") + if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + setupLog.Error(err, "problem running manager") + os.Exit(1) + } +} + +func cleanupOrphanDeprecatedResources(ctx context.Context) error { + // We are going to talk to the API server _before_ we start the manager. + // Since the default manager client reads from cache, we will get an error. + // So, we create a "serverClient" that would read from the API directly. + // We only use it here, this only runs at start up, so it shouldn't be to much for the API + serverClient, err := ctrlclient.New(ctrl.GetConfigOrDie(), ctrlclient.Options{ + Scheme: scheme, + }) + if err != nil { + return errors.Wrap(err, "failed to create a server client") + } + + return gitrepository.Cleanup(ctx, serverClient) +} diff --git a/config.yaml b/config.yaml new file mode 100644 index 00000000..423459f8 --- /dev/null +++ b/config.yaml @@ -0,0 +1,7 @@ +# Samples Config +configs: +# TODO: Add optional manifest installation chart flags and value overrides +# The format below should be followed +# - name: nginx-ingress +# clientConfig: "CreateNamespace=true,Namespace=jakobs-new" +# overrides: "x=4" \ No newline at end of file diff --git a/config/docker-registry/.helmignore b/config/docker-registry/.helmignore new file mode 100644 index 00000000..50af0317 --- /dev/null +++ b/config/docker-registry/.helmignore @@ -0,0 +1,22 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/config/docker-registry/Chart.yaml b/config/docker-registry/Chart.yaml new file mode 100644 index 00000000..8988fbf1 --- /dev/null +++ b/config/docker-registry/Chart.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +description: Kyma component 'docker registry' +name: docker-registry +version: 1.0.0 +home: https://kyma-project.io +icon: https://github.com/kyma-project/kyma/blob/main/logo.png?raw=true +dependencies: + - name: docker-registry \ No newline at end of file diff --git a/config/docker-registry/README.md b/config/docker-registry/README.md new file mode 100644 index 00000000..01d334e7 --- /dev/null +++ b/config/docker-registry/README.md @@ -0,0 +1,15 @@ +# Function Controller + +## Overview + +This project contains the chart for the Function Controller. + +> **NOTE**: This feature is experimental. + +## Prerequisites + +- Kubernetes cluster (v1.16.3) + +## Details + +To learn how Serverless is used in Kyma, see its [official documentation](https://kyma-project.io/#/serverless-manager/user/README.md). diff --git a/config/docker-registry/charts/docker-registry/.helmignore b/config/docker-registry/charts/docker-registry/.helmignore new file mode 100644 index 00000000..f0c13194 --- /dev/null +++ b/config/docker-registry/charts/docker-registry/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/config/docker-registry/charts/docker-registry/Chart.yaml b/config/docker-registry/charts/docker-registry/Chart.yaml new file mode 100644 index 00000000..c8e41309 --- /dev/null +++ b/config/docker-registry/charts/docker-registry/Chart.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +description: A Helm chart for Docker Registry +name: docker-registry +version: 1.9.1 +appVersion: 2.7.1 +home: https://hub.docker.com/_/registry/ +icon: https://hub.docker.com/public/images/logos/mini-logo.svg +sources: + - https://github.com/docker/distribution-library-image +maintainers: + - name: jpds + email: jpds@protonmail.com + - name: rendhalver + email: pete.brown@powerhrg.com diff --git a/config/docker-registry/charts/docker-registry/README.md b/config/docker-registry/charts/docker-registry/README.md new file mode 100644 index 00000000..b9ff4612 --- /dev/null +++ b/config/docker-registry/charts/docker-registry/README.md @@ -0,0 +1,79 @@ +# Docker Registry Helm Chart + +This directory contains a Kubernetes chart to deploy a private Docker Registry. + +## Prerequisites + +* Persistence Volume (PV) support on underlying infrastructure (if persistence is required) + +## Chart Details + +This chart implements the Docker Registry deployment. + +## Installing the Chart + +To install the chart, use the following command: + +```bash +helm install stable/docker-registry +``` + +## Configuration + +The following table lists the configurable parameters of the `docker-registry` chart and +their default values. + +| Parameter | Description | Default | +|:----------------------------|:-------------------------------------------------------------------------------------------|:----------------| +| `image.pullPolicy` | Container pull policy | `IfNotPresent` | +| `image.repository` | Container image to use | `registry` | +| `image.tag` | Container image tag to deploy | `2.7.1` | +| `persistence.accessMode` | Access mode to use for PVC | `ReadWriteOnce` | +| `persistence.enabled` | Whether to use a PVC for the Docker storage | `false` | +| `persistence.deleteEnabled` | Enable the deletion of image blobs and manifests by digest | `nil` | +| `persistence.size` | Amount of space to claim for PVC | `10Gi` | +| `persistence.storageClass` | Storage Class to use for PVC | `-` | +| `persistence.existingClaim` | Name of an existing PVC to use for config | `nil` | +| `service.port` | TCP port on which the service is exposed | `5000` | +| `service.type` | Service type | `ClusterIP` | +| `service.clusterIP` | If `service.type` is `ClusterIP` and this is non-empty, sets the cluster IP of the service | `nil` | +| `service.nodePort` | If `service.type` is `NodePort` and this is non-empty, sets the node port of the service | `nil` | +| `replicaCount` | Kubernetes replicas | `1` | +| `updateStrategy` | update strategy for deployment | `{}` | +| `podAnnotations` | Annotations for Pod | `{}` | +| `podLabels` | Labels for Pod | `{}` | +| `podDisruptionBudget` | Pod disruption budget | `{}` | +| `resources.limits.cpu` | Container requested CPU | `nil` | +| `resources.limits.memory` | Container requested memory | `nil` | +| `storage` | Storage system to use | `filesystem` | +| `tlsSecretName` | Name of Secret for TLS certs | `nil` | +| `secrets.htpasswd` | Htpasswd authentication | `nil` | +| `secrets.s3.accessKey` | Access Key for S3 configuration | `nil` | +| `secrets.s3.secretKey` | Secret Key for S3 configuration | `nil` | +| `secrets.swift.username` | Username for Swift configuration | `nil` | +| `secrets.swift.password` | Password for Swift configuration | `nil` | +| `haSharedSecret` | Shared Secret for Registry | `nil` | +| `configData` | Configuration hash for Docker | `nil` | +| `s3.region` | S3 region | `nil` | +| `s3.regionEndpoint` | S3 region endpoint | `nil` | +| `s3.bucket` | S3 bucket name | `nil` | +| `s3.encrypt` | Store images in encrypted format | `nil` | +| `s3.secure` | Use HTTPS | `nil` | +| `swift.authurl` | Swift authurl | `nil` | +| `swift.container` | Swift container | `nil` | +| `nodeSelector` | node labels for Pod assignment | `{}` | +| `tolerations` | Pod tolerations | `[]` | +| `ingress.enabled` | If true, Ingress will be created | `false` | +| `ingress.annotations` | Ingress annotations | `{}` | +| `ingress.labels` | Ingress labels | `{}` | +| `ingress.path` | Ingress service path | `/` | +| `ingress.hosts` | Ingress hostnames | `[]` | +| `ingress.tls` | Ingress TLS configuration (YAML) | `[]` | +| `extraVolumeMounts` | Additional volumeMounts to the registry container | `[]` | +| `extraVolumes` | Additional volumes to the pod | `[]` | + +Specify each parameter using the `--set key=value[,key=value]` argument with +`helm install`. + +To generate htpasswd file, run this Docker command: +`docker run --entrypoint htpasswd registry:2 -Bbn user password > ./htpasswd`. diff --git a/config/docker-registry/charts/docker-registry/templates/_helpers.tpl b/config/docker-registry/charts/docker-registry/templates/_helpers.tpl new file mode 100644 index 00000000..a91077ef --- /dev/null +++ b/config/docker-registry/charts/docker-registry/templates/_helpers.tpl @@ -0,0 +1,24 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "docker-registry.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "docker-registry.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/config/docker-registry/charts/docker-registry/templates/configmap.yaml b/config/docker-registry/charts/docker-registry/templates/configmap.yaml new file mode 100644 index 00000000..08ee9011 --- /dev/null +++ b/config/docker-registry/charts/docker-registry/templates/configmap.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "docker-registry.fullname" . }}-config + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "docker-registry.name" . }} + chart: {{ .Chart.Name }}-{{ .Chart.Version }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +data: + config.yml: |- +{{ toYaml .Values.configData | indent 4 }} diff --git a/config/docker-registry/charts/docker-registry/templates/deployment.yaml b/config/docker-registry/charts/docker-registry/templates/deployment.yaml new file mode 100644 index 00000000..338ca576 --- /dev/null +++ b/config/docker-registry/charts/docker-registry/templates/deployment.yaml @@ -0,0 +1,231 @@ +{{- $rollme := include "tplValue" ( dict "value" .Values.rollme "context" . ) -}} +{{- $registryHTTPSecret := include "tplValue" ( dict "value" .Values.registryHTTPSecret "context" . ) -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "docker-registry.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "docker-registry.name" . }} + chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + release: {{ .Release.Name }} +spec: + selector: + matchLabels: + app: {{ template "docker-registry.name" . }} + release: {{ .Release.Name }} + replicas: {{ .Values.replicaCount }} + strategy: + type: Recreate + rollingUpdate: null + minReadySeconds: 5 + template: + metadata: + labels: + app: {{ template "docker-registry.name" . }} + release: {{ .Release.Name }} + {{- if .Values.podLabels }} +{{ toYaml .Values.podLabels | indent 8 }} + {{- end }} + annotations: + rollme: {{ $rollme | quote }} +{{- if $.Values.podAnnotations }} +{{ toYaml $.Values.podAnnotations | indent 8 }} +{{- end }} + spec: + {{- if .Values.imagePullSecrets }} + imagePullSecrets: +{{ toYaml .Values.imagePullSecrets | indent 8 }} + {{- end }} + priorityClassName: "{{ .Values.global.dockerregistryPriorityClassName }}" +{{- if .Values.pod.securityContext }} + securityContext: + {{- include "tplValue" ( dict "value" .Values.pod.securityContext "context" . ) | nindent 12 }} +{{- end }} + hostNetwork: false # Optional. The default is false if the entry is not there. + hostPID: false # Optional. The default is false if the entry is not there. + hostIPC: false # Optional. The default is false if the entry is not there. + initContainers: + - name: generate-htpasswd + image: "{{ include "imageurl" (dict "reg" .Values.global.containerRegistry "img" .Values.global.images.registry) }}" +{{- if .Values.initContainers.securityContext }} + securityContext: + {{- include "tplValue" ( dict "value" .Values.initContainers.securityContext "context" . ) | nindent 12 }} +{{- end }} + volumeMounts: + {{- if eq .Values.storage "filesystem" }} + - name: data + mountPath: /var/lib/registry/ + {{- end }} + - name: registry-credentials + mountPath: /regcred + readOnly: true + {{- with .Values.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + command: + - sh + - -ec + - | + htpasswd -Bbn $(cat /regcred/username.txt) $(cat /regcred/password.txt) > ./data/htpasswd + echo "Generated htpasswd file for docker-registry..." +{{- if eq .Values.storage "filesystem" }} + chown -R 1000:1000 "/var/lib/registry/" +{{- end }} + + containers: + - name: {{ .Chart.Name }} + image: "{{ include "imageurl" (dict "reg" .Values.global.containerRegistry "img" .Values.global.images.registry) }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} +{{- if .Values.containers.securityContext }} + securityContext: + {{- include "tplValue" ( dict "value" .Values.containers.securityContext "context" . ) | nindent 12 }} +{{- end }} + command: + - /bin/registry + - serve + - /etc/docker/registry/config.yml + ports: + - containerPort: 5000 + livenessProbe: + httpGet: +{{- if .Values.tlsSecretName }} + scheme: HTTPS +{{- end }} + path: / + port: 5000 + readinessProbe: + httpGet: +{{- if .Values.tlsSecretName }} + scheme: HTTPS +{{- end }} + path: / + port: 5000 + resources: +{{ toYaml .Values.resources | indent 12 }} + env: + - name: REGISTRY_AUTH + value: "htpasswd" + - name: REGISTRY_AUTH_HTPASSWD_REALM + value: "Registry Realm" + - name: REGISTRY_AUTH_HTPASSWD_PATH + value: "/data/htpasswd" + - name: REGISTRY_HTTP_SECRET + # https://docs.docker.com/registry/configuration/#http, there's no problem that it is plainly seen + # using kubectl describe + value: {{ $registryHTTPSecret | quote }} +{{- if .Values.tlsSecretName }} + - name: REGISTRY_HTTP_TLS_CERTIFICATE + value: /etc/ssl/docker/tls.crt + - name: REGISTRY_HTTP_TLS_KEY + value: /etc/ssl/docker/tls.key +{{- end }} +{{- if eq .Values.storage "filesystem" }} + - name: REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY + value: "/var/lib/registry" +{{- else if eq .Values.storage "azure" }} + - name: REGISTRY_STORAGE_AZURE_ACCOUNTNAME + valueFrom: + secretKeyRef: + name: {{ template "docker-registry.fullname" . }}-secret + key: azureAccountName + - name: REGISTRY_STORAGE_AZURE_ACCOUNTKEY + valueFrom: + secretKeyRef: + name: {{ template "docker-registry.fullname" . }}-secret + key: azureAccountKey + - name: REGISTRY_STORAGE_AZURE_CONTAINER + valueFrom: + secretKeyRef: + name: {{ template "docker-registry.fullname" . }}-secret + key: azureContainer +{{- else if eq .Values.storage "s3" }} + {{- if and .Values.secrets.s3.secretKey .Values.secrets.s3.accessKey }} + - name: REGISTRY_STORAGE_S3_ACCESSKEY + valueFrom: + secretKeyRef: + name: {{ template "docker-registry.fullname" . }}-secret + key: s3AccessKey + - name: REGISTRY_STORAGE_S3_SECRETKEY + valueFrom: + secretKeyRef: + name: {{ template "docker-registry.fullname" . }}-secret + key: s3SecretKey + {{- end }} + - name: REGISTRY_STORAGE_S3_REGION + value: {{ required ".Values.s3.region is required" .Values.s3.region }} + {{- if .Values.s3.regionEndpoint }} + - name: REGISTRY_STORAGE_S3_REGIONENDPOINT + value: {{ .Values.s3.regionEndpoint }} + {{- end }} + - name: REGISTRY_STORAGE_S3_BUCKET + value: {{ required ".Values.s3.bucket is required" .Values.s3.bucket }} + {{- if .Values.s3.encrypt }} + - name: REGISTRY_STORAGE_S3_ENCRYPT + value: {{ .Values.s3.encrypt | quote }} + {{- end }} + {{- if .Values.s3.secure }} + - name: REGISTRY_STORAGE_S3_SECURE + value: {{ .Values.s3.secure | quote }} + {{- end }} +{{- else if eq .Values.storage "swift" }} + - name: REGISTRY_STORAGE_SWIFT_AUTHURL + value: {{ required ".Values.swift.authurl is required" .Values.swift.authurl }} + - name: REGISTRY_STORAGE_SWIFT_USERNAME + valueFrom: + secretKeyRef: + name: {{ template "docker-registry.fullname" . }}-secret + key: swiftUsername + - name: REGISTRY_STORAGE_SWIFT_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "docker-registry.fullname" . }}-secret + key: swiftPassword + - name: REGISTRY_STORAGE_SWIFT_CONTAINER + value: {{ required ".Values.swift.container is required" .Values.swift.container }} +{{- end }} + volumeMounts: +{{- if eq .Values.storage "filesystem" }} + - name: data + mountPath: /var/lib/registry/ +{{- end }} + - name: "{{ template "docker-registry.fullname" . }}-config" + mountPath: "/etc/docker/registry" +{{- if .Values.tlsSecretName }} + - mountPath: /etc/ssl/docker + name: tls-cert + readOnly: true +{{- end }} +{{- with .Values.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} +{{- end }} + +{{- if .Values.nodeSelector }} + nodeSelector: +{{ toYaml .Values.nodeSelector | indent 8 }} +{{- end }} +{{- if .Values.tolerations }} + tolerations: +{{ toYaml .Values.tolerations | indent 8 }} +{{- end }} + volumes: +{{- if eq .Values.storage "filesystem" }} + - name: data + {{- if .Values.persistence.enabled }} + persistentVolumeClaim: + claimName: {{ if .Values.persistence.existingClaim }}{{ .Values.persistence.existingClaim }}{{- else }}{{ template "docker-registry.fullname" . }}{{- end }} + {{- else }} + emptyDir: {} + {{- end -}} +{{- end }} + - name: {{ template "docker-registry.fullname" . }}-config + configMap: + name: {{ template "docker-registry.fullname" . }}-config +{{- if .Values.tlsSecretName }} + - name: tls-cert + secret: + secretName: {{ .Values.tlsSecretName }} +{{- end }} +{{- with .Values.extraVolumes }} + {{- toYaml . | nindent 8 }} +{{- end }} diff --git a/config/docker-registry/charts/docker-registry/templates/ingress.yaml b/config/docker-registry/charts/docker-registry/templates/ingress.yaml new file mode 100644 index 00000000..3060a29f --- /dev/null +++ b/config/docker-registry/charts/docker-registry/templates/ingress.yaml @@ -0,0 +1,37 @@ +{{- if .Values.ingress.enabled -}} +{{- $serviceName := include "docker-registry.fullname" . -}} +{{- $servicePort := .Values.service.port -}} +{{- $path := .Values.ingress.path -}} +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: {{ template "docker-registry.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "docker-registry.name" . }} + chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- if .Values.ingress.labels }} +{{ toYaml .Values.ingress.labels | indent 4 }} +{{- end }} + annotations: + {{- range $key, $value := .Values.ingress.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} +spec: + rules: + {{- range $host := .Values.ingress.hosts }} + - host: {{ $host }} + http: + paths: + - path: {{ $path }} + backend: + serviceName: {{ $serviceName }} + servicePort: {{ $servicePort }} + {{- end -}} + {{- if .Values.ingress.tls }} + tls: +{{ toYaml .Values.ingress.tls | indent 4 }} + {{- end -}} +{{- end -}} diff --git a/config/docker-registry/charts/docker-registry/templates/poddisruptionbudget.yaml b/config/docker-registry/charts/docker-registry/templates/poddisruptionbudget.yaml new file mode 100644 index 00000000..fa5f1a7c --- /dev/null +++ b/config/docker-registry/charts/docker-registry/templates/poddisruptionbudget.yaml @@ -0,0 +1,18 @@ +{{- if .Values.podDisruptionBudget -}} +apiVersion: policy/v1beta1 +kind: PodDisruptionBudget +metadata: + name: {{ template "docker-registry.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "docker-registry.name" . }} + chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: + selector: + matchLabels: + app: {{ template "docker-registry.name" . }} + release: {{ .Release.Name }} +{{ toYaml .Values.podDisruptionBudget | indent 2 }} +{{- end -}} diff --git a/config/docker-registry/charts/docker-registry/templates/priorityclass.yaml b/config/docker-registry/charts/docker-registry/templates/priorityclass.yaml new file mode 100644 index 00000000..4717d7d2 --- /dev/null +++ b/config/docker-registry/charts/docker-registry/templates/priorityclass.yaml @@ -0,0 +1,7 @@ +apiVersion: scheduling.k8s.io/v1 +kind: PriorityClass +metadata: + name: {{ .Values.global.dockerregistryPriorityClassName }} +value: {{ .Values.global.dockerregistryPriorityClassValue }} +globalDefault: false +description: "Scheduling priority of dockerregistry components. By default, dockerregistry components should not be blocked by unschedulable user workloads." \ No newline at end of file diff --git a/config/docker-registry/charts/docker-registry/templates/pvc.yaml b/config/docker-registry/charts/docker-registry/templates/pvc.yaml new file mode 100644 index 00000000..96da5061 --- /dev/null +++ b/config/docker-registry/charts/docker-registry/templates/pvc.yaml @@ -0,0 +1,27 @@ +{{- if .Values.persistence.enabled }} +{{- if not .Values.persistence.existingClaim -}} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ template "docker-registry.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "docker-registry.fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + accessModes: + - {{ .Values.persistence.accessMode | quote }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} +{{- if .Values.persistence.storageClass }} +{{- if (eq "-" .Values.persistence.storageClass) }} + storageClassName: "" +{{- else }} + storageClassName: "{{ .Values.persistence.storageClass }}" +{{- end }} +{{- end }} +{{- end }} +{{- end -}} diff --git a/config/docker-registry/charts/docker-registry/templates/secret.yaml b/config/docker-registry/charts/docker-registry/templates/secret.yaml new file mode 100644 index 00000000..c5e04ab3 --- /dev/null +++ b/config/docker-registry/charts/docker-registry/templates/secret.yaml @@ -0,0 +1,31 @@ +{{- if or (eq .Values.storage "azure") (eq .Values.storage "s3") (eq .Values.storage "swift") }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "docker-registry.fullname" . }}-secret + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "docker-registry.name" . }} + chart: {{ .Chart.Name }}-{{ .Chart.Version }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +type: Opaque +data: + {{- if eq .Values.storage "azure" }} + {{- if and .Values.secrets.azure.accountName .Values.secrets.azure.accountKey .Values.secrets.azure.container }} + azureAccountName: {{ .Values.secrets.azure.accountName | b64enc | quote }} + azureAccountKey: {{ .Values.secrets.azure.accountKey | b64enc | quote }} + azureContainer: {{ .Values.secrets.azure.container | b64enc | quote }} + {{- end }} + {{- else if eq .Values.storage "s3" }} + {{- if and .Values.secrets.s3.secretKey .Values.secrets.s3.accessKey }} + s3AccessKey: {{ .Values.secrets.s3.accessKey | b64enc | quote }} + s3SecretKey: {{ .Values.secrets.s3.secretKey | b64enc | quote }} + {{- end }} + {{- else if eq .Values.storage "swift" }} + {{- if and .Values.secrets.swift.username .Values.secrets.swift.password }} + swiftUsername: {{ .Values.secrets.swift.username | b64enc | quote }} + swiftPassword: {{ .Values.secrets.swift.password | b64enc | quote }} + {{- end }} + {{- end }} + {{- end}} \ No newline at end of file diff --git a/config/docker-registry/charts/docker-registry/templates/service.yaml b/config/docker-registry/charts/docker-registry/templates/service.yaml new file mode 100644 index 00000000..cda1a453 --- /dev/null +++ b/config/docker-registry/charts/docker-registry/templates/service.yaml @@ -0,0 +1,25 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "docker-registry.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "docker-registry.name" . }} + chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- if .Values.service.annotations }} + annotations: +{{ toYaml .Values.service.annotations | indent 4 }} +{{- end }} +spec: + type: NodePort + ports: + - port: {{ .Values.global.registryServicePort }} + protocol: TCP + name: http-{{ .Values.service.name }} + targetPort: {{ .Values.global.registryServicePort }} + nodePort: {{ .Values.global.registryNodePort }} + selector: + app: {{ template "docker-registry.name" . }} + release: {{ .Release.Name }} diff --git a/config/docker-registry/charts/docker-registry/values.yaml b/config/docker-registry/charts/docker-registry/values.yaml new file mode 100644 index 00000000..62756567 --- /dev/null +++ b/config/docker-registry/charts/docker-registry/values.yaml @@ -0,0 +1,171 @@ +# Default values for docker-registry. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. +replicaCount: 1 + +updateStrategy: + type: Recreate + rollingUpdate: null + # maxSurge: 1 + # maxUnavailable: 0 + +image: + pullPolicy: IfNotPresent +# imagePullSecrets: + # - name: docker +service: + name: registry + port: "{{ .Values.global.registryServicePort }}" # same as configData.http.addr + annotations: {} +ingress: + enabled: false + path: / + # Used to create an Ingress record. + hosts: + - chart-example.local + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + labels: {} + tls: + # Secrets must be manually created in the namespace. + # - secretName: chart-example-tls + # hosts: + # - chart-example.local +resources: + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + limits: + cpu: 400m + memory: 800Mi + requests: + cpu: 10m + memory: 300Mi + +podAnnotations: + sidecar.istio.io/inject: "false" +podLabels: {} + +persistence: + accessMode: 'ReadWriteOnce' + enabled: true + size: 20Gi + # storageClass: '-' + +# set the type of filesystem to use: filesystem, s3. +# If filesystem is used, you should also add it to configData, below +storage: filesystem + +# Set this to name of secret for tls certs +# tlsSecretName: registry.docker.example.com + +# Secrets for Azure +# azure: +# accountName: "" +# accountKey: "" +# container: "" +# Secrets for S3 access and secret keys +# s3: +# accessKey: "" +# secretKey: "" +# Secrets for Swift username and password +# swift: +# username: "" +# password: "" + +# Options for s3 storage type: +# s3: +# region: us-east-1 +# regionEndpoint: s3.us-east-1.amazonaws.com +# bucket: my-bucket +# encrypt: false +# secure: true + +# Options for swift storage type: +# swift: +# authurl: http://swift.example.com/ +# container: my-container + +# https://docs.docker.com/registry/configuration/ +configData: # example: https://github.com/docker/distribution/blob/master/cmd/registry/config-dev.yml + version: 0.1 + log: + formatter: json + fields: + service: registry + storage: + cache: + blobdescriptor: inmemory + filesystem: + rootdirectory: /var/lib/registry + http: + addr: :5000 # same as .Values.service.port + headers: + X-Content-Type-Options: [nosniff] + debug: + addr: :5001 + prometheus: + enabled: true + path: /metrics + health: + storagedriver: + enabled: true + interval: 10s + threshold: 3 + +containers: + # the following guidelines should be followed for this https://github.com/kyma-project/community/tree/main/concepts/psp-replacement + securityContext: + privileged: false + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + procMount: default # Optional. The default is false if the entry is not there. + readOnlyRootFilesystem: true # Mandatory + +initContainers: + # the following guidelines should be followed for this https://github.com/kyma-project/community/tree/main/concepts/psp-replacement + securityContext: + # this is required to allow the initContainer to chmod the volumemount for the registry storage volume. This is incompatible with the security requirements above and should be fixed in the future. + runAsUser: 0 + runAsGroup: 0 + privileged: false + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + add: ["CHOWN"] + procMount: default # Optional. The default is false if the entry is not there. + readOnlyRootFilesystem: true # Mandatory + +pod: + # the following guidelines should be followed for this https://github.com/kyma-project/community/tree/main/concepts/psp-replacement + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + seccompProfile: # Optional. This option can also be set on container level but it is recommended to set it on Pod level and leave it undefined on container level. + type: RuntimeDefault + + +podDisruptionBudget: {} + # maxUnavailable: 1 + # minAvailable: 2 + +nodeSelector: {} + +tolerations: [] + +extraVolumeMounts: [] + +extraVolumes: [] + +nameOverride: +fullnameOverride: + +destinationRule: + enabled: true + +rollme: "" +registryHTTPSecret: "" diff --git a/config/docker-registry/templates/_helpers.tpl b/config/docker-registry/templates/_helpers.tpl new file mode 100644 index 00000000..8fc39071 --- /dev/null +++ b/config/docker-registry/templates/_helpers.tpl @@ -0,0 +1,49 @@ +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Renders a value that contains template. +Usage: +{{- include "tplValue" ( dict "value" .Values.path.to.the.Value "context" $ ) }} +*/}} +{{- define "tplValue" -}} + {{- if typeIs "string" .value }} + {{- tpl .value .context }} + {{- else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "registry-fullname" -}} +{{- "internal-docker-registry" -}} +{{- end -}} + + +{{/* +Create a URL for container images +*/}} +{{- define "imageurl" -}} +{{- $registry := default $.reg.path $.img.containerRegistryPath -}} +{{- $path := ternary (print $registry) (print $registry "/" $.img.directory) (empty $.img.directory) -}} +{{- $version := ternary (print ":" $.img.version) (print "@sha256:" $.img.sha) (empty $.img.sha) -}} +{{- print $path "/" $.img.name $version -}} +{{- end -}} diff --git a/config/docker-registry/templates/registry-config.yaml b/config/docker-registry/templates/registry-config.yaml new file mode 100644 index 00000000..b099644f --- /dev/null +++ b/config/docker-registry/templates/registry-config.yaml @@ -0,0 +1,21 @@ +{{- $username := include "tplValue" ( dict "value" .Values.dockerRegistry.username "context" . ) -}} +{{- $password := include "tplValue" ( dict "value" .Values.dockerRegistry.password "context" . ) -}} +{{- $encodedUsernamePassword := printf "%s:%s" $username $password | b64enc }} +{{- $internalRegPullAddr := printf "localhost:%d" (int .Values.global.registryNodePort) }} +{{- $internalRegPushAddr := printf "%s.%s.svc.cluster.local:%d" (include "registry-fullname" . ) .Release.Namespace ( int .Values.global.registryServicePort) }} + +apiVersion: v1 +kind: Secret +type: kubernetes.io/dockerconfigjson +metadata: + name: internal-dockerregistry-config + namespace: {{ .Release.Namespace }} + labels: + dockerregistry.kyma-project.io/config: credentials +data: + username: "{{ $username | b64enc }}" + password: "{{ $password | b64enc }}" + isInternal: {{ "true" | b64enc }} + pullRegAddr: {{ $internalRegPullAddr | b64enc }} + pushRegAddr: "{{ $internalRegPushAddr | b64enc }}" + .dockerconfigjson: "{{- (printf "{\"auths\": {\"%s\": {\"auth\": \"%s\"}, \"%s\": {\"auth\": \"%s\"}}}" $internalRegPushAddr $encodedUsernamePassword $internalRegPullAddr $encodedUsernamePassword) | b64enc }}" diff --git a/config/docker-registry/values.yaml b/config/docker-registry/values.yaml new file mode 100644 index 00000000..ffc9f205 --- /dev/null +++ b/config/docker-registry/values.yaml @@ -0,0 +1,47 @@ +# Default values for dockerregistry. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +fullnameOverride: "dockerregistry" +global: + registryServicePort: 5000 + registryNodePort: 32137 + containerRegistry: + path: europe-docker.pkg.dev/kyma-project + images: + registry: + name: "tpi/registry" + version: "2.8.1-1ae4c190" + directory: "prod" + dockerregistryPriorityClassValue: 2000000 + dockerregistryPriorityClassName: "dockerregistry-priority" +dockerRegistry: + username: "{{ randAlphaNum 20 | b64enc }}" # for gcr "_json_key" + password: "{{ randAlphaNum 40 | b64enc }}" # for gcr data from json key + # This is the registry address, for dockerhub it's username, for other it's url. + registryAddress: "" + # This is the server address of the registry which will be used to create docker configuration. + serverAddress: "" +docker-registry: + fullnameOverride: "internal-docker-registry" + destinationRule: + enabled: true + secrets: + haSharedSecret: "secret" + htpasswd: "generated-in-init-container" + extraVolumeMounts: + - name: htpasswd-data + mountPath: /data + extraVolumes: + - name: registry-credentials + secret: + secretName: internal-dockerregistry-config + items: + - key: username + path: username.txt + - key: password + path: password.txt + - name: htpasswd-data + emptyDir: {} + rollme: "{{ randAlphaNum 5}}" + registryHTTPSecret: "{{ randAlphaNum 16 | b64enc }}" diff --git a/config/operator/base/crd/bases/operator.kyma-project.io_dockerregistries.yaml b/config/operator/base/crd/bases/operator.kyma-project.io_dockerregistries.yaml new file mode 100644 index 00000000..088e8e70 --- /dev/null +++ b/config/operator/base/crd/bases/operator.kyma-project.io_dockerregistries.yaml @@ -0,0 +1,165 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: dockerregistries.operator.kyma-project.io +spec: + group: operator.kyma-project.io + names: + kind: DockerRegistry + listKind: DockerRegistryList + plural: dockerregistries + singular: dockerregistry + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Configured')].status + name: Configured + type: string + - jsonPath: .status.conditions[?(@.type=='Installed')].status + name: Installed + type: string + - jsonPath: .metadata.generation + name: generation + type: integer + - jsonPath: .metadata.creationTimestamp + name: age + type: date + - jsonPath: .status.state + name: state + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: DockerRegistry is the Schema for the dockerregistry API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: DockerRegistrySpec defines the desired state of DockerRegistry + properties: + healthzLivenessTimeout: + description: Sets the timeout for the Function health check. The default + value in seconds is `10` + type: string + type: object + status: + properties: + conditions: + description: Conditions associated with CustomStatus. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + healthzLivenessTimeout: + type: string + secretName: + type: string + served: + description: |- + Served signifies that current DockerRegistry is managed. + Value can be one of ("True", "False"). + enum: + - "True" + - "False" + type: string + state: + description: |- + State signifies current state of DockerRegistry. + Value can be one of ("Ready", "Processing", "Error", "Deleting"). + enum: + - Processing + - Deleting + - Ready + - Error + - Warning + type: string + required: + - served + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/operator/base/crd/kustomization.yaml b/config/operator/base/crd/kustomization.yaml new file mode 100644 index 00000000..db1457e0 --- /dev/null +++ b/config/operator/base/crd/kustomization.yaml @@ -0,0 +1,10 @@ +# This kustomization.yaml is not intended to be run by itself, +# since it depends on service name and namespace that are out of this kustomize package. +# It should be run by config/default +resources: +- bases/operator.kyma-project.io_dockerregistries.yaml +#+kubebuilder:scaffold:crdkustomizeresource + +# the following config is for teaching kustomize how to do kustomization for CRDs. +configurations: +- kustomizeconfig.yaml diff --git a/config/operator/base/crd/kustomizeconfig.yaml b/config/operator/base/crd/kustomizeconfig.yaml new file mode 100644 index 00000000..ec5c150a --- /dev/null +++ b/config/operator/base/crd/kustomizeconfig.yaml @@ -0,0 +1,19 @@ +# This file is for teaching kustomize how to substitute name and namespace reference in CRD +nameReference: +- kind: Service + version: v1 + fieldSpecs: + - kind: CustomResourceDefinition + version: v1 + group: apiextensions.k8s.io + path: spec/conversion/webhook/clientConfig/service/name + +namespace: +- kind: CustomResourceDefinition + version: v1 + group: apiextensions.k8s.io + path: spec/conversion/webhook/clientConfig/service/namespace + create: false + +varReference: +- path: metadata/annotations diff --git a/config/operator/base/deployment/deployment.yaml b/config/operator/base/deployment/deployment.yaml new file mode 100644 index 00000000..33436299 --- /dev/null +++ b/config/operator/base/deployment/deployment.yaml @@ -0,0 +1,64 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: operator + namespace: system + labels: + control-plane: operator + app.kubernetes.io/name: deployment + app.kubernetes.io/instance: dockerregistry-operator + app.kubernetes.io/component: operator + app.kubernetes.io/created-by: dockerregistry-operator + app.kubernetes.io/part-of: dockerregistry-operator + app.kubernetes.io/managed-by: kustomize +spec: + selector: + matchLabels: + control-plane: operator + replicas: 1 + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + labels: + control-plane: operator + sidecar.istio.io/inject: "false" + spec: + securityContext: + runAsNonRoot: true + containers: + - command: + - /operator + image: controller:latest + name: manager + env: + - name: DOCKERREGISTRY_MANAGER_UID + valueFrom: + fieldRef: + fieldPath: metadata.uid + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - "ALL" + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + limits: + cpu: 1000m + memory: 512Mi + requests: + cpu: 10m + memory: 64Mi + serviceAccountName: operator + terminationGracePeriodSeconds: 10 diff --git a/config/operator/base/deployment/kustomization.yaml b/config/operator/base/deployment/kustomization.yaml new file mode 100644 index 00000000..2d833f82 --- /dev/null +++ b/config/operator/base/deployment/kustomization.yaml @@ -0,0 +1,8 @@ +resources: +- deployment.yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +images: +- name: controller + newName: dockerregistry-operator + newTag: 2d332b272e2a diff --git a/config/operator/base/kustomization.yaml b/config/operator/base/kustomization.yaml new file mode 100644 index 00000000..95f2c701 --- /dev/null +++ b/config/operator/base/kustomization.yaml @@ -0,0 +1,20 @@ +# Adds namespace to all resources. +namespace: kyma-system + +# Value of this field is prepended to the +# names of all resources, e.g. a deployment named +# "wordpress" becomes "alices-wordpress". +# Note that it should also match with the prefix (text before '-') of the namespace +# field above. +namePrefix: dockerregistry- + +# Labels to add to all resources and selectors. +commonLabels: + app.kubernetes.io/component: dockerregistry-operator.kyma-project.io + + +resources: +- ./crd +- ./deployment +- ./rbac +- ./ui-extensions diff --git a/config/operator/base/rbac/editor_role.yaml b/config/operator/base/rbac/editor_role.yaml new file mode 100644 index 00000000..00c0c261 --- /dev/null +++ b/config/operator/base/rbac/editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit dockerregistry. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: dockerregistry-operator-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: dockerregistry-operator + app.kubernetes.io/part-of: dockerregistry-operator + app.kubernetes.io/managed-by: kustomize + name: operator-editor-role +rules: +- apiGroups: + - operator.kyma-project.io + resources: + - dockerregistries + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - operator.kyma-project.io + resources: + - dockerregistries/status + verbs: + - get diff --git a/config/operator/base/rbac/kustomization.yaml b/config/operator/base/rbac/kustomization.yaml new file mode 100644 index 00000000..b508482b --- /dev/null +++ b/config/operator/base/rbac/kustomization.yaml @@ -0,0 +1,10 @@ +resources: +# All RBAC will be applied under this service account in +# the deployment namespace. You may comment out this resource +# if your operator will use a service account that exists at +# runtime. Be sure to update RoleBinding and ClusterRoleBinding +# subjects if changing service account names. +- service_account.yaml +- role.yaml +- role_binding.yaml + diff --git a/config/operator/base/rbac/role.yaml b/config/operator/base/rbac/role.yaml new file mode 100644 index 00000000..45f4f444 --- /dev/null +++ b/config/operator/base/rbac/role.yaml @@ -0,0 +1,288 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: operator-role +rules: +- apiGroups: + - "" + resources: + - configmaps + - secrets + - serviceaccounts + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - configmaps + - secrets + - serviceaccounts + - services + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - get + - list + - patch + - watch +- apiGroups: + - "" + resources: + - namespaces + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - nodes + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - persistentvolumeclaims + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - admissionregistration.k8s.io + resources: + - mutatingwebhookconfigurations + - validatingwebhookconfigurations + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - apps + resources: + - daemonsets + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - apps + resources: + - deployments + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - apps + resources: + - deployments/status + verbs: + - get +- apiGroups: + - apps + resources: + - replicasets + verbs: + - list +- apiGroups: + - autoscaling + resources: + - horizontalpodautoscalers + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - batch + resources: + - jobs + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - batch + resources: + - jobs/status + verbs: + - get +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - operator.kyma-project.io + resources: + - dockerregistries + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - operator.kyma-project.io + resources: + - dockerregistries/finalizers + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - operator.kyma-project.io + resources: + - dockerregistries/status + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - policy + resources: + - podsecuritypolicies + verbs: + - use +- apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterrolebindings + - clusterroles + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - rbac.authorization.k8s.io + resources: + - rolebindings + - roles + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - scheduling.k8s.io + resources: + - priorityclasses + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch diff --git a/config/operator/base/rbac/role_binding.yaml b/config/operator/base/rbac/role_binding.yaml new file mode 100644 index 00000000..3a699c7d --- /dev/null +++ b/config/operator/base/rbac/role_binding.yaml @@ -0,0 +1,19 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/name: clusterrolebinding + app.kubernetes.io/instance: dockerregistry-operator-rolebinding + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: dockerregistry-operator + app.kubernetes.io/part-of: dockerregistry-operator + app.kubernetes.io/managed-by: kustomize + name: operator-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: operator-role +subjects: +- kind: ServiceAccount + name: operator + namespace: system diff --git a/config/operator/base/rbac/service_account.yaml b/config/operator/base/rbac/service_account.yaml new file mode 100644 index 00000000..c60ca4da --- /dev/null +++ b/config/operator/base/rbac/service_account.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/name: serviceaccount + app.kubernetes.io/instance: dockerregistry-operator-sa + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: dockerregistry-operator + app.kubernetes.io/part-of: dockerregistry-operator + app.kubernetes.io/managed-by: kustomize + name: operator + namespace: system diff --git a/config/operator/base/rbac/viewer_role.yaml b/config/operator/base/rbac/viewer_role.yaml new file mode 100644 index 00000000..61a95ad7 --- /dev/null +++ b/config/operator/base/rbac/viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view dockerregistry. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: dockerregistry-operator-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: dockerregistry-operator + app.kubernetes.io/part-of: dockerregistry-operator + app.kubernetes.io/managed-by: kustomize + name: operator-viewer-role +rules: +- apiGroups: + - operator.kyma-project.io + resources: + - dockerregistries + verbs: + - get + - list + - watch +- apiGroups: + - operator.kyma-project.io + resources: + - dockerregistries/status + verbs: + - get diff --git a/config/operator/base/ui-extensions/dockerregistry/details b/config/operator/base/ui-extensions/dockerregistry/details new file mode 100644 index 00000000..d5cee36c --- /dev/null +++ b/config/operator/base/ui-extensions/dockerregistry/details @@ -0,0 +1,91 @@ +header: + - name: Ready + source: status.state + widget: Badge + highlights: + positive: + - 'Ready' +body: + - name: Desired Specification + widget: Panel + children: + - name: Docker Registry + visibility: $root.spec.dockerRegistry.enableInternal = true + source: spec.dockerRegistry.enableInternal?"INTERNAL":"" + - name: Docker Registry + visibility: '$exists($value)' + source: spec.dockerRegistry.secretName + widget: ResourceLink + resource: + name: spec.dockerRegistry.secretName + namespace: $root.metadata.namespace + kind: "'Secret'" + - name: Eventing Endpoint + source: spec.eventing.endpoint + visibility: '$exists($value)' + - name: OTLP Trace Endpoint + source: spec.tracing.endpoint + visibility: '$exists($value)' + - name: Default Resources Preset (Build-time) + source: spec.defaultBuildJobPreset + visibility: '$exists($value)' + - name: Default Resources Preset (Runtime) + source: spec.defaultRuntimePodPreset + visibility: '$exists($value)' + - name: Custom Build Execution Args + source: spec.functionBuildExecutorArgs + visibility: '$exists($value)' + - name: Max Simultaneous Builds + source: spec.functionBuildMaxSimultaneousJobs + visibility: '$exists($value)' + - name: Function Request Body Limit [Mb] + source: spec.functionRequestBodyLimitMb + visibility: '$exists($value)' + - name: Function Timeout [Sec] + source: spec.functionTimeoutSec + visibility: '$exists($value)' + - name: Function Requeue Duration + source: spec.functionRequeueDuration + visibility: '$exists($value)' + - name: Controller Liveness Timeout + source: spec.healthzLivenessTimeout + visibility: '$exists($value)' + - name: Target CPU utilisation for HPA + source: spec.targetCPUUtilizationPercentage + visibility: '$exists($value)' + - name: Status + widget: Panel + children: + - name: Docker Registry + source: status.dockerRegistry + - name: Eventing Endpoint + source: status.eventingEndpoint + - name: OTLP Trace Endpoint + source: status.tracingEndpoint + + - source: status.conditions + widget: Table + name: Reconciliation Conditions + children: + - source: type + name: Type + - source: status + name: Status + widget: Badge + highlights: + positive: + - 'True' + negative: + - 'False' + - source: reason + name: Reason + - source: message + name: Message + - source: '$readableTimestamp(lastTransitionTime)' + name: Last transition + sort: true + + - widget: EventList + filter: '$matchEvents($$, $root.kind, $root.metadata.name)' + name: events + defaultType: information diff --git a/config/operator/base/ui-extensions/dockerregistry/form b/config/operator/base/ui-extensions/dockerregistry/form new file mode 100644 index 00000000..90e23e78 --- /dev/null +++ b/config/operator/base/ui-extensions/dockerregistry/form @@ -0,0 +1,25 @@ +- path: spec.dockerRegistry.enableInternal + simple: true + name: Enable Internal Docker Registry +- simple: true + widget: Alert + severity: warning + alert: "'Internal Docker Registry is not recommended for production grade installations'" + visibility: "$root.spec.dockerRegistry.enableInternal = true" +- path: spec.dockerRegistry.secretName + visibility: $root.spec.dockerRegistry.enableInternal != true + simple: true + widget: Resource + name: External Docker Registry Configuration + resource: + kind: Secret + version: v1 + scope: namespace +- path: spec.tracing.endpoint + name: OTLP Trace Endpoint + simple: true + required: false +- path: spec.eventing.endpoint + name: Eventing Endpoint + simple: true + required: false \ No newline at end of file diff --git a/config/operator/base/ui-extensions/dockerregistry/general b/config/operator/base/ui-extensions/dockerregistry/general new file mode 100644 index 00000000..a02076a6 --- /dev/null +++ b/config/operator/base/ui-extensions/dockerregistry/general @@ -0,0 +1,15 @@ +resource: + kind: DockerRegistry + group: operator.kyma-project.io + version: v1alpha1 +urlPath: dockerregistries +category: Kyma +name: DockerRegistry +scope: namespace +features: + actions: + disableCreate: true + disableDelete: true +description: >- + {{[DockerRegistry CR](https://github.com/kyma-project/docker-registry/blob/main/config/samples/default-dockerregistry-cr.yaml)}} + specifies dockerregistry module. diff --git a/config/operator/base/ui-extensions/dockerregistry/kustomization.yaml b/config/operator/base/ui-extensions/dockerregistry/kustomization.yaml new file mode 100644 index 00000000..91a95ffb --- /dev/null +++ b/config/operator/base/ui-extensions/dockerregistry/kustomization.yaml @@ -0,0 +1,14 @@ +configMapGenerator: +- name: operator.kyma-project.io + namespace: kube-public + files: + - general + - form + - list + - details + options: + disableNameSuffixHash: true + labels: + app.kubernetes.io/name: dockerregistries.operator.kyma-project.io + busola.io/extension: resource + busola.io/extension-version: "0.5" \ No newline at end of file diff --git a/config/operator/base/ui-extensions/dockerregistry/list b/config/operator/base/ui-extensions/dockerregistry/list new file mode 100644 index 00000000..3c25b7c5 --- /dev/null +++ b/config/operator/base/ui-extensions/dockerregistry/list @@ -0,0 +1,6 @@ +- name: Ready + source: status.state + widget: Badge + highlights: + positive: + - 'Ready' \ No newline at end of file diff --git a/config/operator/base/ui-extensions/kustomization.yaml b/config/operator/base/ui-extensions/kustomization.yaml new file mode 100644 index 00000000..83221f54 --- /dev/null +++ b/config/operator/base/ui-extensions/kustomization.yaml @@ -0,0 +1,2 @@ +resources: +- dockerregistry \ No newline at end of file diff --git a/config/operator/dev/.gitignore b/config/operator/dev/.gitignore new file mode 100644 index 00000000..738a410d --- /dev/null +++ b/config/operator/dev/.gitignore @@ -0,0 +1 @@ +kustomization.yaml \ No newline at end of file diff --git a/config/operator/dev/kustomization.yaml.tpl b/config/operator/dev/kustomization.yaml.tpl new file mode 100644 index 00000000..412e9c94 --- /dev/null +++ b/config/operator/dev/kustomization.yaml.tpl @@ -0,0 +1,9 @@ +resources: +- ../base +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +# To overwrite image in base it has to point to the image in base kustomization.yaml +images: +- name: europe-docker.pkg.dev/kyma-project/prod/dockerregistry-operator + newName: local-registry + newTag: local diff --git a/config/samples/default-dockerregistry-cr.yaml b/config/samples/default-dockerregistry-cr.yaml new file mode 100644 index 00000000..9f9f1a1c --- /dev/null +++ b/config/samples/default-dockerregistry-cr.yaml @@ -0,0 +1,6 @@ +apiVersion: operator.kyma-project.io/v1alpha1 +kind: DockerRegistry +metadata: + name: default + namespace: kyma-system +spec: {} diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 84aca7bc..00000000 --- a/docs/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# Docs - -## Overview - -The `docs` folder contains two subfolders - `user` and `contributor`. - -The `user` subfolder contains the end-user documentation, which is displayed on the [Kyma website](https://kyma-project.io/#/). Depending on your module needs, the subfolder must include overview, usage, or technical reference documents. To display the content on the website properly, create a `_sidebar.md` file in the `user` subfolder and list the documents it contains there. For more information on how to publish user documentation, follow [this guide](https://github.com/kyma-project/community/blob/main/docs/guidelines/content-guidelines/01-user-docs.md). - -The `contributor` subfolder includes any developer-related documentation to help them manually install, develop, and operate a module. - -To have a common structure across all modules, all documents must be properly numbered according to the following structure: - -> **NOTE:** It is suggested to use the following titles if you have the content that matches them; otherwise use your own, more suitable titles, or simply skip the ones you find irrelevant. - - - 00-xx-overview - - 01-xx-tutorial/configuration - - 02-xx-usage - - 03-xx-troubleshooting - -where `xx` is the number of the given document. For example: - - ```bash - 00-00-overview-telemetry-manager - 00-10-overview-logs - 00-20-overview-traces - 00-30-overview-metrics - 01-10-configure-logs - 01-20-configure-traces - 01-30-configure-metrics - 02-10-use-logs - 02-20-use-traces - 02-30-use-metrics - (...) - ``` -> **NOTE:** Before introducing [docsify](https://docsify.js.org/#/?id=docsify), we agreed to use the `10`, `20`, `30` numbering. It was to help maintain the proper order of docs if they were rendered automatically on the website. With docsify, you manually add the content to the `_sidebar.md` file, and docs are displayed in the order you add them. However, this numbering is still recommended to have the unified structure of the docs in the module repositories. - -If you have other content that does not fit into the above topics, create your own 04-10-module-specific document(s). - -You can divide your documentation into subfolders to avoid having too many documents in one `docs/user` or `docs/contributor` folder. For example, if you have many technical reference documents, you can create a `technical reference` subfolder in `docs/user` and keep relevant documentation there. Each subfolder in the `user` folder must have its own `_sidebar.md` file with the links to the main module page and the list of docs it contains. \ No newline at end of file diff --git a/docs/assets/modular-serverless.svg b/docs/assets/modular-serverless.svg new file mode 100644 index 00000000..971e9080 --- /dev/null +++ b/docs/assets/modular-serverless.svg @@ -0,0 +1,4 @@ + + + +
Serverless Module
Serverless Module
Serverless
<<Custom Resource>>

spec:
<config>
status:
OK/ERR
Serverless...
watch 
watch 
reconcile
reconcile
reconcile (if configured)
reconcile (if configured)
Serverless Operator
<<Deployment>>
Serverless Operator...
watch
watch
reconcile
(configures ENVS)
reconcile...
serverless controller
<<Deployment>>
serverless controlle...
docker registry
docker registry
Function
<<CR>>
Function...
Eventing
<<CR>>

status:
 - publisherProxyEndpoint
Eventing...
Deployment
<<CR>>
(function runtime)
Deployment...
Serverless Module
Serverless Module
Serverless
<<Custom Resource>>

spec:
<config>
status:
 - health status
- used otlpEndpoints
-used publisherProxyEndpoint
Serverless...
watch 
watch 
reconcile
reconcile
reconcile (if configured)
reconcile (if configured)
watch
watch
reconcile/generate CA for
reconcile/generate CA for
Serverless Operator
<<Deployment>>
Serverless Operator...
watch
watch
reconcile
(configures ENVS)
reconcile...
serverless controller
<<Deployment>>
serverless controlle...
docker registry
docker registry
Function
<<CR>>
Function...
Telemetry
<<CR>>

status:
 - traceOTLPEndpoint
 - metricOTLPEndpoint
Telemetry...
Deployment
<<CR>>
(function runtime)
Deployment...
watch
watch
validates/converts
validates/converts
serverless webhook
<<Deployment>>
serverless webhook...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/assets/svls-add-ssh-key.png b/docs/assets/svls-add-ssh-key.png new file mode 100644 index 0000000000000000000000000000000000000000..32ddd1be1853366fce1a3e10339f1cd810383b74 GIT binary patch literal 59049 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�BmTHuAiFN) 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 D`#JuT literal 0 HcmV?d00001 diff --git a/docs/assets/svls-api-rules.svg b/docs/assets/svls-api-rules.svg new file mode 100644 index 00000000..db9f53a1 --- /dev/null +++ b/docs/assets/svls-api-rules.svg @@ -0,0 +1,4 @@ + + + +
User
User
creates
creates
API Gateway Controller
API Gateway Controller
APIRule CR
APIRule CR
listens for new CRs
listens for new CRs
1
1
2
2
Virtual Service
Virtual Service
creates
creates
3
3
Kyma cluster
Kyma cluster
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/assets/svls-architecture.svg b/docs/assets/svls-architecture.svg new file mode 100644 index 00000000..fde10d04 --- /dev/null +++ b/docs/assets/svls-architecture.svg @@ -0,0 +1,4 @@ + + + +
Function Controller
Function Controller
Function CR
(Function)
Function CR...
Docker image
Docker image
K8s Job
K8s Job
Defaulting
webhook
Defaulting...
Validation
webhook
Validation...
Deployment
Deployment
Service
Service
HorizontalPodAutoscaler
HorizontalPodAutoscaler
ConfigMap
ConfigMap
creates
creates
watches
watches
creates
creates
references
refer...
pushes
pushes
creates
creates
creates and watches
creates and watches
creates
creates
creates
creates
scales
scales
exposes
expos...
references
refer...
Kyma cluster
Kyma cluster
User
User
Docker registry
Docker registry
1
1
2
2
3
3
2
2
4
4
5
5
7
7
10
10
8
8
9
9
6
6
Reference key:
Reference key:

Dashed connectors and borders represent background processes
Dashed connectors and borders re...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/assets/svls-built.svg b/docs/assets/svls-built.svg new file mode 100644 index 00000000..b7cabd5a --- /dev/null +++ b/docs/assets/svls-built.svg @@ -0,0 +1,4 @@ + + + +
User
User
Create Job CR
Create Job CR
Create 
Function's image
Create...
Set Built 
condition to True
Set Built...
Delete all previous Job CRs labeled with Function's UID
Delete all previous...
creates Function
creates Function
updates Function
updates Function
Push image to Docker Registry 
Push image to Docke...
Don't change condition status
Don't change condition statu...
Is image rebuild required?
Is image reb...
yes
yes
no
no
Set Built 
condition to False
Set Built...
Set Built 
condition to Unknown
Set Built...
success
success
failure
failure
processing
processing
Kyma cluster
Kyma cluster
Reference key:
Reference key:

Dashed connectors and borders point to Function status conditions
Dashed connectors and borders po...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/assets/svls-configured.svg b/docs/assets/svls-configured.svg new file mode 100644 index 00000000..b236e712 --- /dev/null +++ b/docs/assets/svls-configured.svg @@ -0,0 +1,4 @@ + + + +
User
User
Create / Update ConfigMap
Create / Update Con...
Set Configured 
condition to True
Set Configured...
creates Function
creates Function
updates Function
updates Function
Don't change condition status
Don't change condition statu...
Is image rebuild required?
Is image reb...
yes
yes
no
no
Set Configured 
condition to False
Set Configured...
success
success
failure
failure
Kyma cluster
Kyma cluster
Reference key:
Reference key:

Dashed connectors and borders point to Function status conditions
Dashed connectors and borders po...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/assets/svls-create-ssh-key.png b/docs/assets/svls-create-ssh-key.png new file mode 100644 index 0000000000000000000000000000000000000000..e77b8011fb1563136f820058e7d12b31c3f6d0ae GIT binary patch literal 39233 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 L>ZPm;CJ+A`jDDp) literal 0 HcmV?d00001 diff --git a/docs/assets/svls-function-ui.png b/docs/assets/svls-function-ui.png new file mode 100644 index 0000000000000000000000000000000000000000..a484465943c8c0eb73627529af9895458cc9c0ae GIT binary patch literal 336968 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�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%0A?2}A}KdV!Z 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�#H5au;c$TFx(bCm93-md|M}(Lq?Mc(#(FBy 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

t4mM}k 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 literal 0 HcmV?d00001 diff --git a/docs/assets/svls-internal-registry.svg b/docs/assets/svls-internal-registry.svg new file mode 100644 index 00000000..4e053c08 --- /dev/null +++ b/docs/assets/svls-internal-registry.svg @@ -0,0 +1,4 @@ + + + +
K8s network
K8s network
Node 3
Node 3
Node 2
Node 2
Node 1
Node 1
internal image registry
internal imag...
kubelet
kubelet
kubelet
kubelet
kubelet
kubelet
node port
n...
node port
n...
node port
n...
k8s dns
k8s dns
function build job
function b...

Internal Image Registry - communication schema

Internal Image Registry - communication schema
1
1
3
3
4
4
2
2
5
5
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/assets/svls-kyma-cli-functions.png b/docs/assets/svls-kyma-cli-functions.png new file mode 100644 index 0000000000000000000000000000000000000000..7ab9acfbd1d650be6aff07f07b93ee4c9c453dc0 GIT binary patch literal 197634 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≤L3L;)YazJ!uvczMKJ^tx>l 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 cW78Y3>mQgI literal 0 HcmV?d00001 diff --git a/docs/assets/svls-running.svg b/docs/assets/svls-running.svg new file mode 100644 index 00000000..33f88631 --- /dev/null +++ b/docs/assets/svls-running.svg @@ -0,0 +1,4 @@ + + + +
User
User
Create/Update
Deployment, Service, and/or HorizontalPodAutoscaler
Create/Update...
Set Running 
condition to True
Set Running...
creates Function
creates Function
updates Function
updates Function
Don't change condition status
Don't change condition statu...
Is Deployment, Service, or HorizontalPodAutoscaler update required?
Is Deployment...
yes
yes
no
no
Set Running 
condition to False
Set Running...
Set Running 
condition to Unknown
Set Running...
success
success
failure
failure
processing
processing
Kyma cluster
Kyma cluster
Reference key:
Reference key:

Dashed connectors and borders point to Function status conditions
Dashed connectors and borders po...
Set Running 
condition to Unknown with reason MinimumReplcasUnavailable
Set Running...
unhealthy deployment
unhealthy d...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/assets/svls-settings.png b/docs/assets/svls-settings.png new file mode 100644 index 0000000000000000000000000000000000000000..6e46c4ecd64b95a0d4b0bd675ffa242f1ce52acf GIT binary patch literal 18152 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 [!TIP] +> See [sample Functions](technical-reference/07-10-sample-functions.md) for each available runtime. + +## Source Code + +You can also choose where you want to keep your Function's source code and dependencies. You can either place them directly in the Function CR under the **spec.source** and **spec.deps** fields as an **inline Function**, or store the code and dependencies in a public or private Git repository (**Git Functions**). Choosing the second option ensures your Function is versioned and gives you more development freedom in the choice of a project structure or an IDE. + +> [!TIP] +> Read more about [Git Functions](technical-reference/07-40-git-source-type.md). diff --git a/docs/user/00-20-configure-serverless.md b/docs/user/00-20-configure-serverless.md new file mode 100644 index 00000000..c047046b --- /dev/null +++ b/docs/user/00-20-configure-serverless.md @@ -0,0 +1,222 @@ +# Serverless Configuration + +## Overview + +The Serverless module has its own operator (Serverless operator). It watches the Serverless custom resource (CR) and reconfigures (reconciles) the Serverless workloads. + +The Serverless CR becomes an API to configure the Serverless module. You can use it to: + +- enable or disable the internal Docker registry +- configure the external Docker registry +- override endpoint for traces collected by the Serverless Functions +- override endpoint for eventing +- override the target CPU utilization percentage +- override the Function requeue duration +- override the Function build executor arguments +- override the Function build max simultaneous jobs +- override the healthz liveness timeout +- override the Function request body limit +- override the Function timeout +- override the default build Job preset +- override the default runtime Pod preset + +The default configuration of the Serverless Module is following: + + ```yaml + apiVersion: operator.kyma-project.io/v1alpha1 + kind: Serverless + metadata: + name: serverless-sample + spec: + dockerRegistry: + enableInternal: true + ``` + +## Configure Docker Registry + +By default, Serverless uses PersistentVolume (PV) as the internal registry to store Docker images for Functions. The default storage size of a single volume is 20 GB. This internal registry is suitable for local development. + +If you use Serverless for production purposes, it is recommended that you use an external registry, such as Docker Hub, Artifact Registry, or Azure Container Registry (ACR). + +Follow these steps to use the external Docker registry in Serverless: + +1. Create a Secret in the `kyma-system` namespace with the required data (`username`, `password`, `serverAddress`, and `registryAddress`): + + ```bash + kubectl create secret generic my-registry-config \ + --namespace kyma-system \ + --from-literal=username={USERNAME} \ + --from-literal=password={PASSWORD} \ + --from-literal=serverAddress={SERVER_URL} \ + --from-literal=registryAddress={REGISTRY_URL} + ``` + +> [!TIP] +> In case of DockerHub, usually the Docker registry address is the same as the account name. + +Examples: + + + +### **Docker Hub** + + ```bash + kubectl create secret generic my-registry-config \ + --namespace kyma-system \ + --from-literal=username={USERNAME} \ + --from-literal=password={PASSWORD} \ + --from-literal=serverAddress=https://index.docker.io/v1/ \ + --from-literal=registryAddress={USERNAME} + ``` + +### **Artifact Registry** + +To learn how to set up authentication for Docker with Artifact Registry, visit the [Artifact Registry documentation](https://cloud.google.com/artifact-registry/docs/docker/authentication#json-key). + + ```bash + kubectl create secret generic my-registry-config \ + --namespace kyma-system \ + --from-literal=username=_json_key \ + --from-literal=password={GCR_KEY_JSON} \ + --from-literal=serverAddress=gcr.io \ + --from-literal=registryAddress=gcr.io/{YOUR_GCR_PROJECT} + ``` + +### **ACR** + +To learn how to authenticate with ACR, visit the [ACR documentation](https://learn.microsoft.com/en-us/azure/container-registry/container-registry-authentication?tabs=azure-cli#az-acr-login-with---expose-token). + + ```bash + kubectl create secret generic my-registry-config \ + --namespace kyma-system \ + --from-literal=username=00000000-0000-0000-0000-000000000000 \ + --from-literal=password={ACR_TOKEN} \ + --from-literal=serverAddress={AZ_REGISTRY_NAME}.azurecr.io \ + --from-literal=registryAddress={AZ_REGISTRY_NAME}.azurecr.io + ``` + + + +2. Reference the Secret in the Serverless CR: + + ```yaml + spec: + dockerRegistry: + secretName: my-registry-config + ``` + +The URL of the currently used Docker registry is visible in the Serverless CR status. + +## Configure Trace Endpoint + +By default, the Serverless operator checks if there is a trace endpoint available. If available, the detected trace endpoint is used as the trace collector URL in Functions. +If no trace endpoint is detected, Functions are configured with no trace collector endpoint. +You can configure a custom trace endpoint so that Function traces are sent to any tracing backend you choose. +The currently used trace endpoint is visible in the Serverless CR status. + + ```yaml + spec: + tracing: + endpoint: http://jaeger-collector.observability.svc.cluster.local:4318/v1/traces + ``` + +## Configure Eventing Endpoint + +You can configure a custom eventing endpoint, so when you use SDK for sending events from your Functions, it is used to publish events. +The currently used trace endpoint is visible in the Serverless CR status. +By default `http://eventing-publisher-proxy.kyma-system.svc.cluster.local/publish` is used. + + ```yaml + spec: + eventing: + endpoint: http://eventing-publisher-proxy.kyma-system.svc.cluster.local/publish + ``` + +## Configure Target CPU Utilization Percentage + +You can set a custom target threshold for CPU utilization. The default value is set to `50%`. + +```yaml + spec: + targetCPUUtilizationPercentage: 50 +``` + +## Configure the Function Requeue Duration + +By default, the Function associated with the default configuration will be requeued every 5 minutes. + +```yaml + spec: + functionRequeueDuration: 5m +``` + +## Configure the Function Build Executor Arguments + +Use this label to choose the [arguments](https://github.com/GoogleContainerTools/kaniko?tab=readme-ov-file#additional-flags) passed to the Function build executor, for example: + +- `--insecure` - executor operates in an insecure mode +- `--skip-tls-verify` - executor skips the TLS certificate verification +- `--skip-unused-stages` - executor skips any stages that aren't used for the current execution +- `--log-format=text` - executor uses logs in a given format +- `--cache=true` - enables caching for the executor +- `--compressed-caching=false` - Prevents tar compression for cached layers. This will increase the runtime of the build, but decrease the memory usage especially for large builds. +- `--use-new-run` - Improves performance by avoiding the full filesystem snapshots. + +```yaml + spec: + functionBuildExecutorArgs: "--insecure,--skip-tls-verify,--skip-unused-stages,--log-format=text,--cache=true,--use-new-run,--compressed-caching=false" +``` + +## Configure the Function Build Max Simultaneous Jobs + +You can set a custom maximum number of simultaneous jobs which can run at the same time. The default value is set to `5`. + +```yaml + spec: + functionBuildMaxSimultaneousJobs: 5 +``` + +## Configure the healthz Liveness Timeout + +By default, the Function is considered unhealthy if the liveness health check endpoint does not respond within 10 seconds. + +```yaml + spec: + healthzLivenessTimeout: "10s" +``` + +## Configure the Function Request Body Limit + +Use this field to configure the maximum size limit for the request body of a Function. The default value is set to `1` megabyte. + +```yaml + spec: + functionRequestBodyLimitMb: 1 +``` + +## Configure the Function Timeout + +By default, the maximum execution time limit for a Function is set to `180` seconds. + +```yaml + spec: + functionTimeoutSec: 180 +``` + +## Configure the Default Build Job Preset + +You can configure the default build Job preset to be used. + +```yaml + spec: + defaultBuildJobPreset: "normal" +``` + +## Configure the Default Runtime Pod Preset + +You can configure the default runtime Pod preset to be used. + +```yaml + spec: + defaultRuntimePodPreset: "M" +``` diff --git a/docs/user/00-30-development-toolkit.md b/docs/user/00-30-development-toolkit.md new file mode 100644 index 00000000..e5e105a3 --- /dev/null +++ b/docs/user/00-30-development-toolkit.md @@ -0,0 +1,13 @@ +# Development Toolkit + +To start developing your first Functions, you need: + +- Self-hosted **Kubernetes cluster** and the **KUBECONFIG** file to authenticate to the cluster +- **Kyma** as the platform for managing the Function-related workloads +- [**Docker**](https://www.docker.com/) as the container runtime +- [**kubectl**](https://kubernetes.io/docs/reference/kubectl/kubectl/), the Kubernetes command-line tool, for running commands against clusters +- Development environment of your choice: + - **Kyma CLI** to easily initiate inline Functions or Git Functions locally, run, test, and later apply them in the clusters + - **Node.js** or **Python** + - **IDE** as the source code editor + - **Kyma dashboard** to manage Functions and related workloads through the graphical user interface diff --git a/docs/user/00-40-security-considerations.md b/docs/user/00-40-security-considerations.md new file mode 100644 index 00000000..2a0023f0 --- /dev/null +++ b/docs/user/00-40-security-considerations.md @@ -0,0 +1,20 @@ +# Security Considerations + +To eliminate potential security risks when using Functions, bear in mind these few facts: + +- Kyma provides base images for serverless runtimes. Those default runtimes are maintained with regards to commonly known security advisories. It is possible to use a custom runtime image (see this [tutorial](tutorials/01-110-override-runtime-image.md)). In such a case, you are responsible for security compliance and assessment of exploitability of any potential vulnerabilities of the custom runtime image. + +- Kyma does not run any security scans against Functions and their images. Before you store any sensitive data in Functions, consider the potential risk of data leakage. + +- Kyma does not define any authorization policies that would restrict Functions' access to other resources within the namespace. If you deploy a Function in a given namespace, it can freely access all events and APIs of services within this namespace. + +- Since Kubernetes is [moving from PodSecurityPolicies to PodSecurity Admission Controller](https://kubernetes.io/docs/tasks/configure-pod-container/migrate-from-psp/), Kyma Functions require running in namespaces with the `baseline` Pod security level. The `restricted` level is not currently supported due to the requirements of the Function building process. + +- Kyma Serverless components can run with the PodSecurity Admission Controller support in the `restricted` Pod security level when using an external registry. When the Internal Docker Registry is enabled, the Internal Registry DaemonSet requires elevated privileges to function correctly, exceeding the limitations of both the `restricted` and `baseline` levels. + +- All administrators and regular users who have access to a specific namespace in a cluster can also access: + + - Source code of all Functions within this namespace + - Internal Docker registry that contains Function images + - Secrets allowing the build Job to pull and push images from and to the Docker registry (in non-system namespaces) + \ No newline at end of file diff --git a/docs/user/00-50-limitations.md b/docs/user/00-50-limitations.md new file mode 100644 index 00000000..907d52af --- /dev/null +++ b/docs/user/00-50-limitations.md @@ -0,0 +1,86 @@ +# Serverless Limitations + +## Controller Limitations + +Serverless controller does not serve time-critical requests from users. +It reconciles Function custom resources (CR), stored at the Kubernetes API Server, and has no persistent state on its own. + +Serverless controller doesn't build or serve Functions using its allocated runtime resources. It delegates this work to the dedicated Kubernetes workloads. It schedules (build-time) jobs to build the Function Docker image and (runtime) Pods to serve them once they are built. +Refer to the [architecture](technical-reference/04-10-architecture.md) diagram for more details. + +Having this in mind Serverless Controller does not require horizontal scaling. +It scales vertically up to the `160Mi` of memory and `500m` of CPU time. + +## Limitation for the Number of Functions + +There is no upper limit of Functions that can be run on Kyma (similar to Kubernetes workloads in general). Once a user defines a Function, its build jobs and runtime Pods will always be requested by Serverless controller. It's up to Kubernetes to schedule them based on the available memory and CPU time on the Kubernetes worker nodes. This is determined mainly by the number of the Kubernetes worker nodes (and the Node auto-scaling capabilities) and their computational capacity. + +## Build Phase Limitation + +The time necessary to build Function depends on: + +- selected [build profile](technical-reference/07-80-available-presets.md#build-jobs-resources) that determines the requested resources (and their limits) for the build phase +- number and size of dependencies that must be downloaded and bundled into the Function image +- cluster Nodes specification (see the note with reference specification at the end of this document) + + + +#### **Node.js** + +| | local-dev | no profile (no limits for resource) | +|-----------------|-----------|-------------------------------------| +| no dependencies | 24 sec | 15 sec | +| 2 dependencies | 26 sec | 16 sec | + +#### **Python** + +| | local-dev | no profile (no limits for resource) | +|-----------------|-----------|-------------------------------------| +| no dependencies | 30 sec | 16 sec | +| 2 dependencies | 32 sec | 20 sec | + + + +The shortest build time (the limit) is approximately 15 seconds and requires no limitation of the build job resources and a minimum number of dependencies that are pulled in during the build phase. + +Running multiple Function build jobs at once (especially with no limits) may drain the cluster resources. To mitigate such risk, there is an additional limit of 5 simultaneous Function builds. If a sixth one is scheduled, it is built once there is a vacancy in the build queue. + +## Runtime Phase Limitations + +In the runtime, the Functions serve user-provided logic wrapped in the WEB framework (`express` for Node.js and `bottle` for Python). Taking the user logic aside, those frameworks have limitations and depend on the selected [runtime profile](technical-reference/07-80-available-presets.md#functions-resources) and the Kubernetes nodes specification (see the note with reference specification at the end of this document). + +The following describes the response times of the selected runtime profiles for a "Hello World" Function requested at 50 requests/second. This describes the overhead of the serving framework itself. Any user logic added on top of that will add extra milliseconds and must be profiled separately. + + + +#### **Node.js** + +| | XL | L | M | S | XS | +|-------------------------------|--------|--------|--------|--------|---------| +| response time [avarage] | ~13ms | 13ms | ~15ms | ~60ms | ~400ms | +| response time [95 percentile] | ~20ms | ~30ms | ~70ms | ~200ms | ~800ms | +| response time [99 percentile] | ~200ms | ~200ms | ~220ms | ~500ms | ~1.25ms | + +#### **Python** + +| | XL | L | M | S | +|-------------------------------|--------|--------|--------|--------| +| response time [avarage] | ~11ms | 12ms | ~12ms | ~14ms | +| response time [95 percentile] | ~25ms | ~25ms | ~25ms | ~25ms | +| response time [99 percentile] | ~175ms | ~180ms | ~210ms | ~280ms | + + + +Obviously, the bigger the runtime profile, the more resources are available to serve the response quicker. Consider these limits of the serving layer as a baseline - as this does not take your Function logic into account. + +### Scaling + +Function runtime Pods can be scaled horizontally from zero up to the limits of the available resources at the Kubernetes worker nodes. +See the [Use external scalers](tutorials/01-130-use-external-scalers.md) tutorial for more information. + +## In-Cluster Docker Registry + +Serverless comes with an in-cluster Docker registry for the Function images. For more information on the Docker registry configuration, visit [Serverless configuration](00-20-configure-serverless.md#configure-docker-registry). + +> [!NOTE] +> All measurements were done on Kubernetes with five AWS worker nodes of type `m5.xlarge` (four CPU 3.1 GHz x86_64 cores, 16 GiB memory). diff --git a/docs/user/08-10-best-practices.md b/docs/user/08-10-best-practices.md new file mode 100644 index 00000000..83c725ee --- /dev/null +++ b/docs/user/08-10-best-practices.md @@ -0,0 +1,62 @@ +# Serverless Best Practices + +## Overview - It's All About Custom Resources + +Kyma Serverless introduces a [Function](resources/06-10-function-cr.md) CustomResourceDefinition (CRD) as an extension to the Kubernetes API server. +Defining a Function in Kyma essentially means creating a new instance of the Function custom resource (CR). However, the content of the Function CR specification may become quite long. It consists of the code (or Git reference to the code), dependencies, runtime specification, build-time specification, etc. Additionally, there are other CRs that are relevant for a Function developer - that is, [APIRule](https://kyma-project.io/docs/kyma/latest/05-technical-reference/00-custom-resources/apix-01-apirule/) (defining how Function is exposed to the outside world), [Subscription](https://kyma-project.io/docs/kyma/latest/05-technical-reference/00-custom-resources/evnt-01-subscription/) (defining which CloudEvents should trigger a Function), and others. + +All of that can be easily managed using the following best practices for the Function development. You will find recommendations that will be helpful for you at any stage of your development journey. + +## Use UI to Explore + +At the beginning of your Kyma journey, you will probably want to evaluate Serverless and draft a few Functions. +Kyma dashboard is perfect to gain basic experience and start the journey with Kyma Functions. Its dedicated Serverless features help you draft your first Functions by putting the code directly in the browser using a Web IDE. +Kyma dashboard will also help you expose your Function using HTTP, define environment variables, subscribe to CloudEvents, bind ServiceInstances, and even show you the Function logs - all in one place. + +Get started with [Function UI](tutorials/01-10-create-inline-function.md) +![function-ui](../assets/svls-function-ui.png) + +## Use Kyma CLI for Better Development Experience + +Defining your Function from the Kyma dashboard is very quick and easy, but it might not be enough to satisfy your needs as a developer. To code and test more complex cases, you may want to write your Function in your favorite IDE or run and debug the Function on your local machine, before actually deploying in Kyma runtime. Also, you might want to avoid recreating the same Functions manually from the UI on a different environment. In the end, having deployable artifacts is more desirable. This is where Kyma CLI comes in handy, as it enables you to keep your Function's code and configuration in the form of a workspace. + +Initialize a scaffold for a brand new Function using the `kyma init function` command or fetch the current state of an existing Function deployed in your Kyma runtime using `kyma sync function`. +Focus on the Function code and develop it from your favorite IDE. Configure your Functions directly in the [`config.yaml` manifest file](technical-reference/07-60-function-configuration-file.md) + +> [!TIP] +> Use `kyma init function --vscode` to generate a `.json` schema, which can be used in VSCode for autocompletion. + +Kyma CLI helps you run your code locally with a single `kyma run function` command. You can run your Function using your local Docker daemon with the same runtime Docker context, as if it was run in Kyma runtime. + +> [!TIP] +> Use `kyma run function` with `--hot-deploy` and spare yourself unnecessary restarts of the Functions whenever you test a changed Function logic. Also, use [`--debug` option](tutorials/01-40-debug-function.md) to allow connecting with your favorite debugger. + +![kyma-cli-functions](../assets/svls-kyma-cli-functions.png) + +Having written and tested your Function locally, simply deploy it to the Kyma runtime with the `kyma apply function` command, used in the folder of your Function's workspace. The command reads the files, translates them to the Kubernetes manifests, and deploys the Function. + +## Deploy Using CI/CD + +Kyma dashboard helps you get started. Kyma CLI helps you iterate and develop Functions. +But at the end of the day, you may want an automated deployment of your application, where Functions are just part of it. +It all comes down to the deployment the Kubernetes applications on different Kyma runtimes in a GitOps fashion. For the sake of simplicity, the deployment approach for Functions should not differ from the deployment of the other Kubernetes workloads, ConfigMaps, or Secrets. + +So, in the end, what you need is YAML manifests for everything - including Functions. + +Good news: Kyma CLI helps you generate the YAML manifests matching your `config.yaml` file crafted before. +Use the `--dry-run` option of the `kyma apply function` command to generate Kubernetes manifests that will include the Function CR itself and all the related CRs (for example, APIRules, Subscriptions, etc.). + + ```bash + kyma apply function --dry-run --ci -o yaml > my-function.yaml + ``` + +The generated manifest should be a part of all the manifests that define your application and are pushed to the Git repository. +Deploy everything in a consistent way either using CI/CD or GitOps operators (for example, `fluxcd` or `argocd`) installed on your Kyma runtime. + +> [!NOTE] +> Kyma Functions come in two types: `git` and `inline`. For the [Git type](tutorials/01-11-create-git-function.md), you configure a Git repository as a source of your Function code instead of creating it `inline`. +Thus, you can skip rendering the Kubernetes manifests and deploying them each time you made a change in the Function code or dependencies. Simply push the changes to the referenced Git repository, and the Serverless controller will rebuild the Function deployed in your Kyma runtime. + +Have a look at this [example](https://github.com/kyma-project/serverless/tree/main/examples/incluster_eventing) that illustrates how you can set up your Git project. Mind the `k8s resources` folder with the YAML manifests to be pushed to the Kubernetes API server (for example, using kubectl in our CI/CD or GitOps) and the `src` folder containing the Functions' source code. They are pulled directly by Kyma Serverless to build new Function images whenever the source content changes in the Git repository. + +Browse the [tutorials](tutorials/README.md) for Serverless to learn how to use it step-by-step in different scenarios. diff --git a/docs/user/README.md b/docs/user/README.md index 1c6854d6..4b1374c2 100644 --- a/docs/user/README.md +++ b/docs/user/README.md @@ -1,14 +1,43 @@ -> **TIP:** Apart from the {Module Name} heading, you can use your own titles for the remaining sections. You can also add more module-specific sections. +# Serverless Module -# {Module Name} -> Modify the title and insert the name of your module. Use Heading 1 (H1). +## What is serverless? -## Overview -> Provide a description of your module and its components. Describe its features and functionalities. Mention the scope and add information on the CustomResourceDefinitions (CRDs). -> You can divide this section to the relevant subsections. +"Serverless" refers to an architecture in which the infrastructure of your applications is managed by cloud providers. Contrary to its name, a serverless application does require a server but it doesn't require you to run and manage it on your own. Instead, you subscribe to a given cloud provider, such as AWS, Azure, or GCP, and pay a subscription fee only for the resources you actually use. Because the resource allocation can be dynamic and depends on your current needs, the serverless model is particularly cost-effective when you want to implement a certain logic that is triggered on demand. Simply, you get your things done and don't pay for the infrastructure that stays idle. -## Useful Links (Optional) -> Provide links to the most relevant module documentation (tutorials, technical references, resources, etc.). +Kyma Serverless Module offers a Kubernetes-based platform on which you can build, run, and manage serverless applications in Kubernetes. These applications are called **Functions**, and they are based on the [Function custom resource (CR)](resources/06-10-function-cr.md) objects. They contain simple code snippets that implement specific business logic. For example, you can define that you want to use a Function as a proxy that saves all incoming event details to an external database. -## Feedback (Optional) -> Describe how users can provide feedback. \ No newline at end of file +Such a Function can be: + +- Triggered by other workloads in the cluster (in-cluster events) or business events coming from external sources. You can subscribe to them using a [Subscription CR](https://kyma-project.io/#/eventing-manager/user/resources/evnt-cr-subscription). +- Exposed to an external endpoint (HTTPS). With an [APIRule CR](https://kyma-project.io/#/api-gateway/user/custom-resources/apirule/04-10-apirule-custom-resource), you can define who can reach the endpoint and what operations they can perform on it. + +## What is Serverless in Kyma? + +Serverless in Kyma is an area that: + +- Ensures quick deployments following a Function approach +- Enables scaling independent of the core applications +- Gives a possibility to revert changes without causing production system downtime +- Supports the complete asynchronous programming model +- Offers loose coupling of Event providers and consumers +- Enables flexible application scalability and availability + +Serverless in Kyma allows you to reduce the implementation and operation effort of an application to the absolute minimum. It provides a platform to run lightweight Functions in a cost-efficient and scalable way using JavaScript and Node.js. Serverless in Kyma relies on Kubernetes resources like [Deployments](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/), [Services](https://kubernetes.io/docs/concepts/services-networking/service/) and [HorizontalPodAutoscalers](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) for deploying and managing Functions and [Kubernetes Jobs](https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/) for creating Docker images. + +## Serverless Module + +The Serverless module allows you to install, uninstall and configure Kyma's Serverless on your Kubernetes cluster, using Serverless Operator. + +## Serverless Operator + +When you enable the Serverless module, Serverless Operator takes care of installation and configuration of Serverless on your cluster. It manages the Serverless lifecycle based on the dedicated Serverless custom resource (CR). + +## Useful Links + +If you want to perform some simple and more advanced tasks, check the [Serverless tutorials](tutorials/README.md). + +To troubleshoot Serverless-related issues, see the [troubleshooting guides](troubleshooting-guides/README.md). + +To analyze Function specification and configuration files and to understand technicalities behind Serverless implementation, visit [technical reference](technical-reference/README.md). + +For more information on the Serverless custom resources, see [Resources](resources/README.md). diff --git a/docs/user/_sidebar.md b/docs/user/_sidebar.md index 467416ed..a0a1ec87 100644 --- a/docs/user/_sidebar.md +++ b/docs/user/_sidebar.md @@ -1 +1,41 @@ -Use this file to create an unordered list of documents you want to display on the [Kyma website](https://kyma-project.io). The list serves to navigate through the user documentation. For more information, visit the [User documentation](https://github.com/kyma-project/community/blob/main/docs/guidelines/content-guidelines/01-user-docs.md) guide. \ No newline at end of file + +* [Back to Kyma Home](/) +* [Serverless Module](/serverless-manager/user/README.md) +* [From Code to Function](/serverless-manager/user/00-10-from-code-to-function.md) +* [Serverless Configuration](/serverless-manager/user/00-20-configure-serverless.md) +* [Development Toolkit](/serverless-manager/user/00-30-development-toolkit.md) +* [Security Considerations](/serverless-manager/user/00-40-security-considerations.md) +* [Limitations](/serverless-manager/user/00-50-limitations.md) +* [Tutorials](/serverless-manager/user/tutorials/README.md) + * [Create an Inline Function](/serverless-manager/user/tutorials/01-10-create-inline-function.md) + * [Create a Git Function](/serverless-manager/user/tutorials/01-11-create-git-function.md) + * [Expose the Function](/serverless-manager/user/tutorials/01-20-expose-function.md) + * [Manage Functions Through Kyma CLI](/serverless-manager/user/tutorials/01-30-manage-functions-with-kyma-cli.md) + * [Debug a Function](/serverless-manager/user/tutorials/01-40-debug-function.md) + * [Log Into a Private Package Registry](/serverless-manager/user/tutorials/01-80-log-into-private-packages-registry.md) + * [Set Asynchronous Communication Between Functions](/serverless-manager/user/tutorials/01-90-set-asynchronous-connection.md) + * [Customize Function Traces](/serverless-manager/user/tutorials/01-100-customize-function-traces.md) + * [Override Runtime Image](/serverless-manager/user/tutorials/01-110-override-runtime-image.md) + * [Inject Environment Variables](/serverless-manager/user/tutorials/01-120-inject-envs.md) + * [Use External Scalers](/serverless-manager/user/tutorials/01-130-use-external-scalers.md) + * [Access to Secrets Mounted as Volume](/serverless-manager/user/tutorials/01-140-use-secret-mounts.md) +* [Resources](/serverless-manager/user/resources/README.md) + * [Function CR](/serverless-manager/user/resources/06-10-function-cr.md) + * [Serverless CR](/serverless-manager/user/resources/06-20-serverless-cr.md) +* [Technical Reference](/serverless-manager/user/technical-reference/README.md) + * [Serverless Architecture](/serverless-manager/user/technical-reference/04-10-architecture.md) + * [Internal Docker Registry](/serverless-manager/user/technical-reference/04-20-internal-registry.md) + * [Environment Variables in Functions](/serverless-manager/user/technical-reference/05-20-env-variables.md) + * [Sample Functions](/serverless-manager/user/technical-reference/07-10-sample-functions.md) + * [Function Processing](/serverless-manager/user/technical-reference/07-20-function-processing-stages.md) + * [Git Source Type](/serverless-manager/user/technical-reference/07-40-git-source-type.md) + * [Function Configuration File](/serverless-manager/user/technical-reference/07-60-function-configuration-file.md) + * [Function's Specification](/serverless-manager/user/technical-reference/07-70-function-specification.md) + * [Available Presets](/serverless-manager/user/technical-reference/07-80-available-presets.md) +* [Troubleshooting Guides](/serverless-manager/user/troubleshooting-guides/README.md) + * [Functions Won't Build](/serverless-manager/user/troubleshooting-guides/03-10-cannot-build-functions.md) + * [Container Fails](/serverless-manager/user/troubleshooting-guides/03-20-failing-function-container.md) + * [Functions Failing To Build on k3d](/serverless-manager/user/troubleshooting-guides/03-40-function-build-failing-k3d.md) + * [Serverless Periodically Restarting](/serverless-manager/user/troubleshooting-guides/03-50-serverless-periodically-restaring.md) +* [Best Practices](/serverless-manager/user/08-10-best-practices.md) + \ No newline at end of file diff --git a/docs/user/resources/06-10-function-cr.md b/docs/user/resources/06-10-function-cr.md new file mode 100644 index 00000000..784d91a1 --- /dev/null +++ b/docs/user/resources/06-10-function-cr.md @@ -0,0 +1,210 @@ +# Function + +The `functions.serverless.kyma-project.io` CustomResourceDefinition (CRD) is a detailed description of the kind of data and the format used to manage Functions within Kyma. To get the up-to-date CRD and show the output in the YAML format, run this command: + +```bash +kubectl get crd functions.serverless.kyma-project.io -o yaml +``` + +## Sample Custom Resource + +The following Function object creates a Function which responds to HTTP requests with the "Hello John" message. The Function's code (**source**) and dependencies (**dependencies**) are specified in the Function CR. + +```yaml +apiVersion: serverless.kyma-project.io/v1alpha2 +kind: Function +metadata: + name: my-test-function + namespace: default + labels: + app: my-test-function +spec: + runtime: nodejs20 + source: + inline: + dependencies: | + { + "name": "hellowithdeps", + "version": "0.0.1", + "dependencies": { + "end-of-stream": "^1.4.1", + "from2": "^2.3.0", + "lodash": "^4.17.5" + } + } + source: | + module.exports = { + main: function(event, context) { + const name = process.env.PERSON_NAME; + return 'Hello ' + name; + } + } + scaleConfig: + minReplicas: 3 + maxReplicas: 3 + resourceConfiguration: + function: + resources: + limits: + cpu: 1 + memory: 1Gi + requests: + cpu: 500m + memory: 500Mi + build: + resources: + limits: + cpu: 2 + memory: 2Gi + requests: + cpu: 1 + memory: 1Gi + env: + - name: PERSON_NAME + value: "John" + secretMounts: + - secretName: SECRET_NAME + mountPath: /secret/mount/path + status: + conditions: + - lastTransitionTime: "2020-04-14T08:17:11Z" + message: "Deployment my-test-function-nxjdp is ready" + reason: DeploymentReady + status: "True" + type: Running + - lastTransitionTime: "2020-04-14T08:16:55Z" + message: "Job my-test-function-build-552ft finished" + reason: JobFinished + status: "True" + type: BuildReady + - lastTransitionTime: "2020-04-14T08:16:16Z" + message: "ConfigMap my-test-function-xv6pc created" + reason: ConfigMapCreated + status: "True" + type: ConfigurationReady +``` + +If you store the Function's source code and dependencies in a Git repository and want the Function Controller to fetch them from it, use these parameters in the Function CR: + +```yaml +apiVersion: serverless.kyma-project.io/v1alpha2 +kind: Function +metadata: + name: my-test-function +spec: + source: + gitRepository: + url: github.com/username/repo + baseDir: "/" + reference: "branchA" + auth: + type: basic + secretName: secret-name + runtime: "nodejs20" +``` + +## Custom Resource Parameters + +### Function.serverless.kyma-project.io/v1alpha2 + +**Spec:** + +| Parameter | Type | Description | +| ---- | ----------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **annotations** | map\[string\]string | Defines annotations used in Deployment's PodTemplate and applied on the Function's runtime Pod. | +| **env** | \[\]object | Specifies an array of key-value pairs to be used as environment variables for the Function. You can define values as static strings or reference values from ConfigMaps or Secrets. For configuration details, see the [official Kubernetes documentation](https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/). | +| **labels** | map\[string\]string | Defines labels used in Deployment's PodTemplate and applied on the Function's runtime Pod. | +| **replicas** | integer | Defines the exact number of Function's Pods to run at a time. If **ScaleConfig** is configured, or if the Function is targeted by an external scaler, then the **Replicas** field is used by the relevant HorizontalPodAutoscaler to control the number of active replicas. | +| **resourceConfiguration** | object | Specifies resources requested by the Function and the build Job. | +| **resourceConfiguration.​build** | object | Specifies resources requested by the build Job's Pod. | +| **resourceConfiguration.​build.​profile** | string | Defines the name of the predefined set of values of the resource. Can't be used together with **Resources**. | +| **resourceConfiguration.​build.​resources** | object | Defines the amount of resources available for the Pod. Can't be used together with **Profile**. For configuration details, see the [official Kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/). | +| **resourceConfiguration.​function** | object | Specifies resources requested by the Function's Pod. | +| **resourceConfiguration.​function.​profile** | string | Defines the name of the predefined set of values of the resource. Can't be used together with **Resources**. | +| **resourceConfiguration.​function.​resources** | object | Defines the amount of resources available for the Pod. Can't be used together with **Profile**. For configuration details, see the [official Kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/). | +| **runtime** (required) | string | Specifies the runtime of the Function. The available values are `nodejs18` - deprecated, `nodejs20`, `python39` - deprecated, and `python312`. | +| **runtimeImageOverride** | string | Specifies the runtime image used instead of the default one. | +| **scaleConfig** | object | Defines the minimum and maximum number of Function's Pods to run at a time. When it is configured, a HorizontalPodAutoscaler will be deployed and will control the **Replicas** field to scale the Function based on the CPU utilisation. | +| **scaleConfig.​maxReplicas** (required) | integer | Defines the maximum number of Function's Pods to run at a time. | +| **scaleConfig.​minReplicas** (required) | integer | Defines the minimum number of Function's Pods to run at a time. | +| **secretMounts** | \[\]object | Specifies Secrets to mount into the Function's container filesystem. | +| **secretMounts.​mountPath** (required) | string | Specifies the path within the container where the Secret should be mounted. | +| **secretMounts.​secretName** (required) | string | Specifies the name of the Secret in the Function's namespace. | +| **source** (required) | object | Contains the Function's source code configuration. | +| **source.​gitRepository** | object | Defines the Function as Git-sourced. Can't be used together with **Inline**. | +| **source.​gitRepository.​auth** | object | Specifies the authentication method. Required for SSH. | +| **source.​gitRepository.​auth.​secretName** (required) | string | Specifies the name of the Secret with credentials used by the Function Controller to authenticate to the Git repository in order to fetch the Function's source code and dependencies. This Secret must be stored in the same namespace as the Function CR. | +| **source.​gitRepository.​auth.​type** (required) | string | Defines the repository authentication method. The value is either `basic` if you use a password or token, or `key` if you use an SSH key. | +| **source.​gitRepository.​baseDir** | string | Specifies the relative path to the Git directory that contains the source code from which the Function is built. | +| **source.​gitRepository.​reference** | string | Specifies either the branch name, tag or commit revision from which the Function Controller automatically fetches the changes in the Function's code and dependencies. | +| **source.​gitRepository.​url** (required) | string | Specifies the URL of the Git repository with the Function's code and dependencies. Depending on whether the repository is public or private and what authentication method is used to access it, the URL must start with the `http(s)`, `git`, or `ssh` prefix. | +| **source.​inline** | object | Defines the Function as the inline Function. Can't be used together with **GitRepository**. | +| **source.​inline.​dependencies** | string | Specifies the Function's dependencies. | +| **source.​inline.​source** (required) | string | Specifies the Function's full source code. | + +**Status:** + +| Parameter | Type | Description | +| ---- | ----------- | ---- | +| **baseDir** | string | Specifies the relative path to the Git directory that contains the source code from which the Function is built. | +| **commit** | string | Specifies the commit hash used to build the Function. | +| **conditions** | \[\]object | Specifies an array of conditions describing the status of the parser. | +| **conditions.​lastTransitionTime** | string | Specifies the last time the condition transitioned from one status to another. | +| **conditions.​message** | string | Provides a human-readable message indicating details about the transition. | +| **conditions.​reason** | string | Specifies the reason for the condition's last transition. | +| **conditions.​status** (required) | string | Specifies the status of the condition. The value is either `True`, `False`, or `Unknown`. | +| **conditions.​type** | string | Specifies the type of the Function's condition. | +| **podSelector** | string | Specifies the Pod selector used to match Pods in the Function's Deployment. | +| **reference** | string | Specifies either the branch name, tag or commit revision from which the Function Controller automatically fetches the changes in the Function's code and dependencies. | +| **replicas** | integer | Specifies the total number of non-terminated Pods targeted by this Function. | +| **runtime** | string | Specifies the **Runtime** type of the Function. | +| **runtimeImage** | string | Specifies the image version used to build and run the Function's Pods. | +| **runtimeImageOverride** | string | Deprecated: Specifies the runtime image version which overrides the **RuntimeImage** status parameter. **RuntimeImageOverride** exists for historical compatibility and should be removed with v1alpha3 version. | + + + +### Status Reasons + +Processing of a Function CR can succeed, continue, or fail for one of these reasons: + +| Reason | Type | Description | +| -------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ConfigMapCreated` | `ConfigurationReady` | A new ConfigMap was created based on the Function CR definition. | +| `ConfigMapUpdated` | `ConfigurationReady` | The existing ConfigMap was updated after changes in the Function CR name, its source code or dependencies. | +| `SourceUpdated` | `ConfigurationReady` | The Function Controller managed to fetch changes in the Functions's source code and configuration from the Git repository. | +| `SourceUpdateFailed` | `ConfigurationReady` | The Function Controller failed to fetch changes in the Functions's source code and configuration from the Git repository. | +| `JobFailed` | `BuildReady` | The image with the Function's configuration could not be created due to an error. | +| `JobCreated` | `BuildReady` | The Kubernetes Job resource that builds the Function image was created. | +| `JobUpdated` | `BuildReady` | The existing Job was updated after changing the Function's metadata or spec fields that do not affect the way of building the Function image, such as labels. | +| `JobRunning` | `BuildReady` | The Job is in progress. | +| `JobsDeleted` | `BuildReady` | Previous Jobs responsible for building Function images were deleted. | +| `JobFinished` | `BuildReady` | The Job was finished and the Function's image was uploaded to the Docker Registry. | +| `DeploymentCreated` | `Running` | A new Deployment referencing the Function's image was created. | +| `DeploymentUpdated` | `Running` | The existing Deployment was updated after changing the Function's image, scaling parameters, variables, or labels. | +| `DeploymentFailed` | `Running` | The Function's Pod crashed or could not start due to an error. | +| `DeploymentWaiting` | `Running` | The Function was deployed and is waiting for the Deployment to be ready. | +| `DeploymentReady` | `Running` | The Function was deployed and is ready. | +| `ServiceCreated` | `Running` | A new Service referencing the Function's Deployment was created. | +| `ServiceUpdated` | `Running` | The existing Service was updated after applying required changes. | +| `ServiceFailed` | `Running` | The Function's service could not be created or updated. | +| `HorizontalPodAutoscalerCreated` | `Running` | A new Horizontal Pod Scaler referencing the Function's Deployment was created. | +| `HorizontalPodAutoscalerUpdated` | `Running` | The existing Horizontal Pod Scaler was updated after applying required changes. | +| `MinimumReplicasUnavailable` | `Running` | Insufficient number of available Replicas. The Function is unhealthy. | + +## Related Resources and Components + +These are the resources related to this CR: + +| Custom resource | Description | +| ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | +| [ConfigMap](https://kubernetes.io/docs/concepts/configuration/configmap/) | Stores the Function's source code and dependencies. | +| [Job](https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/) | Builds an image with the Function's code in a runtime. | +| [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) | Serves the Function's image as a microservice. | +| [Service](https://kubernetes.io/docs/concepts/services-networking/service/) | Exposes the Function's Deployment as a network service inside the Kubernetes cluster. | +| [HorizontalPodAutoscaler](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) | Automatically scales the number of Function's Pods. | + +These components use this CR: + +| Component | Description | +| ------------------- | ------------------------------------------------------------------------------------------------------------ | +| Function Controller | Uses the Function CR for the detailed Function definition, including the environment on which it should run. | diff --git a/docs/user/resources/06-20-serverless-cr.md b/docs/user/resources/06-20-serverless-cr.md new file mode 100644 index 00000000..de0b562b --- /dev/null +++ b/docs/user/resources/06-20-serverless-cr.md @@ -0,0 +1,130 @@ +# Serverless + +The `serverlesses.operator.kyma-project.io` CustomResourceDefinition (CRD) is a detailed description of the Serverless configuration that you want to install on your cluster. To get the up-to-date CRD and show the output in the YAML format, run this command: + + ```bash + kubectl get crd serverlesses.operator.kyma-project.io -o yaml + ``` + +## Sample Custom Resource + +The following Serverless custom resource (CR) shows configuration of Serverless with the external registry, custom endpoints for eventing and tracing and custom additional configuration. + + ```yaml + apiVersion: operator.kyma-project.io/v1alpha1 + kind: Serverless + metadata: + finalizers: + - dockerregistry-operator.kyma-project.io/deletion-hook + name: default + namespace: kyma-system + spec: + dockerRegistry: + enableInternal: false + secretName: my-secret + eventing: + endpoint: http://eventing-publisher-proxy.kyma-system.svc.cluster.local/publish + tracing: + endpoint: http://telemetry-otlp-traces.kyma-system.svc.cluster.local:4318/v1/traces + targetCPUUtilizationPercentage: 50 + functionRequeueDuration: 5m + functionBuildExecutorArgs: "--insecure,--skip-tls-verify,--skip-unused-stages,--log-format=text,--cache=true,--use-new-run,--compressed-caching=false" + functionBuildMaxSimultaneousJobs: 5 + healthzLivenessTimeout: "10s" + functionRequestBodyLimitMb: 1 + functionTimeoutSec: 180 + defaultBuildJobPreset: "normal" + defaultRuntimePodPreset: "M" + status: + conditions: + - lastTransitionTime: "2023-04-28T10:09:37Z" + message: Configured with default Publisher Proxy URL and default Trace Collector + URL. + reason: Configured + status: "True" + type: Configured + - lastTransitionTime: "2023-04-28T10:15:15Z" + message: Serverless installed + reason: Installed + status: "True" + type: Installed + eventPublisherProxyURL: http://eventing-publisher-proxy.kyma-system.svc.cluster.local/publish + state: Ready + traceCollectorURL: http://telemetry-otlp-traces.kyma-system.svc.cluster.local:4318/v1/traces + ``` + +## Custom Resource Parameters + +For details, see the [Serverless specification file](https://github.com/kyma-project/serverless-manager/blob/main/components/operator/api/v1alpha1/serverless_types.go). + +### Serverless.operator.kyma-project.io/v1alpha1 + +**Spec:** + +| Parameter | Type | Description | +|-------------------------------------------|---------|-------------| +| **dockerRegistry** | object | | +| **dockerRegistry.​enableInternal** | boolean | When set to `true`, the internal Docker registry is enabled | +| **dockerRegistry.​secretName** | string | Secret used for configuration of the Docker registry | +| **eventing** | object | | +| **eventing.​endpoint** (required) | string | Used Eventing endpoint | +| **tracing** | object | | +| **tracing.​endpoint** (required) | string | Used Tracing endpoint | +| **targetCPUUtilizationPercentage** | string | Sets a custom CPU utilization threshold for scaling Function Pods | +| **functionRequeueDuration** | string | Sets the requeue duration for Function. By default, the Function associated with the default configuration is requeued every 5 minutes | +| **functionBuildExecutorArgs** | string | Specifies the arguments passed to the Function build executor | +| **functionBuildMaxSimultaneousJobs** | string | A number of simultaneous jobs that can run at the same time. The default value is `5` | +| **healthzLivenessTimeout** | string | Sets the timeout for the Function health check. The default value in seconds is `10` | +| **functionRequestBodyLimitMb** | string | Used to configure the maximum size limit for the request body of a Function. The default value is `1` megabyte | +| **functionTimeoutSec** | string | Sets the maximum execution time limit for a Function. By default, the value is `180` seconds | +| **defaultBuildJobPreset** | string | Configures the default build Job preset to be used | +| **defaultRuntimePodPreset** | string | Configures the default runtime Pod preset to be used | + +**Status:** + +| Parameter | Type | Description | +|------------------------------------------------------|------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **conditions** | \[\]object | Conditions associated with CustomStatus. | +| **conditions.​lastTransitionTime** (required) | string | Specifies the last time the condition transitioned from one status to another. This should be when the underlying condition changes. If that is not known, then using the time when the API field changed is acceptable. | +| **conditions.​message** (required) | string | Provides a human-readable message indicating details about the transition. This may be an empty string. | +| **conditions.​observedGeneration** | integer | Represents the **.metadata.generation** that the condition was set based upon. For instance, if **.metadata.generation** is currently `12`, but the **.status.conditions[x].observedGeneration** is `9`, the condition is out of date with respect to the current state of the instance. | +| **conditions.​reason** (required) | string | Contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field and whether the values are considered a guaranteed API. The value should be a camelCase string. This field may not be empty. | +| **conditions.​status** (required) | string | Specifies the status of the condition. The value is either `True`, `False`, or `Unknown`. | +| **conditions.​type** (required) | string | Specifies the condition type in camelCase or in `foo.example.com/CamelCase`. Many **.conditions.type** values are consistent across resources like `Available`, but because arbitrary conditions can be useful (see **.node.status.conditions**), the ability to deconflict is important. The regex it matches is `(dns1123SubdomainFmt/)?(qualifiedNameFmt)`. | +| **dockerRegistry** | string | Used registry configuration. Contains registry URL or "internal". | +| **eventingEndpoint** | string | Used Eventing endpoint. | +| **served** (required) | string | Served signifies that current Serverless is managed. Value can be one of `True`, or `False`. | +| **state** | string | Signifies the current state of Serverless. Value can be one of `Ready`, `Processing`, `Error`, or `Deleting`. | +| **tracingEndpoint** | string | Used Tracing endpoint. | +| **targetCPUUtilizationPercentage** | string | Used target CPU utilization percentage. | +| **functionRequeueDuration** | string | Used the Function requeue duration. | +| **functionBuildExecutorArgs** | string | Used the Function build executor arguments. | +| **functionBuildMaxSimultaneousJobs** | string | Used the Function build max number of simultaneous jobs. | +| **healthzLivenessTimeout** | string | Used the healthz liveness timeout. | +| **functionRequestBodyLimitMb** | string | Used the Function request body limit. | +| **functionTimeoutSec** | string | Used the Function timeout. | +| **defaultBuildJobPreset** | string | Used the default build Job preset. | +| **defaultRuntimePodPreset** | string | Used the default runtime Pod preset. | + + + +### Status Reasons + +Processing of a Serverless CR can succeed, continue, or fail for one of these reasons: + +## Serverless CR Conditions + +This section describes the possible states of the Serverless CR. Three condition types, `Installed`, `Configured` and `Deleted`, are used. + +| No | CR State | Condition type | Condition status | Condition reason | Remark | +|----|------------|----------------|------------------|-----------------------|-----------------------------------------------| +| 1 | Processing | Configured | true | Configured | Serverless configuration verified | +| 2 | Processing | Configured | unknown | ConfigurationCheck | Serverless configuration verification ongoing | +| 3 | Error | Configured | false | ConfigurationCheckErr | Serverless configuration verification error | +| 7 | Error | Configured | false | ServerlessDuplicated | Only one Serverless CR is allowed | +| 4 | Ready | Installed | true | Installed | Serverless workloads deployed | +| 5 | Processing | Installed | unknown | Installation | Deploying serverless workloads | +| 6 | Error | Installed | false | InstallationErr | Deployment error | +| 8 | Deleting | Deleted | unknown | Deletion | Deletion in progress | +| 9 | Deleting | Deleted | true | Deleted | Serverless module deleted | +| 10 | Error | Deleted | false | DeletionErr | Deletion failed | diff --git a/docs/user/resources/README.md b/docs/user/resources/README.md new file mode 100644 index 00000000..847fa413 --- /dev/null +++ b/docs/user/resources/README.md @@ -0,0 +1,3 @@ +# Resources + +In this section, you can find the custom resources (CR) used in the Serverless module. diff --git a/docs/user/technical-reference/04-10-architecture.md b/docs/user/technical-reference/04-10-architecture.md new file mode 100644 index 00000000..d13ae366 --- /dev/null +++ b/docs/user/technical-reference/04-10-architecture.md @@ -0,0 +1,28 @@ +# Serverless Architecture + +Serverless relies heavily on Kubernetes resources. It uses [Deployments](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/), [Services](https://kubernetes.io/docs/concepts/services-networking/service/) and [HorizontalPodAutoscalers](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) to deploy and manage Functions, and [Kubernetes Jobs](https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/) to create Docker images. See how these and other resources process a Function within a Kyma cluster: + +![Serverless architecture](../../assets/svls-architecture.svg) + +> [!WARNING] +> Serverless imposes some requirements on the setup of namespaces. For example, if you apply custom [LimitRanges](https://kubernetes.io/docs/concepts/policy/limit-range/) for a new namespace, they must be higher than or equal to the limits for building Jobs' resources. + +1. Create a Function either through the UI or by applying a Function custom resource (CR). This CR contains the Function definition (business logic that you want to execute) and information on the environment on which it should run. + +2. Before the Function can be saved or modified, it is first updated and then verified by the defaulting and validation webhooks respectively. + +3. Function Controller (FC) detects the new, validated Function CR. + +4. FC creates a ConfigMap with the Function definition. + +5. Based on the ConfigMap, FC creates a Kubernetes Job that triggers the creation of a Function image. + +6. The Job creates a Pod which builds the production Docker image based on the Function's definition. The Job then pushes this image to a Docker registry. + +7. FC monitors the Job status. When the image creation finishes successfully, FC creates a Deployment that uses the newly built image. + +8. FC creates a Service that points to the Deployment. + +9. FC creates a HorizontalPodAutoscaler that automatically scales the number of Pods in the Deployment based on the observed CPU utilization. + +10. FC waits for the Deployment to become ready. diff --git a/docs/user/technical-reference/04-20-internal-registry.md b/docs/user/technical-reference/04-20-internal-registry.md new file mode 100644 index 00000000..3257f2aa --- /dev/null +++ b/docs/user/technical-reference/04-20-internal-registry.md @@ -0,0 +1,22 @@ +# Internal Docker Registry + +By default, the Kyma Serverless module comes with the internal Docker registry, which stores the Function container images without using the third-party registry. + +The internal Docker registry is not recommended for production, as it's not deployed in the High Availability (HA) setup and has limited storage space and no garbage collection of the orphaned images. + +Still, it is very convenient for development and getting first-time experience with Kyma Serverless. + +See the following diagram to learn how it works: + +![Serverless architecture](../../assets/svls-internal-registry.svg) + +1. Build job pushes the Function image to the Docker registry using the in-cluster URL. +2. The Kubernetes DNS resolves the internal Docker registry URL to the actual IP address. +3. [kubelet](https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/) fetches the image using the URL: `localhost:{node_port}/{image}`. +4. NodePort allows kubelet to get into the cluster network, translate `localhost` to `internal-registry.kyma-system.svc.cluster.local`, and ask the Kubernetes DNS to resolve the name. +5. The Kubernetes DNS service resolves the name and provides the IP of the internal Docker registry. + +> [!NOTE] +> kubelet cannot resolve the in-cluster URL. That's why Serverless uses the NodePort service. + +The NodePort service routing assures that the pull request reaches the internal Docker registry regardless of whether it is from a different node. diff --git a/docs/user/technical-reference/05-20-env-variables.md b/docs/user/technical-reference/05-20-env-variables.md new file mode 100644 index 00000000..d5574c22 --- /dev/null +++ b/docs/user/technical-reference/05-20-env-variables.md @@ -0,0 +1,128 @@ +# Environment Variables + +You can use environment variables to configure an existing runtime, to read existing configuration or to build your own runtime based on them. + +## Environments Passed to Runtimes + +Every runtime provides its own unique environment configuration which can be read by a server and the `handler.js` file during the container run: + +### Common Environments + +| Environment | Default | Description | +|---------------|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **FUNC_HANDLER** | `main` | The name of the exported Function inside the `MOD_NAME` file. | +| **MOD_NAME** | `handler` | The name of the main exported file. It must have an extension of `.py` for the Python runtimes and `.js` for the Node.js ones. The extension must be added on the server side. | +| **FUNC_PORT** | `8080` | The right port a server listens to. | +| **SERVICE_NAMESPACE** | None | The namespace where the right Function exists in a cluster. | +| **KUBELESS_INSTALL_VOLUME** | `/kubeless` | Full path to volume mount with users source code. | +| **FUNC_RUNTIME** | None | The name of the actual runtime. Possible values: `nodejs18` - deprecated, `nodejs20`, `python39` - deprecated, and `python312`. | +| **TRACE_COLLECTOR_ENDPOINT** | None | Full address of OpenTelemetry Trace Collector is exported if the trace collector's endpoint is present. | +| **PUBLISHER_PROXY_ADDRESS** | `http://eventing-publisher-proxy.kyma-system.svc .cluster.local/publish` | Full address of the Publisher Proxy service. | + +### Specific Environments + +There are a few environments that occur only for a specific runtimes. The following list includes all of them: + +#### Python Runtime-Specific Environment Variables + +| Environment | Default | Description | +|---------------|-----------|-------------| +| **PYTHONPATH** | `$(KUBELESS_INSTALL_VOLUME)/lib.python3.9/site-packages :$(KUBELESS_INSTALL_VOLUME)` | List of directories that Python must add to the sys.path directory list. | +| **PYTHONUNBUFFERED** | `TRUE` | Defines if Python's logs must be buffered before printing them out. | + +## Configure Runtime + +You can configure environment variables either separately for a given runtime or make them runtime-agnostic using a ConfigMap. + +### Define Environment Variables in a Config Map + +ConfigMaps allow you to define Function's environment variables for any runtime through key-value pairs. After you define the values in a ConfigMap, simply reference it in the Function custom resource (CR) through the **valueFrom** parameter. See an example of such a Function CR that specifies the `my-var` value as a reference to the key stored in the `my-vars-cm` ConfigMap as the `MY_VAR` environment variable. + +```yaml +apiVersion: serverless.kyma-project.io/v1alpha2 +kind: Function +metadata: + name: sample-cm-env-values + namespace: default +spec: + env: + - name: MY_VAR + valueFrom: + configMapKeyRef: + key: my-var + name: my-vars-cm + runtime: nodejs20 + source: + inline: + source: | + module.exports = { + main: function (event, context) { + return process.env["MY_VAR"]; + } + } +``` + +### Node.js Runtime-Specific Environment Variables + +To configure the Function with the Node.js runtime, override the default values of these environment variables: + +| Environment variable | Description | Type | Default value | +| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ------- | ------------- | +| **FUNC_TIMEOUT** | Specifies the number of seconds in which a runtime must execute the code. | Number | `180` | +| **REQ_MB_LIMIT** | Specifies the payload body size limit in megabytes. | Number | `1` | +| **KYMA_INTERNAL_LOGGER_ENABLED** | Enables the default HTTP request logger which uses the standard Apache combined log output. To enable it, set its value to `true`. | Boolean | `false` | + +See the example of a Function with these environment variables set: + +```yaml +apiVersion: serverless.kyma-project.io/v1alpha2 +kind: Function +metadata: + name: sample-fn-with-envs + namespace: default +spec: + env: + - name: FUNC_TIMEOUT + value: '2' + - name: REQ_MB_LIMIT + value: '10' + runtime: nodejs20 + source: + inline: + source: | + module.exports = { + main: function (event, context) { + return "Hello World!"; + } + } +``` + +### Python Runtime-Specific Environment Variables + +To configure a Function with the Python runtime, override the default values of these environment variables: + +| Environment variable | Description | Unit | Default value | +| -------------------------------- |---------------------------------------------------------------------------------------------------------------------------- | ------- | --------------- | +|**FUNC_MEMFILE_MAX**|for the HTTP request body in bytes. | Number | `100*1024*1024` | | +| **CHERRYPY_NUMTHREADS** | Specifies the number of requests that can be handled in parallel | Number | `50` | +| **KYMA_INTERNAL_LOGGER_ENABLED** | Enables the default HTTP request logger which uses the standard Apache combined log output. To enable it, set its value to `true`. | Boolean | `false` | + +See [`kubeless.py`](https://github.com/kubeless/runtimes/blob/master/stable/python/_kubeless.py) to get a deeper understanding of how the Bottle server, which acts as a runtime, uses these values internally to run Python Functions. + +```yaml +apiVersion: serverless.kyma-project.io/v1alpha2 +kind: Function +metadata: + name: sample-fn-with-envs + namespace: default +spec: + env: + - name: FUNC_MEMFILE_MAX + value: '1048576' #1MiB + runtime: nodejs20 + source: + inline: + source: | + def main(event. context): + return "Hello World!" +``` diff --git a/docs/user/technical-reference/07-10-sample-functions.md b/docs/user/technical-reference/07-10-sample-functions.md new file mode 100644 index 00000000..1869e92c --- /dev/null +++ b/docs/user/technical-reference/07-10-sample-functions.md @@ -0,0 +1,64 @@ +# Sample Functions + +Functions support multiple languages through the use of runtimes. To use a chosen runtime, add its name and version as a value in the **spec.runtime** field of the [Function custom resource (CR)](../resources/06-10-function-cr.md). If this value is not specified, it defaults to `nodejs18`. Dependencies for a Node.js Function must be specified using the [`package.json`](https://docs.npmjs.com/creating-a-package-json-file) file format. Dependencies for a Python Function must follow the format used by [pip](https://packaging.python.org/key_projects/#pip). + +> [!TIP] +> If you are interested in the Function's signature, `event` and `context` objects, and custom HTTP responses the Function returns, read about [Function’s specification](07-70-function-specification.md). + +See sample Functions for all available runtimes: + + + +#### **Node.js** + +```bash +cat < diff --git a/docs/user/technical-reference/07-20-function-processing-stages.md b/docs/user/technical-reference/07-20-function-processing-stages.md new file mode 100644 index 00000000..8d2f5b39 --- /dev/null +++ b/docs/user/technical-reference/07-20-function-processing-stages.md @@ -0,0 +1,50 @@ +# Function Processing Stages + +From the moment you [create a Function](../tutorials/01-10-create-inline-function.md) (Function CR) until the time it is ready, it goes through three processing stages that are defined as these condition types: + +1. `ConfigurationReady` (PrinterColumn `CONFIGURED`) +2. `BuildReady` (PrinterColumn `BUILT`) +3. `Running` (PrinterColumn `RUNNING`) + +For a Function to be considered ready, the status of all three conditions must be `True`: + +```bash +NAME CONFIGURED BUILT RUNNING RUNTIME VERSION AGE +test-function True True True nodejs20 1 96s +``` + +When you update an existing Function, conditions change asynchronously depending on the change type. + +The diagrams illustrate all three core status changes in the Function processing circle that the Function Controller handles. They also list all custom resources involved in this process and specify in which cases their update is required. + +> [!NOTE] +> Before you start reading, see the [Function CR](../resources/06-10-function-cr.md) document for the custom resource detailed definition, the list of all Function's condition types, and reasons for their success or failure. + +## Configured + +This initial phase starts when you create a Function CR with configuration specifying the Function's setup. It ends with creating a ConfigMap that is used as a building block for a Function image. + +![Function configured](../../assets/svls-configured.svg) + +## Built + +This phase involves creating and processing the Job CR. It ends successfully when the Function image is built and sent to the Docker registry. If the image already exists and only an update is required, the Docker image receives a new tag. + +Updating an existing Function requires an image rebuild only if you change the Function's body (**source**) or dependencies (**deps**). An update of a Function's other configuration details, such as environment variables, replicas, resources, or labels, does not require image rebuild because it only affects the Deployment. + +> [!NOTE] +> Each time you update a Function's configuration, the Function Controller deletes all previous Job CRs for the given Function's **UID**. + +![Function built](../../assets/svls-built.svg) + +## Running + +This stage revolves around creating a Deployment, Service and HorizontalPodAutoscaler or updating them when configuration changes were made in the Function CR or the Function image was rebuilt. + +In general, the Deployment is considered updated when both configuration and the image tag in the Deployment are up to date. Service and HorizontalPodAutoscaler are considered updated when there are proper labels set and configuration is up to date. + +Thanks to the implemented reconciliation loop, the Function Controller constantly observes all newly created or updated resources. If it detects changes, it fetches the appropriate resource's status and only then updates the Function's status. + +The Function Controller observes the status of the underlying Deployment. If the minimum availability condition for the replicas is not satisfied, the Function Controller sets the **Running** status to `Unknown` with reason `MinimumReplicasUnavailable`. Such a Function should be considered unhealthy and the runtime profile or number of Replicas must be adjusted. + +![Function running](../../assets/svls-running.svg) diff --git a/docs/user/technical-reference/07-40-git-source-type.md b/docs/user/technical-reference/07-40-git-source-type.md new file mode 100644 index 00000000..fe63ee7d --- /dev/null +++ b/docs/user/technical-reference/07-40-git-source-type.md @@ -0,0 +1,31 @@ +# Git Source Type + +Depending on a runtime you use to build your Function (Node.js or Python), your Git repository must contain at least a directory with these files: + +- `handler.js` or `handler.py` with Function's code +- `package.json` or `requirements.txt` with Function's dependencies + +The Function CR must contain **spec.source.gitRepository** to specify that you use a Git repository for the Function's sources. + +To create a Function with the Git source, you must: + +1. Create a [Secret](https://kubernetes.io/docs/concepts/configuration/secret/) (optional, only if you must authenticate to the repository). +2. Create a [Function CR](../resources/06-10-function-cr.md) with your Function definition and references to the Git repository. + +> [!NOTE] +> For detailed steps, see the tutorial on [creating a Function from Git repository sources](../tutorials/01-11-create-git-function.md). + +You can have various setups for your Function's Git source with different: + +- Directory structures + + To specify the location of your code dependencies, use the **baseDir** parameter in the Function CR. For example, use `"/"` if you keep the source files at the root of your repository. + +- Authentication methods + + To define that you must authenticate to the repository with a password or token (`basic`), or an SSH key (`key`), use the **spec.source.gitRepository.auth** parameter in the Function CR. + +- Function's rebuild triggers + + To define whether the Function Controller must monitor a given branch or commit in the Git repository to rebuild the Function upon their changes, use the **spec.source.gitRepository.reference** parameter in the Function CR. + \ No newline at end of file diff --git a/docs/user/technical-reference/07-60-function-configuration-file.md b/docs/user/technical-reference/07-60-function-configuration-file.md new file mode 100644 index 00000000..3a99aca2 --- /dev/null +++ b/docs/user/technical-reference/07-60-function-configuration-file.md @@ -0,0 +1,178 @@ +# Function Configuration File + +When you initialize a Function (with the `init` command), Kyma CLI creates the `config.yaml` file in your workspace folder. This file contains the whole Function's configuration and specification not only for the Function custom resource (CR) but also any other related resources you create for it, such as Subscriptions and APIRules. + +## Specification for an Inline Function + +See the sample `config.yaml` for an inline Function for which code and dependencies are stored in the Function CR under the **spec.source** and **spec.deps** fields. This specification also contains the definition of a sample Subscription and APIRules for the Function: + +```yaml +name: function-practical-filip5 +namespace: testme +runtime: nodejs20 +runtimeImageOverride: europe-docker.pkg.dev/kyma-project/prod/function-runtime-nodejs20:v20240320-dacf4702 +labels: + app: serverless-test +source: + sourceType: inline + sourcePath: /tmp/cli + sourceHandlerName: /code/handler.js + depsHandlerName: /dependencies/package.json +resources: + limits: + cpu: 1 + memory: 1Gi + requests: + cpu: 500m + memory: 500Mi +subscriptions: + - name: function-practical-filip5 + typeMatching: exact + source: "" + types: + - sap.kyma.custom.demo-app.order.created.v1 +apiRules: + - name: function-practical-filip5 + gateway: kyma-system/kyma-gateway + service: + host: path.kyma.example.com + port: 80 + rules: + - methods: + - GET + - POST + - PUT + - PATCH + - DELETE + - HEAD + accessStrategies: [] + - path: /path1/something1 + methods: + - PUT + - PATCH + - DELETE + accessStrategies: + - handler: noop + - path: /path1/something2 + methods: + - GET + accessStrategies: + - config: + required_scope: ["read"] + handler: oauth2_introspection + - path: /path2 + methods: + - DELETE + accessStrategies: + - handler: jwt + config: + jwksUrls: + - {jwks_uri of your OpenID Connect-compliant identity provider} + trustedIssuers: + - {issuer URL of your OpenID Connect-compliant Identity provider} +env: + - name: REDIS_PASS + value: YgJUg8z6eA + - name: REDIS_PORT + value: "6379" + - name: REDIS_HOST + value: hb-redis-enterp-6541066a-edbc-422f-8bef-fafca0befea8-redis.testme.svc.cluster.local + - valueFrom: + configMapKeyRef: + Name: configmap1 + Key: token-field + - valueFrom: + secretKeyRef: + Name: secret1 + Key: token-field +schemaVersion: v1 +``` + +## Specification for a Git Function + +See the sample `config.yaml` for a [Git Function](07-40-git-source-type.md) for which code and dependencies are stored in a selected Git repository: + +```yaml +name: function-practical-marcin +namespace: iteration-review +runtime: nodejs20 +source: + sourceType: git + url: https://github.com/username/public-gitops.git + repository: my-repo + reference: main + baseDir: / + credentialsType: basic + credentialsSecretName: secret2 +``` + +## Parameters + +See all parameter descriptions. + +> [!NOTE] +> The **Default value** column specifies the values that Kyma CLI sets when applying resources in a cluster, if no other values are provided. + +| Parameter | Required | Related custom resource | Default value | Description | +|----------------------------------------------------------------|:--------:| ---------| ---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **name** | Yes | Function | | Specifies the name of your Function. | +| **namespace** | No | Function | `default` | Defines the namespace in which the Function is created. | +| **runtime** | Yes | Function | | Specifies the execution environment for your Function. The available values are `nodejs18` - deprecated, `nodejs20`, `python39` - deprecated, and `python312`. | +| **runtimeImageOverride** | No | Function | | Specifies the runtimes image which must be used instead of default one. | +| **labels** | No | Function | | Specifies the Function's Pod labels. | +| **source** | Yes | Function | | Provides details on the type and location of your Function's source code and dependencies. | +| **source.sourceType** | Yes | Function | | Defines whether you use either inline code or a Git repository as the source of the Function's code and dependencies. It must be set either to `inline` or `git`. | +| **source.sourcePath** | No | Function | Location of the `config.yaml` file | Specifies the absolute path to the directory with the Function's source code. | +| **source.sourceHandlerName** | No | Function | `handler.js` (Node.js) or `handler.py` (Python) | Defines the path to the file with your Function's code. Specify it if you want to store source code separately from the `config.yaml`. This path is a relative path to the one provided in **source.sourcePath**. | +| **source.depsHandlerName** | No | Function | `package.json` (Node.js) or `requirements.txt` (Python) | Defines the path to the file with your Function's dependencies. Specify it if you want to store dependencies separately from the `config.yaml`. This path is a relative path to the one provided in **source.sourcePath**. | +| **source.url** | No | Function | | Provides the address to the Git repository with the Function's code and dependencies. Depending on whether the repository is public or private and what authentication method is used to access it, the URL must start with the `http(s)`, `git`, or `ssh` prefix, and end with the `.git` suffix. | +| **source.repository** | No | Function | Function name | Specifies the name of the Git repository. | +| **source.reference** | No | Function | | Specifies either the branch name or the commit revision from which the Function Controller automatically fetches the changes in the Function's code and dependencies. | +| **source.baseDir** | No | Function | | Specifies the location of your code dependencies in the repository. It is recommended to keep the source files at the root of your repository (`/`). | +| **source.credentialsType** | No | Function | `basic` | Specifies the content type of the Secret with credentials to the Git repository. Defines if you must authenticate to the repository with a password or token (`basic`), or an SSH key (`key`). | +| **source.credentialsSecretName** | No | Function | | Specifies the name of the Secret with credentials to the Git repository. It is used by the Function Controller to authenticate to the Git repository to fetch the Function's source code and dependencies. This Secret must be stored in the same namespace as the [Function CR](../resources/06-10-function-cr.md). | +| **resources** | No | Function | | Defines CPU and memory available for the Function's Pod to use. | +| **resources.limits** | No | Function | | Defines the maximum available CPU and memory values for the Function. | +| **resources.limits.cpu** | No | Function | `100m` | Defines the maximum available CPU value for the Function. | +| **resources.limits.memory** | No | Function | `128Mi` | Defines the maximum available memory value for the Function. | +| **resources.requests** | No | Function | | Defines the minimum requested CPU and memory values for a Function. | +| **resources.requests.cpu** | No | Function | `50m` | Defines the minimum requested CPU value for the Function. | +| **resources.requests.memory** | No | Function | `64Mi` | Defines the minimum requested memory value for the Function. | +| **subscriptions** | No | Subscription | | Defines a Subscription by which the Function gets triggered to perform a business logic defined in the Function's source code. | +| **subscriptions.name** | Yes | Subscription | Function name | Specifies the name of the Subscription custom resource. It takes the name from the Function unless you specify otherwise. | +| **subscriptions.typeMatching** | No | Subscription | | Defines the matching type (`standard` or `exact`) for event types. When it is set to `exact`, Eventing does not do any kind of modifications to the provided `spec.types` internally. In case of `standard`, Eventing modifies the types internally to fulfil the backend requirements. It is set to `standard` unless you specify otherwise. | +| **subscriptions.source** | Yes | Subscription | | Defines the source of the event originated from. | +| **subscriptions.types** | Yes | Subscription | | Defines the list of event types used to trigger workloads. | +| **apiRules** | No | APIRule | | Provides the rules defining how your Function's Service API can be accessed. | +| **apiRules.name** | Yes | APIRule | Function name | Specifies the name of the exposed Service. It takes the name from the Function unless you specify otherwise. | +| **apiRules.gateway** | No | APIRule | `kyma-system/kyma-gateway` | Specifies the [Istio Gateway](https://istio.io/latest/docs/reference/config/networking/gateway/). | +| **apiRules.service** | No | APIRule | | Specifies the name of the exposed Service. | +| **apiRules.service.host** | No | APIRule | | Specifies the Service's communication address for inbound external traffic. | +| **apiRules.service.port** | No | APIRule | `80`. | Defines the port on which the Function's Service is exposed. This value cannot be modified. | +| **apiRules.rules** | Yes | APIRule | | Specifies the array of [Oathkeeper](https://www.ory.sh/oathkeeper/) access rules. | +| **apiRules.rules.methods** | No | APIRule | | Specifies the list of HTTP request methods available for **apiRules.rules.path** . | +| **apiRules.rules.accessStrategies** | Yes | APIRule | | Specifies the array of [Oathkeeper authenticators](https://www.ory.sh/oathkeeper/docs/pipeline/authn/). The supported authenticators are `oauth2_introspection`, `jwt`, `noop`, and `allow`. | +| **apiRules.rules.path** | No | APIRule | `/.*` | Specifies the path to the exposed Service. | +| **apiRules.rules.path.accessStrategies.handler** | Yes | APIRule | `allow` | Specifies one of the authenticators used: `oauth2_introspection`, `jwt`, `noop`, or `allow`. | +| **apiRules.rules.path.accessStrategies.config.** | No | APIRule | | Defines the handler used. It can be specified globally or per access rule. | +| **apiRules.rules.path.accessStrategies.config.required_scope** | No | APIRule | | Defines the [limits](https://oauth.net/2/scope/) that the client specifies for an access request. In turn, the authorization server issues the access token in the defined scope. | +| **apiRules.rules.path.accessStrategies.config.jwks_urls** | No | APIRule | | The URLs where ORY Oathkeeper can retrieve [JSON Web Keys](https://www.ory.sh/oathkeeper/docs/pipeline/authn/#jwt) from to validate the JSON Web Token. | +| **apiRules.rules.path.accessStrategies.config.trustedIssuers** | No | APIRule | | Sets a list of trusted token issuers. | +| **env.name** | No | Function | | Specifies the name of the environment variable to export for the Function. | +| **env.value** | No | Function | | Specifies the value of the environment variable to export for the Function. | +| **env.valueFrom** | No | Function | | Specifies that you want the Function to use values either from a Secret or a ConfigMap. These objects must be stored in the same namespace as the Function. | +| **env.valueFrom.configMapKeyRef** | No | Function | | Refers to the values from a ConfigMap that you want to use in the Function. | +| **env.valueFrom.configMapKeyRef.Name** | No | Function | | Specifies the name of the referred ConfigMap. | +| **env.valueFrom.configMapKeyRef.Key** | No | Function | | Specifies the key containing the referred value from the ConfigMap. | +| **env.valueFrom.secretKeyRef** | No | Function | | Refers to the values from a Secret that you want to use in the Function. | +| **env.valueFrom.secretKeyRef.Name** | No | Function | | Specifies the name of the referred Secret. | +| **env.valueFrom.secretKeyRef.Key** | No | Function | | Specifies the key containing the referred value from the Secret. | +| **schemaVersion** | Yes | Function | | Specifies the Subscription API version. | + +## Related Resources + +See the detailed descriptions of all related custom resources referred to in the `config.yaml`: + +- [Function](../resources/06-10-function-cr.md) +- [Subscription](https://kyma-project.io/docs/kyma/latest/05-technical-reference/00-custom-resources/evnt-01-subscription/) +- [API Rule](https://kyma-project.io/docs/kyma/latest/05-technical-reference/00-custom-resources/apix-01-apirule/) diff --git a/docs/user/technical-reference/07-70-function-specification.md b/docs/user/technical-reference/07-70-function-specification.md new file mode 100644 index 00000000..442a9d50 --- /dev/null +++ b/docs/user/technical-reference/07-70-function-specification.md @@ -0,0 +1,234 @@ +# Function's Specification + +With the Serverless module, you can create Functions in both Node.js and Python. Although the Function's interface is unified, its specification differs depending on the runtime used to run the Function. + +## Signature + +Function's code is represented by a handler that is a method that processes events. When your Function is invoked, it runs this handler method to process a given request and return a response. + +All Functions have a predefined signature with elements common for all available runtimes: + +- Functions' code must be introduced by the `main` handler name. +- Functions must accept two arguments that are passed to the Function handler: + - `event` + - `context` + +See these signatures for each runtime: + + + +#### **Node.js** + +```js +module.exports = { + main: function (event, context) { + return + } +} +``` + +#### **Python** + +```python +def main(event, context): + return +``` + + + +### Event Object + +The `event` object contains information about the event the Function refers to. For example, an API request event holds the HTTP request object. + +Functions in Kyma accept [CloudEvents](https://cloudevents.io/) (**ce**) with the following specification: + + + +#### **Node.js** + +```json +... +{ + "ce-type": "com.github.pull_request.opened", + "ce-source": "/cloudevents/spec/pull/123", + "ce-eventtypeversion": "v1", + "ce-specversion": "1.0", + "ce-id": "abc123", + "ce-time": "2020-12-20T13:37:33.647Z", + "data": {BUFFER}, + "tracer": {OPENTELEMETRY_TRACER}, + "extensions": { + "request": {INCOMING_MESSAGE}, + "response": {SERVER_RESPONSE}, + } +} +``` + +#### **Python** + +```json +{ + "ce-type": "com.github.pull_request.opened", + "ce-source": "/cloudevents/spec/pull/123", + "ce-eventtypeversion": "v1", + "ce-specversion": "1.0", + "ce-id": "abc123", + "ce-time": "2020-12-20T13:37:33.647Z", + "data": "", + "tracer": {OPENTELEMETRY_TRACER}, + "extensions": { + "request": {PICKLABLE_BOTTLE_REQUEST}, + } +} +``` + + + +See the detailed descriptions of these fields: + +| Field | Description | +|-------|-------------| +| **ce-type** | Type of the CloudEvent data related to the originating occurrence | +| **ce-source** | Unique context in which an event happened and can relate to an organization or a process | +| **ce-eventtypeversion** | Version of the CloudEvent type | +| **ce-specversion** | Version of the CloudEvent specification used for this event | +| **ce-id** | Unique identifier of the event | +| **ce-time** | Time at which the event was sent | +| **data** | Either JSON or a string, depending on the request type. Read more about [Buffer](https://nodejs.org/api/buffer.html) in Node.js and [bytes literals](https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals) in Python. | +| **tracer** | Fully configured OpenTelemetry [tracer](https://opentelemetry.io/docs/reference/specification/trace/api/#tracer) object that allows you to communicate with the user-defined trace backend service to share tracing data. For more information on how to use the tracer object see [Customize Function traces](../tutorials/01-100-customize-function-traces.md) | +| **extensions** | JSON object that can contain event payload, a Function's incoming request, or an outgoing response | + +### Event Object SDK + +The `event` object is extended by methods making some operations easier. You can use every method by providing `event.{FUNCTION_NAME(ARGUMENTS...)}`. + + + +#### **Node.js** + +| Method name | Arguments | Description | +|---------------|-----------|-------------| +| **setResponseHeader** | key, value | Sets a header to the `response` object based on the given key and value | +| **setResponseContentType** | type | Sets the `ContentType` header to the `response` object based on the given type | +| **setResponseStatus** | status | Sets the `response` status based on the given status | +| **publishCloudEvent** | event | **Deprecated: use `emitCloudEvent` instead.** Publishes a CloudEvent on the publisher service based on the given CloudEvent object | +| **buildResponseCloudEvent** | id, type, data | **Deprecated: use `emitCloudEvent` instead.** Builds a CloudEvent object based on the `request` CloudEvent object and the given arguments | +| **emitCloudEvent** | type, source, data, optionalCloudEventAttribute | Builds a CloudEvent based on the arguments and emits it on the eventing publisher service. You can pass any additional [cloudevent attributes](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/formats/json-format.md#2-attributes) as properties of the last optional argument `optionalCloudEventAttribute` | + +#### **Python** + +| Method name | Arguments | Description | +|----------|-----------|-------------| +| **publishCloudEvent** | event | Publishes a CloudEvent on the publisher service based on the given CloudEvent object | +| **buildResponseCloudEvent** | id, type, data | Builds a CloudEvent object based on the `request` CloudEvent object and the given arguments | + + + +### Context Object + +The `context` object contains information about the Function's invocation, such as runtime details, execution timeout, or memory limits. + +See sample context details: + +```json +... +{ + "function-name": "main", + "timeout": 180, + "runtime": "nodejs20", + "memory-limit": 200Mi +} +``` + +See the detailed descriptions of these fields: + +| Field | Description | +|-------|---------------------------------------------------------------------------------------------------------------------------------------------| +| **function-name** | Name of the invoked Function | +| **timeout** | Time, in seconds, after which the system cancels the request to invoke the Function | +| **runtime** | Environment used to run the Function. You can use `nodejs18` - deprecated, `nodejs20`, `python39` - deprecated, or `python312`. | +| **memory-limit** | Maximum amount of memory assigned to run a Function | + +## HTTP Requests + +You can use the **event.extensions.request** object to access properties and methods of a given request that vary depending on the runtime. For more information, read the API documentation for [Node.js Express](http://expressjs.com/en/api.html#req) and [Python](https://bottlepy.org/docs/dev/api.html#the-request-object). + +## Custom HTTP Responses + +By default, a failing Function simply throws an error to tell the Event Service to reinject the event at a later point. Such an HTTP-based Function returns the HTTP status code `500`. If you manage to invoke a Function successfully, the system returns the default HTTP status code `200`. + +Apart from these two default codes, you can define custom responses. Learn how to do that in Node.js and Python: + +By default, a failing Function simply throws an error to tell the Event Service to reinject the event at a later point. Such an HTTP-based Function returns the HTTP status code `500`. If you manage to invoke a Function successfully, the system returns the default HTTP status code `200`. + +Apart from these two default codes, you can define custom responses. Learn how to do that in Node.js and Python: + + + +#### **Node.js** + +To define custom responses in all Node.js runtimes, use the **event.extensions.response** object. + +This object is created by the Express framework and can be customized. For more information, read [Node.js API documentation](https://nodejs.org/docs/latest-v12.x/api/http.html#http_class_http_serverresponse). + +This example shows how to set such a custom response in Node.js for the HTTP status code `400`: + +```js +module.exports = { + main: function (event, context) { + if (event.extensions.request.query.id === undefined) { + res = event.extensions.response; + res.status(400) + return + } + return "42" + } +} +``` + +#### **Python** + +To define custom responses in all Python runtimes, use the **HTTPResponse** object available in Bottle. + +This object must be instantiated and can be customized. For more information, read [Bottle API documentation](https://bottlepy.org/docs/dev/api.html#the-response-object). + +The following example shows how to set such a custom response in Python for the HTTP status code `400`: + +```python +from bottle import HTTPResponse + +SUPPORTED_CONTENT_TYPES = ['application/json'] + +def main(event, context): + request = event['extensions']['request'] + + response_content_type = 'application/json' + headers = { + 'Content-Type': response_content_type + } + + status = 202 + response_payload = {'success': 'Message accepted.'} + + if request.headers.get('Content-Type') not in SUPPORTED_CONTENT_TYPES: + status = 400 + response_payload = json.dumps({'error': 'Invalid Content-Type.'}) + + return HTTPResponse(body=response_payload, status=status, headers=headers) +``` + + + +## Override Runtime Image + +You can use a custom runtime image to override the existing one. Your image must meet all the following requirements: + +- Expose the workload endpoint on the right port +- Provide liveness and readiness check endpoints at `/healthz` +- Fetch sources from the path under the `KUBELESS_INSTALL_VOLUME` environment +- Security support. Kyma runtimes are secure by default. You only need to protect your images. + +> [!NOTE] +> For better understanding, you can look at the [main Docker files](../../../config/serverless/templates/runtimes.yaml). They are responsible for building the final image based on the `base_image` argument. You, as a user, can override it and what we are doing in [this tutorial](../tutorials/01-110-override-runtime-image.md). + +Every Function's Pods container has the same system environments, which helps you configure the Functions server. For more information, read the [Environment variables](05-20-env-variables.md) page. diff --git a/docs/user/technical-reference/07-80-available-presets.md b/docs/user/technical-reference/07-80-available-presets.md new file mode 100644 index 00000000..588eb8cb --- /dev/null +++ b/docs/user/technical-reference/07-80-available-presets.md @@ -0,0 +1,35 @@ +# Available Presets + +Function's resources and replicas as well as resources for image-building Jobs are based on presets. A preset is a predefined group of values. There are two groups of presets defined for a Function CR and include the presents for: + +- Function's resources +- Image-building Job's resources + +## Usage + +If you want to apply values from a preset to a single Function, override the existing values for a given preset in the Function CR: First, remove the relevant fields from the Function CR and then, add the relevant preset labels. + +For example, to modify the default values for **buildResources**, remove all its entries from the Function CR and add an appropriate **serverless.kyma-project.io/build-resources-preset: {PRESET}** label to the Function CR. + +### Function's Resources + +| Preset | Request CPU | Request memory | Limit CPU | Limit memory | +| - | - | - | - | - | +| `XS` | `50m` | `64Mi` | `100m` | `128Mi` | +| `S` | `100m` | `128Mi` | `200m` | `256Mi` | +| `M` | `200m` | `256Mi` | `400m` | `512Mi` | +| `L` | `400m` | `512Mi` | `800m` | `1024Mi` | +| `XL` | `800m` | `1024Mi` | `1600m` | `2048Mi` | + +To apply values ​​from a given preset, use the **serverless.kyma-project.io/function-resources-preset: {PRESET}** label in the Function CR. + +### Build Job's Resources + +| Preset | Request CPU | Request memory | Limit CPU | Limit memory | +| - | - | - | - | - | +| `local-dev` | `200m` | `200Mi` | `400m` | `400Mi` | +| `slow` | `200m` | `200Mi` | `700m` | `700Mi` | +| `normal` | `700m` | `700Mi` | `1100m` | `1100Mi`| +| `fast` | `1100m` | `1100Mi` | `1700m` | `1100Mi`| + +To apply values ​​from a given preset, use the **serverless.kyma-project.io/build-resources-preset: {PRESET}** label in the Function CR. \ No newline at end of file diff --git a/docs/user/technical-reference/README.md b/docs/user/technical-reference/README.md new file mode 100644 index 00000000..5226938c --- /dev/null +++ b/docs/user/technical-reference/README.md @@ -0,0 +1,3 @@ +# Technical Reference + +In this section, you'll find the architecture documents, the configuration parameters, custom resources (CRs) and other useful references. diff --git a/docs/user/troubleshooting-guides/03-10-cannot-build-functions.md b/docs/user/troubleshooting-guides/03-10-cannot-build-functions.md new file mode 100644 index 00000000..077b0281 --- /dev/null +++ b/docs/user/troubleshooting-guides/03-10-cannot-build-functions.md @@ -0,0 +1,67 @@ +# Failure to Build Functions + +## Symptom + +You have issues when building your Function. + +## Cause + +In its default configuration, Serverless uses persistent volumes as the internal registry to store Docker images for Functions. The default storage size of such a volume is 20 GB. When this storage becomes full, you will have issues with building your Functions. + +## Remedy + +As a workaround, increase the default capacity up to a maximum of 100 GB by editing the `serverless-docker-registry` PersistentVolumeClaim (PVC) object on your cluster. + +1. Edit the `serverless-docker-registry` PVC: + + ```bash + kubectl edit pvc -n kyma-system serverless-docker-registry + ``` + +2. Change the value of **spec.resources.requests.storage** to higher, such as 30 GB, to increase the PVC capacity: + + ```yaml + ... + spec: + resources: + requests: + storage: 30Gi + ``` + +3. Save the changes and wait for a few minutes. Use this command to check if the **CAPACITY** of the `serverless-docker-registry` PVC has changed as expected: + + ```bash + kubectl get pvc serverless-docker-registry -n kyma-system + ``` + + You should get the following result: + + ```bash + NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE + serverless-docker-registry Bound pvc-a69b96hc-ahbc-k85d-0gh6-19gkcr4yns4k 30Gi RWO standard 23d + ``` + +If the value of the storage does not change, restart the Pod to which this PVC is bound to finish the volume resize. + +To do this, follow these steps: + +1. List all available Pods in the `kyma-system` namespace: + + ```bash + kubectl get pods -n kyma-system + ``` + +2. Search for the Pod with the `serverless-docker-registry-{UNIQUE_ID}` name and delete it. See the example below: + + ```bash + kubectl delete pod serverless-docker-registry-6869bd57dc-9tqxp -n kyma-system + ``` + + > [!WARNING] + > Do not remove Pods named `serverless-docker-registry-self-signed-cert-{UNIQUE_ID}`. + +3. Search for the `serverless-docker-registry` PVC again to check that the capacity was resized: + + ```bash + kubectl get pvc serverless-docker-registry -n kyma-system + ``` diff --git a/docs/user/troubleshooting-guides/03-20-failing-function-container.md b/docs/user/troubleshooting-guides/03-20-failing-function-container.md new file mode 100644 index 00000000..6f969271 --- /dev/null +++ b/docs/user/troubleshooting-guides/03-20-failing-function-container.md @@ -0,0 +1,16 @@ +# Failing Function Container + +## Symptom + +The container suddenly fails when you use the `kyma run function` command with these flags: + +- `runtime=nodejs20` +- `debug=true` +- `hot-deploy=true` + +In such a case, you can see the `[nodemon] app crashed` message in the container's logs. + +## Remedy + +If you use Kyma in Kubernetes, Kubernetes itself should run the Function in the container. +If you use Kyma without Kubernetes, you have to rerun the container yourself. diff --git a/docs/user/troubleshooting-guides/03-40-function-build-failing-k3d.md b/docs/user/troubleshooting-guides/03-40-function-build-failing-k3d.md new file mode 100644 index 00000000..aee50fe9 --- /dev/null +++ b/docs/user/troubleshooting-guides/03-40-function-build-failing-k3d.md @@ -0,0 +1,52 @@ +# Functions Failing to Build on k3d + +## Symptom + +There are rare cases, for some k3d versions and configurations, where users experience Functions failing to be built. + +Your Function cannot be built and you get the following output: + + ```bash + $ kubectl get functions.serverless.kyma-project.io nyfun + NAME CONFIGURED BUILT RUNNING RUNTIME VERSION AGE + myfun True False nodejs20 1 3h15m + ``` + +Additionally, the Function build job shows the following error, meaning that your host k3d environment is likely to experience the problem: + + ```bash + $ kubectl logs myfun-build-zqhk8-7xl6h + kaniko should only be run inside of a container, run with the --force flag if you are sure you want to continue + ``` + +## Cause + +This problem originates in [kaniko](https://github.com/GoogleContainerTools/kaniko) - the container image build tool used in Kyma. kaniko is designed to be run in a container and this is how it is executed in Kyma (as build jobs). +kaniko has a detection mechanism to verify whether the build is actually executed in a container and fails in case it is not. +This detection mechanism has issues and in some circumstances (that is, hosts with cgroups in version 2 or other, not yet clearly identified) it shows a false positive result. + +Related issues: + +- https://github.com/kyma-project/kyma/issues/13051 +- https://github.com/GoogleContainerTools/kaniko/issues/1592 + +## Remedy + +You can force kaniko to skip the verification by overriding the kaniko execution arguments with the `--force` flag. + +Introduce a file with overrides, for example `my-overrides.yaml`. + + ```yaml + serverless: + containers: + manager: + envs: + functionBuildExecutorArgs: + value: --insecure,--skip-tls-verify,--skip-unused-stages,--log-format=text,--cache=true,--use-new-run,--compressed-caching=false,--force + ``` + +Use the file to override the default configuration while deploying Kyma on your k3d instance: + + ```bash + kyma deploy --values-file my-overrides.yaml + ``` diff --git a/docs/user/troubleshooting-guides/03-50-serverless-periodically-restaring.md b/docs/user/troubleshooting-guides/03-50-serverless-periodically-restaring.md new file mode 100644 index 00000000..029a6ab1 --- /dev/null +++ b/docs/user/troubleshooting-guides/03-50-serverless-periodically-restaring.md @@ -0,0 +1,13 @@ +# Serverless Periodically Restarting + +## Symptom + +When reconciling Git-sourced Functions, Serverless restarts every 10 minutes. + +## Cause + +Function Controller is polling for changes in referenced Git repositories. If you have a lot of Git-sourced Functions and they were deployed together at approximately the same time, their git sources will be checked out in a synchronized pulse (every 10 minutes). If you happen to reference large repositories (multi-repositories), there will be rhythmical, high demand on CPU and I/O resources necessary to check out repositories. This may cause Function Controller to crash and restart. + +## Remedy + +Avoid using multi-repositories or large repositories to source your git Functions. Using small, dedicated Function repositories decreases the CPU and I/O resources used to check out Git sources, and hence improves the stability of Function Controller. diff --git a/docs/user/troubleshooting-guides/README.md b/docs/user/troubleshooting-guides/README.md new file mode 100644 index 00000000..7c26f104 --- /dev/null +++ b/docs/user/troubleshooting-guides/README.md @@ -0,0 +1,5 @@ +# Troubleshooting + +The troubleshooting guides aim to identify the most common recurring problems the users face, as well as the most suitable solutions to these problems. + +If you can't find a solution, don't hesitate to create a [GitHub](https://github.com/kyma-project/kyma/issues) issue or reach out to our [Slack channel](https://kyma-community.slack.com/) to get direct support from the community. diff --git a/docs/user/tutorials/01-10-create-inline-function.md b/docs/user/tutorials/01-10-create-inline-function.md new file mode 100644 index 00000000..bb7bacb6 --- /dev/null +++ b/docs/user/tutorials/01-10-create-inline-function.md @@ -0,0 +1,147 @@ +# Create and Modify an Inline Function + +This tutorial shows how you can create a simple "Hello World" Function in Node.js. The Function's code and dependencies are defined as an inline code in the Function's **spec**. + +Serverless also allows you to store the Function's code and dependencies as sources in a Git repository. To learn more, read how to [Create a Git Function](01-11-create-git-function.md). +To learn more about Function's signature, `event` and `context` objects, and custom HTTP responses the Function returns, read [Function’s specification](../technical-reference/07-70-function-specification.md). + +> [!NOTE] +> Read about [Istio sidecars in Kyma and why you want them](https://kyma-project.io/docs/kyma/latest/01-overview/service-mesh/smsh-03-istio-sidecars-in-kyma/). Then, check how to [enable automatic Istio sidecar proxy injection](https://kyma-project.io/docs/kyma/latest/04-operation-guides/operations/smsh-01-istio-enable-sidecar-injection/). For more details, see [Default Istio setup in Kyma](https://kyma-project.io/docs/kyma/latest/01-overview/service-mesh/smsh-02-default-istio-setup-in-kyma/). + +## Steps + +You can create a Function with Kyma dashboard, Kyma CLI, or kubectl: + + + +#### **Kyma Dashboard** + +> [!NOTE] +> Kyma dashboard uses Busola, which is not installed by default. Follow the [installation instructions](https://github.com/kyma-project/busola/blob/main/docs/install-kyma-dashboard-manually.md). + +1. Create a namespace or select one from the drop-down list in the top navigation panel. + +2. Go to **Workloads** > **Functions** and select **Create Function**. + +3. In the dialog box, provide the Function's name or click on **Generate**. + +> [!NOTE] +> The **Node.js Function** preset is selected by default. It means that the selected runtime is `Node.js`, and the **Source** code is autogenerated. You can choose the Python runtime by clicking on the **Choose preset** button. + + ```js + module.exports = { + main: async function (event, context) { + const message = + `Hello World` + + ` from the Kyma Function ${context['function-name']}` + + ` running on ${context.runtime}!`; + console.log(message); + return message; + }, + }; + ``` + +The dialog box closes. Wait for the **Status** field to change into `RUNNING`, confirming that the Function was created successfully. + +1. If you decide to modify it, click **Edit** and confirm changes afterward by selecting the **Update** button. You will see the message at the bottom of the screen confirming the Function was updated. + +#### **Kyma CLI** + +1. Export these variables: + + ```bash + export NAME={FUNCTION_NAME} + export NAMESPACE={FUNCTION_NAMESPACE} + ``` + +2. Create your local development workspace. + + a. Create a new folder to keep the Function's code and configuration in one place: + + ```bash + mkdir {FOLDER_NAME} + cd {FOLDER_NAME} + ``` + + b. Create initial scaffolding for the Function: + + ```bash + kyma init function --name $NAME --namespace $NAMESPACE + ``` + +3. Code and configure. + + Open the workspace in your favorite IDE. If you have Visual Studio Code installed, run the following command from the terminal in your workspace folder: + + ```bash + code . + ``` + + It's time to inspect the code and the `config.yaml` file. Feel free to adjust the "Hello World" sample code. + +4. Deploy and verify. + + a. Call the `apply` command from the workspace folder. It will build the container and run it on the Kyma runtime pointed by your current KUBECONFIG file: + + ```bash + kyma apply function + ``` + + b. Check if your Function was created successfully: + + ```bash + kubectl get functions $NAME -n $NAMESPACE + ``` + + You should get a result similar to this example: + + ```bash + NAME CONFIGURED BUILT RUNNING RUNTIME VERSION AGE + test-function True True True nodejs20 1 96s + ``` + +#### **kubectl** + +1. Export these variables: + + ```bash + export NAME={FUNCTION_NAME} + export NAMESPACE={FUNCTION_NAMESPACE} + ``` + +2. Create a Function CR that specifies the Function's logic: + + ```bash + cat < diff --git a/docs/user/tutorials/01-100-customize-function-traces.md b/docs/user/tutorials/01-100-customize-function-traces.md new file mode 100644 index 00000000..b8ede922 --- /dev/null +++ b/docs/user/tutorials/01-100-customize-function-traces.md @@ -0,0 +1,100 @@ +# Customize Function Traces + +This tutorial shows how to use the built-in OpenTelemetry tracer object to send custom trace data to the trace backend. + +Kyma Functions are instrumented to handle trace headers. This means that every time you call your Function, the executed logic is traceable using a dedicated span visible in the trace backend (that is, start time and duration). +Additionally, you can extend the default trace context and create your own custom spans as you wish (that is, when calling a remote service in your distributed application) or add additional information to the tracing context by introducing events and tags. The following tutorial shows you how to do it using tracer client that is available as part of the [event](../technical-reference/07-70-function-specification.md#event-object) object. + +## Prerequisites + +Before you start, make sure you have these tools installed: + +- [Telemetry component installed](https://kyma-project.io/docs/kyma/latest/04-operation-guides/operations/02-install-kyma/#install-specific-components) +- [Trace pipeline configured](https://github.com/kyma-project/telemetry-manager/blob/main/docs/user/03-traces.md#setting-up-a-tracepipeline) + +## Steps + +The following code samples illustrate how to enrich the default trace with custom spans, events, and tags: + +1. [Create an inline Function](01-10-create-inline-function.md) with the following body: + + + +#### **Node.js** + + ```javascript + + const { SpanStatusCode } = require("@opentelemetry/api/build/src/trace/status"); + const axios = require("axios") + module.exports = { + main: async function (event, context) { + + const data = { + name: "John", + surname: "Doe", + type: "Employee", + id: "1234-5678" + } + + const span = event.tracer.startSpan('call-to-acme-service'); + return await callAcme(data) + .then(resp => { + if(resp.status!==200){ + throw new Error("Unexpected response from acme service"); + } + span.addEvent("Data sent"); + span.setAttribute("data-type", data.type); + span.setAttribute("data-id", data.id); + span.setStatus({code: SpanStatusCode.OK}); + return "Data sent"; + }).catch(err=> { + console.error(err) + span.setStatus({ + code: SpanStatusCode.ERROR, + message: err.message, + }); + return err.message; + }).finally(()=>{ + span.end(); + }); + } + } + + let callAcme = (data)=>{ + return axios.post('https://acme.com/api/people', data) + } + ``` + +#### **Python** + + [OpenTelemetry SDK](https://opentelemetry.io/docs/instrumentation/python/manual/#traces) allows you to customize trace spans and events. + + ```python + import requests + import time + + def main(event, context): + # Create a new span to track some work + with event.tracer.start_as_current_span("parent"): + time.sleep(1) + + # Create a nested span to track nested work + with event.tracer.start_as_current_span("child"): + time.sleep(2) + # the nested span is closed when it's out of scope + + # Now the parent span is the current span again + time.sleep(1) + + # This span is also closed when it goes out of scope + + # This request will be auto-intrumented + r = requests.get('https://swapi.dev/api/people/2') + return r.json() + ``` + + + +2. [Expose your Function](01-20-expose-function.md). + +3. Find the traces for the Function in the trace backend. diff --git a/docs/user/tutorials/01-11-create-git-function.md b/docs/user/tutorials/01-11-create-git-function.md new file mode 100644 index 00000000..808ceeba --- /dev/null +++ b/docs/user/tutorials/01-11-create-git-function.md @@ -0,0 +1,148 @@ +# Create a Git Function + +This tutorial shows how you can build a Function from code and dependencies stored in a Git repository, which is an alternative way to keeping the code in the Function CR. The tutorial is based on the Function from the [`orders service` example](https://github.com/kyma-project/examples/tree/main/orders-service). It describes steps required to fetch the Function's source code and dependencies from a public Git repository that does not need any authentication method. However, it also provides additional guidance on how to secure it if you are using a private repository. + +To learn more about Git repository sources for Functions and different ways of securing your repository, read about the [Git source type](../technical-reference/07-40-git-source-type.md). + +> [!NOTE] +> Read about [Istio sidecars in Kyma and why you want them](https://kyma-project.io/docs/kyma/latest/01-overview/service-mesh/smsh-03-istio-sidecars-in-kyma/). Then, check how to [enable automatic Istio sidecar proxy injection](https://kyma-project.io/docs/kyma/latest/04-operation-guides/operations/smsh-01-istio-enable-sidecar-injection/). For more details, see [Default Istio setup in Kyma](https://kyma-project.io/docs/kyma/latest/01-overview/service-mesh/smsh-02-default-istio-setup-in-kyma/). + +## Steps + +You can create a Function either with kubectl or Kyma dashboard: + + + +#### **Kyma Dashboard** + +> [!NOTE] +> Kyma dashboard uses Busola, which is not installed by default. Follow the [installation instructions](https://github.com/kyma-project/busola/blob/main/docs/install-kyma-dashboard-manually.md). + +1. Create a namespace or select one from the drop-down list in the top navigation panel. + +2. Create a Secret (optional). + + If you use a secured repository, you must first create a Secret with either basic (username and password or token) or SSH key authentication to this repository in the same namespace as the Function. To do that, follow these sub-steps: + + - Open your namespace view. In the left navigation panel, go to **Configuration** > **Secrets** and select the **Create Secret** button. + + - Open the **Advanced** view and enter the Secret name and type. + + - Under **Data**, enter these key-value pairs with credentials: + + - Basic authentication: `username: {USERNAME}` and `password: {PASSWORD_OR_TOKEN}` + + - SSH key: `key: {SSH_KEY}` + + > [!NOTE] + > Read more about the [supported authentication methods](../technical-reference/07-40-git-source-type.md). + + - Confirm by selecting **Create**. + +3. To connect the repository, go to **Workloads** > **Functions** > **Create Function**. + +4. Provide or generate the Function's name. + +5. Go to **Advanced**, change **Source Type** from **Inline** to **Git Repository**. + +6. Choose `JavaScript` from the **Language** dropdown and select the proper runtime. + +7. Click on the **Git Repository** section and enter the following values: + - Repository **URL**: `https://github.com/kyma-project/examples.git` + - **Base Dir**:`orders-service/function` + - **Reference**:`main` + + > [!NOTE] + > If you want to connect a secured repository instead of a public one, toggle the **Auth** switch. In the **Auth** section, choose **Secret** from the list and choose the preferred type. + +8. Click **Create**. + + After a while, a message confirms that the Function has been created. + Make sure that the new Function has the `RUNNING` status. + +#### **kubectl** + +1. Export these variables: + + ```bash + export GIT_FUNCTION={GIT_FUNCTION_NAME} + export NAMESPACE={FUNCTION_NAMESPACE} + ``` + +2. Create a Secret (optional). + + If you use a secured repository, follow the sub-steps for the basic or SSH key authentication: + + - Basic authentication (username and password or token) to this repository in the same namespace as the Function: + + 1. Generate a [personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic) and copy it. + 2. Create a Secret containg your username and the generated token. + + ```bash + kubectl -n $NAMESPACE create secret generic git-creds-basic --from-literal=username={GITHUB_USERNAME} --from-literal=password={GENERATED_PERSONAL_TOKEN} + ``` + + - SSH key: + + 1. Generate a new SSH key pair (private and public). Follow [this tutorial](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent) to learn how to do it. Alternatively, you can use the existing pair. + 2. Install the generated private key in Kyma, as a Kubernetes Secret that lives in the same namespace as your Function. + + ```bash + kubectl -n $NAMESPACE create secret generic git-creds-ssh --from-file=key={PATH_TO_THE_FILE_WITH_PRIVATE_KEY} + ``` + + 3. Configure the public key in GitHub. Follow the steps described in [this tutorial](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account). + + > [!NOTE] + > Read more about the [supported authentication methods](../technical-reference/07-40-git-source-type.md). + +3. Create a Function CR that specifies the Function's logic and points to the directory with code and dependencies in the given repository. It also specifies the Git repository metadata: + + ```bash + cat < [!NOTE] + > If you use a secured repository, add the **auth** object with the adequate **type** and **secretName** fields to the spec under **gitRepository**: + + ```yaml + gitRepository: + ... + auth: + type: # "basic" or "key" + secretName: # "git-creds-basic" or "git-creds-key" + ``` + + > [!NOTE] + > To avoid performance degradation caused by large Git repositories and large monorepos, [Function Controller](../resources/06-10-function-cr.md#related-resources-and-components) implements a configurable backoff period for the source checkout based on `APP_FUNCTION_REQUEUE_DURATION`. If you want to allow the controller to perform the source checkout with every reconciliation loop, disable the backoff period by marking the Function CR with the annotation `serverless.kyma-project.io/continuousGitCheckout: true` + + > [!NOTE] + > See this [Function's code and dependencies](https://github.com/kyma-project/examples/tree/main/orders-service). + +4. Check if your Function was created and all conditions are set to `True`: + + ```bash + kubectl get functions $GIT_FUNCTION -n $NAMESPACE + ``` + + You should get a result similar to this example: + + ```bash + NAME CONFIGURED BUILT RUNNING RUNTIME VERSION AGE + test-function True True True nodejs20 1 96s + ``` + + diff --git a/docs/user/tutorials/01-110-override-runtime-image.md b/docs/user/tutorials/01-110-override-runtime-image.md new file mode 100644 index 00000000..a469e419 --- /dev/null +++ b/docs/user/tutorials/01-110-override-runtime-image.md @@ -0,0 +1,88 @@ +# Override Runtime Image + +This tutorial shows how to build a custom runtime image and override the Function's base image with it. + +## Prerequisites + +Before you start, make sure you have these tools installed: + +- [Serverless module installed](https://kyma-project.io/docs/kyma/latest/04-operation-guides/operations/08-install-uninstall-upgrade-kyma-module/) in a cluster + +## Steps + +Follow these steps: + +1. Follow [this example](https://github.com/kyma-project/serverless/tree/main/examples/custom-serverless-runtime-image) to build the Python's custom runtime image. + + + +#### **Kyma CLI** + +2. Export these variables: + + ```bash + export NAME={FUNCTION_NAME} + export NAMESPACE={FUNCTION_NAMESPACE} + export RUNTIME_IMAGE={RUNTIME_IMAGE_WITH_TAG} + ``` + +3. Create your local development workspace using the built image: + + ```bash + mkdir {FOLDER_NAME} + cd {FOLDER_NAME} + kyma init function --name $NAME --namespace $NAMESPACE --runtime-image-override $RUNTIME_IMAGE --runtime python312 + ``` + +4. Deploy your Function: + + ```bash + kyma apply function + ``` + +5. Verify whether your Function is running: + + ```bash + kubectl get functions $NAME -n $NAMESPACE + ``` + +#### **kubectl** + +2. Export these variables: + + ```bash + export NAME={FUNCTION_NAME} + export NAMESPACE={FUNCTION_NAMESPACE} + export RUNTIME_IMAGE={RUNTIME_IMAGE_WITH_TAG} + ``` + +3. Create a Function CR that specifies the Function's logic: + + ```bash + cat < diff --git a/docs/user/tutorials/01-120-inject-envs.md b/docs/user/tutorials/01-120-inject-envs.md new file mode 100644 index 00000000..abe22e6c --- /dev/null +++ b/docs/user/tutorials/01-120-inject-envs.md @@ -0,0 +1,147 @@ +# Inject Environment Variables + +This tutorial shows how to inject environment variables into Function. + +You can specify environment variables in the Function definition, or define references to the Kubernetes Secrets or ConfigMaps. + +## Prerequisites + +Before you start, make sure you have these tools installed: + +- [Serverless module installed](https://kyma-project.io/docs/kyma/latest/04-operation-guides/operations/08-install-uninstall-upgrade-kyma-module/) in a cluster + +## Steps + +Follow these steps: + +1. Create your ConfigMap + +```bash +kubectl create configmap my-config --from-literal config-env="I come from config map" +``` + +2. Create your Secret + +```bash +kubectl create secret generic my-secret --from-literal secret-env="I come from secret" +``` + + + +#### **Kyma CLI** + +3. Generate the Function's configuration and sources: + + ```bash + kyma init function --name my-function + ``` + +4. Define environment variables as part of the Function configuration file. Modify `config.yaml` with the following: + + ```yaml + name: my-function + namespace: default + runtime: nodejs20 + source: + sourceType: inline + env: + - name: env1 + value: "I come from function definition" + - name: env2 + valueFrom: + configMapKeyRef: + name: my-config + key: config-env + - name: env3 + valueFrom: + secretKeyRef: + name: my-secret + key: secret-env + ``` + +5. Use injected environment variables in the handler file. Modify `handler.js` with the following: + + ```js + module.exports = { + main: function (event, context) { + envs = ["env1", "env2", "env3"] + envs.forEach(function(key){ + console.log(`${key}:${readEnv(key)}`) + }); + return 'Hello Serverless' + } + } + + readEnv=(envKey) => { + if(envKey){ + return process.env[envKey]; + } + return + } + ``` + +6. Deploy your Function: + + ```bash + kyma apply function + ``` + +7. Verify whether your Function is running: + + ```bash + kubectl get functions my-function + ``` + +#### **kubectl** + +3. Create a Function CR that specifies the Function's logic: + + ```bash + cat < { + if(envKey){ + return process.env[envKey]; + } + return + } + EOF + ``` + +4. Verify whether your Function is running: + + ```bash + kubectl get functions my-function + ``` + + diff --git a/docs/user/tutorials/01-130-use-external-scalers.md b/docs/user/tutorials/01-130-use-external-scalers.md new file mode 100644 index 00000000..19893376 --- /dev/null +++ b/docs/user/tutorials/01-130-use-external-scalers.md @@ -0,0 +1,195 @@ +# Use External Scalers + +This tutorial shows how to use an external resource scaler, for example, HorizontalPodAutoscaler (HPA) or Keda's ScaledObject, with the Serverless Function. + +Keep in mind that the Serverless Functions implement the [scale subresource](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#scale-subresource), which means that you can use any Kubernetes-based scaler. + +## Prerequisites + +Before you start, make sure you have these tools installed: + +- [Keda module enabled](https://kyma-project.io/docs/kyma/latest/04-operation-guides/operations/08-install-uninstall-upgrade-kyma-module/) + +## Steps + +Follow these steps: + + + +#### **HPA** + +1. Create your Function with the `replicas` value set to 1, to prevent the internal Serverless HPA creation: + + ```bash + cat < [!NOTE] + > This tutorial uses the `cpu` trigger because of its simple configuration. If you want to use another trigger, check the official [list of supported triggers](https://keda.sh/docs/scalers/). + +3. After a few seconds, ScaledObject should be up to date and contain information about the actual replicas: + + ```bash + kubectl get scaledobject scaled-function + ``` + + You should get a result similar to this example: + + ```bash + NAME SCALETARGETKIND SCALETARGETNAME MIN MAX TRIGGERS AUTHENTICATION READY ACTIVE FALLBACK AGE + scaled-function serverless.kyma-project.io/v1alpha2.Function scaled-function 5 10 cpu True True Unknown 4m15s + ``` + +#### **Keda Prometheus** + +1. Create your Function with the **replicas** value set to `1` to prevent the internal Serverless HPA creation: + + ```bash + cat < [!NOTE] + > This tutorial uses the `prometheus` trigger because of its simple configuration. If you want to use another trigger, check the official [list of supported triggers](https://keda.sh/docs/scalers/). + +3. After a few seconds, ScaledObject should be up to date and contain information about the actual replicas: + + ```bash + kubectl get scaledobject scaled-function + ``` + + You should get a result similar to this example: + + ```bash + NAME SCALETARGETKIND SCALETARGETNAME MIN MAX TRIGGERS AUTHENTICATION READY ACTIVE FALLBACK AGE + scaled-function serverless.kyma-project.io/v1alpha2.Function scaled-function 1 5 prometheus True True Unknown 4m15s + ``` + +Check out this [example](https://github.com/kyma-project/keda-manager/tree/main/examples/scale-to-zero-with-keda) to see how to use Kyma Serverless and Eventing in combination with Keda to accomplish scaling to zero. + + diff --git a/docs/user/tutorials/01-140-use-secret-mounts.md b/docs/user/tutorials/01-140-use-secret-mounts.md new file mode 100644 index 00000000..c0a5de59 --- /dev/null +++ b/docs/user/tutorials/01-140-use-secret-mounts.md @@ -0,0 +1,123 @@ +# Access to Secrets Mounted as Volume + +This tutorial shows how to use Secrets mounted as volume with the Serverless Function. +It's based on a simple Function in Python 3.9. The Function reads data from Secret and returns it. + +## Prerequisites + +Before you start, make sure you have these tools installed: + +- [Serverless module installed](https://kyma-project.io/docs/kyma/latest/04-operation-guides/operations/08-install-uninstall-upgrade-kyma-module/) in a cluster + +## Steps + +Follow these steps: + +1. Export these variables: + + ```bash + export FUNCTION_NAME={FUNCTION_NAME} + export NAMESPACE={FUNCTION_NAMESPACE} + export DOMAIN={DOMAIN_NAME} + + export SECRET_NAME={SECRET_NAME} + export SECRET_DATA_KEY={SECRET_DATA_KEY} + export SECRET_MOUNT_PATH={SECRET_MOUNT_PATH} + ``` + +2. Create a Secret: + + ```bash + kubectl -n $NAMESPACE create secret generic $SECRET_NAME \ + --from-literal=$SECRET_DATA_KEY={SECRET_DATA_VALUE} + ``` + +3. Create your Function with `secretMounts`: + + ```bash + cat < [!NOTE] + > Read more about [creating Functions](01-10-create-inline-function.md). + +4. Create an APIRule: + + The following steps allow you to test the Function in action. + + ```bash + cat < [!NOTE] + > Read more about [exposing Functions](01-20-expose-function.md). + +5. Call Function: + + ```bash + curl https://$FUNCTION_NAME.$DOMAIN + ``` + + You should get `{SECRET_DATA_VALUE}` as a result. + +6. Next steps: + + Now you can edit the Secret and see if the Function returns the new value from the Secret. + + To edit your Secret, use: + + ```bash + kubectl -n $NAMESPACE edit secret $SECRET_NAME + ``` + + To encode values used in `data` from the Secret, use `base64`, for example: + + ```bash + echo -n '{NEW_SECRET_DATA_VALUE}' | base64 + ``` + + Calling the Function again (using `curl`) must return `{NEW_SECRET_DATA_VALUE}`. + Note that the Secret propagation may take some time, and the call may initially return the old value. diff --git a/docs/user/tutorials/01-20-expose-function.md b/docs/user/tutorials/01-20-expose-function.md new file mode 100644 index 00000000..053fff64 --- /dev/null +++ b/docs/user/tutorials/01-20-expose-function.md @@ -0,0 +1,163 @@ +# Expose a Function with an API Rule + +This tutorial shows how you can expose your Function to access it outside the cluster, through an HTTP proxy. To expose it, use an [APIRule custom resource (CR)](https://kyma-project.io/docs/kyma/latest/05-technical-reference/00-custom-resources/apix-01-apirule/). Function Controller reacts to an instance of the APIRule CR and, based on its details, it creates an Istio VirtualService and Oathkeeper Access Rules that specify your permissions for the exposed Function. + +When you complete this tutorial, you get a Function that: + +- Is available on an unsecured endpoint (**handler** set to `noop` in the APIRule CR). +- Accepts the `GET`, `POST`, `PUT`, and `DELETE` methods. + +To learn more about securing your Function, see the [Expose and secure a workload with OAuth2](https://kyma-project.io/docs/kyma/latest/03-tutorials/00-api-exposure/apix-05-expose-and-secure-a-workload/apix-05-01-expose-and-secure-workload-oauth2/) or [Expose and secure a workload with JWT](https://kyma-project.io/docs/kyma/latest/03-tutorials/00-api-exposure/apix-05-expose-and-secure-a-workload/apix-05-03-expose-and-secure-workload-jwt/) tutorials. + +Read also about [Function’s specification](../technical-reference/07-70-function-specification.md) if you are interested in its signature, `event` and `context` objects, and custom HTTP responses the Function returns. + +## Prerequisites + +- [Existing Function](01-10-create-inline-function.md) +- [API Gateway component installed](https://kyma-project.io/docs/kyma/latest/04-operation-guides/operations/02-install-kyma/#install-specific-components) + +## Steps + +You can expose a Function with Kyma dashboard, Kyma CLI, or kubectl: + + + +#### **Kyma Dashboard** + +> [!NOTE] +> Kyma dashboard uses Busola, which is not installed by default. Follow the [installation instructions](https://github.com/kyma-project/busola/blob/main/docs/install-kyma-dashboard-manually.md). + +1. Select a namespace from the drop-down list in the top navigation panel. Make sure the namespace includes the Function that you want to expose through an APIRule. + +2. Go to **Discovery and Network** > **API Rules**, and click on **Create API Rule**. + +3. Enter the following information: + + - The APIRule's **Name** matching the Function's name. + + > [!NOTE] + > The APIRule CR can have a name different from that of the Function, but it is recommended that all related resources share a common name. + + - **Service Name** matching the Function's name. + + - **Host** to determine the host on which you want to expose your Function. You must change the `*` symbol at the beginning to the subdomain name you want. + +4. In the **Rules > Access Strategies > Config** section, change the handler from `allow` to `noop` and select all the methods below. + +5. Select **Create** to confirm your changes. + +6. Check if you can access the Function by selecting the HTTPS link under the **Host** column for the newly created APIRule. + +#### **Kyma CLI** + +1. Export these variables: + + ```bash + export DOMAIN={DOMAIN_NAME} + export NAME={FUNCTION_NAME} + export NAMESPACE={NAMESPACE_NAME} + ``` + + > [!NOTE] + > The Function takes the name from the Function CR name. The APIRule CR can have a different name but for the purpose of this tutorial, all related resources share a common name defined under the **NAME** variable. +2. Download the latest configuration of the Function from the cluster. This way, you update the local `config.yaml` file with the Function's code. + + ```bash + kyma sync function $NAME -n $NAMESPACE + ``` + +3. Edit the local `config.yaml` file and add the **apiRules** schema for the Function at the end of the file: + + ```yaml + apiRules: + - name: {FUNCTION_NAME} + service: + host: {FUNCTION_NAME}.{DOMAIN_NAME} + rules: + - methods: + - GET + - POST + - PUT + - DELETE + accessStrategies: + - handler: noop + ``` + +4. Apply the new configuration to the cluster: + + ```bash + kyma apply function + ``` + +5. Check if the Function's code was pushed to the cluster and reflects the local configuration: + + ```bash + kubectl get apirules $NAME -n $NAMESPACE + ``` + +6. Check that the APIRule was created successfully and has the status `OK`: + + ```bash + kubectl get apirules $NAME -n $NAMESPACE -o=jsonpath='{.status.APIRuleStatus.code}' + ``` + +7. Call the Function's external address: + + ```bash + curl https://$NAME.$DOMAIN + ``` + +#### **kubectl** + +1. Export these variables: + + ```bash + export DOMAIN={DOMAIN_NAME} + export NAME={FUNCTION_NAME} + export NAMESPACE={FUNCTION_NAMESPACE} + ``` + + > [!NOTE] + > The Function takes the name from the Function CR name. The APIRule CR can have a different name but for the purpose of this tutorial, all related resources share a common name defined under the **NAME** variable. + +2. Create an APIRule CR for your Function. It is exposed on port `80`, which is the default port of the [Service Placeholder](../technical-reference/04-10-architecture.md). + + ```bash + cat < \ No newline at end of file diff --git a/docs/user/tutorials/01-30-manage-functions-with-kyma-cli.md b/docs/user/tutorials/01-30-manage-functions-with-kyma-cli.md new file mode 100644 index 00000000..d6ef5249 --- /dev/null +++ b/docs/user/tutorials/01-30-manage-functions-with-kyma-cli.md @@ -0,0 +1,111 @@ +# Manage Functions with Kyma CLI + +This tutorial shows how to use the available CLI commands to manage Functions in Kyma. You will see how to: + +1. Create local files that contain the basic configuration for a sample "Hello World" Python Function (`kyma init function`). +2. Generate a Function custom resource (CR) from these files and apply it on your cluster (`kyma apply function`). +3. Fetch the current state of your Function's cluster configuration after it was modified (`kyma sync function`). + +> [!NOTE] +> Read about [Istio sidecars in Kyma and why you want them](https://kyma-project.io/docs/kyma/latest/01-overview/service-mesh/smsh-03-istio-sidecars-in-kyma/). Then, check how to [enable automatic Istio sidecar proxy injection](https://kyma-project.io/docs/kyma/latest/04-operation-guides/operations/smsh-01-istio-enable-sidecar-injection/). For more details, see [Default Istio setup in Kyma](https://kyma-project.io/docs/kyma/latest/01-overview/service-mesh/smsh-02-default-istio-setup-in-kyma/). + +This tutorial is based on a sample Python Function run in a lightweight [k3d](https://k3d.io/) cluster. + +## Prerequisites + +Before you start, make sure you have these tools installed: + +- [Docker](https://www.docker.com/) +- [Kyma CLI](https://github.com/kyma-project/cli) +- [Serverless module installed](https://kyma-project.io/docs/kyma/latest/04-operation-guides/operations/08-install-uninstall-upgrade-kyma-module/) locally or in a cluster + +## Steps + +Follow these steps: + +1. To create local files with the default configuration for a Python Function, go to the folder in which you want to initiate the workspace content and run the `init` Kyma CLI command: + + ```bash + kyma init function --runtime python312 --name {FUNCTION_NAME} + ``` + + You can also use the `--dir {FULL_FOLDER_PATH}` flag to point to the directory where you want to create the Function's source files. + + > [!NOTE] + > Python 3.9 is only one of the available runtimes. Read about all [supported runtimes and sample Functions to run on them](../technical-reference/07-10-sample-functions.md). + + The `init` command creates these files in your workspace folder: + + - `config.yaml` with the Function's configuration + + > [!NOTE] + > See the detailed description of all fields available in the [`config.yaml` file](../technical-reference/07-60-function-configuration-file.md). + + - `handler.py` with the Function's code and the simple "Hello World" logic + - `requirements.txt` with an empty file for your Function's custom dependencies + + The `kyma init` command also sets **sourcePath** in the `config.yaml` file to the full path of the workspace folder: + + ```yaml + name: my-function + namespace: default + runtime: python312 + source: + sourceType: inline + sourcePath: {FULL_PATH_TO_WORKSPACE_FOLDER} + ``` + +1. Run the `apply` Kyma CLI command to create a Function CR in the YAML format on your cluster: + + ```bash + kyma apply function + ``` + + > [!TIP] + > To apply a Function from a different location, use the `--filename` flag followed by the full path to the `config.yaml` file. + + Alternatively, use the `--dry-run` flag to list the file that will be created before you apply it. You can also preview the file's content in the format of your choice by adding the `--output {FILE_FORMAT}` flag, such as `--output yaml`. + +3. Once applied, view the Function's details in the cluster: + + ```bash + kubectl describe function {FUNCTION_NAME} + ``` + +4. Change the Function's source code in the cluster to return "Hello Serverless!": + + a) Edit the Function: + + ```bash + kubectl edit function {FUNCTION_NAME} + ``` + + b) Modify **source** as follows: + + ```yaml + ... + spec: + runtime: python312 + source: |- + def main(event, context): + return "Hello Serverless!" + ``` + +5. Fetch the content of the resource to synchronize your local workspace sources with the cluster changes: + + ```bash + kyma sync function {FUNCTION_NAME} + ``` + +6. Check the local `handler.py` file with the Function's code to make sure that the cluster changes were fetched: + + ```bash + cat handler.py + ``` + + This command returns the result confirming that the local sources were synchronized with cluster changes: + + ```python + def main(event, context): + return "Hello Serverless!" + ``` diff --git a/docs/user/tutorials/01-40-debug-function.md b/docs/user/tutorials/01-40-debug-function.md new file mode 100644 index 00000000..99067a9a --- /dev/null +++ b/docs/user/tutorials/01-40-debug-function.md @@ -0,0 +1,83 @@ +# Debug a Function + +This tutorial shows how to use an external IDE to debug a Function in Kyma CLI. + +## Steps + +Learn how to debug a Function with Visual Studio Code for Node.js or Python, or GoLand: + + + +#### **Visual Studio Code** + +1. In VSC, navigate to the location of the file with the Function definition. +2. Create the `.vscode` directory. +3. In the `.vscode` directory, create the `launch.json` file with the following content: + + For Node.js: + + ```json + { + "version": "0.2.0", + "configurations": [ + { + "name": "attach", + "type": "node", + "request": "attach", + "port": 9229, + "address": "localhost", + "localRoot": "${workspaceFolder}/kubeless", + "remoteRoot": "/kubeless", + "restart": true, + "protocol": "inspector", + "timeout": 1000 + } + ] + } + ``` + + For Python: + + ```json + { + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Kyma function", + "type": "python", + "request": "attach", + "pathMappings": [ + { + "localRoot": "${workspaceFolder}", + "remoteRoot": "/kubeless" + } + ], + "connect": { + "host": "localhost", + "port": 5678 + } + } + ] + } + ``` + +4. Run the Function with the `--debug` flag. + + ```bash + kyma run function --debug + ``` + +#### **GoLand** + +1. In GoLand, navigate to the location of the file with the Function definition. +2. Choose the **Add Configuration...** option. +3. Add new **Attach to Node.js/Chrome** configuration with these options: + - Host: `localhost` + - Port: `9229` +4. Run the Function with the `--debug` flag. + + ```bash + kyma run function --debug + ``` + + \ No newline at end of file diff --git a/docs/user/tutorials/01-50-sync-function-with-gitops.md b/docs/user/tutorials/01-50-sync-function-with-gitops.md new file mode 100644 index 00000000..1a21e66b --- /dev/null +++ b/docs/user/tutorials/01-50-sync-function-with-gitops.md @@ -0,0 +1,222 @@ +# Synchronize Git Resources with the Cluster Using a Gitops Operator + +This tutorial shows how you can automate the deployment of local Kyma resources in a cluster using the GitOps logic. You will use [Kyma CLI](https://github.com/kyma-project/cli) to create an inline Python Function. You will later push the resource to a GitHub repository of your choice and set up a GitOps operator to monitor the given repository folder and synchronize any changes in it with your cluster. For the purpose of this tutorial, you will install and use the [Flux](https://fluxcd.io/flux/get-started/) GitOps operator and a lightweight [k3d](https://k3d.io/) cluster. + +> [!TIP] +> Although this tutorial uses Flux to synchronize Git resources with the cluster, you can use an alternative GitOps operator for this purpose, such as [Argo](https://argoproj.github.io/argo-cd/). + +## Prerequisites + +All you need before you start is to have the following: + +- [Docker](https://www.docker.com/) +- Git repository +- [Homebrew](https://docs.brew.sh/Installation) +- Kyma CLI +- Kubeconfig file to your Kyma cluster + +## Steps + +These sections will lead you through the whole installation, configuration, and synchronization process. You will first install k3d and create a cluster for your custom resources (CRs). Then, you will need to apply the necessary CustomResourceDefinition (CRD) from Kyma to be able to create Functions. Finally, you will install Flux and authorize it with the `write` access to your GitHub repository in which you store the resource files. Flux will automatically synchronize any new changes pushed to your repository with your k3d cluster. + +### Install and Configure a k3d Cluster + +1. Install k3d using Homebrew on macOS: + + ```bash + brew install k3d + ``` + +2. Create a default k3d cluster with a single server node: + + ```bash + k3d cluster create {CLUSTER_NAME} + ``` + + This command also sets your context to the newly created cluster. Run this command to display the cluster information: + + ```bash + kubectl cluster-info + ``` + +3. Apply the `functions.serverless.kyma-project.io` CRD from sources in the [`serverless`](https://github.com/kyma-project/serverless/tree/main/components/serverless/config/crd) repository. You will need it to create the Function CR in the cluster. + + ```bash + kubectl apply -f https://raw.githubusercontent.com/kyma-project/serverless/main/components/serverless/config/crd/bases/serverless.kyma-project.io_functions.yaml + ``` + +4. Run this command to make sure the CRs are applied: + + ```bash + kubectl get customresourcedefinitions + ``` + +### Prepare Your Local Workspace + +1. Create a workspace folder in which you will create source files for your Function: + + ```bash + mkdir {WORKSPACE_FOLDER} + ``` + +2. Use the `init` Kyma CLI command to create a local workspace with default configuration for a Python Function: + + ```bash + kyma init function --runtime python312 --dir $PWD/{WORKSPACE_FOLDER} + ``` + + > [!TIP] + > Python 3.9 is only one of the available runtimes. Read about all [supported runtimes and sample Functions to run on them](../technical-reference/07-10-sample-functions.md). + + This command will download the following files to your workspace folder: + + - `config.yaml` with the Function's configuration + - `handler.py` with the Function's code and the simple "Hello World" logic + - `requirements.txt` with an empty file for your Function's custom dependencies + +### Install and Configure Flux + +You can now install the Flux operator, connect it with a specific Git repository folder, and authorize Flux to automatically pull changes from this repository folder and apply them on your cluster. + +1. Install Flux: + + ```bash + brew install fluxctl + ``` + +2. Create a `flux` namespace for the Flux operator's CRDs: + + ```bash + kubectl create namespace flux + kubectl label namespace flux istio-injection=enabled --overwrite + ``` + +3. Export details of your GitHub repository - its name, the account name, and related e-mail address. You must also specify the name of the folder in your GitHub repository to which you will push the Function CR built from local sources. If you don't have this folder in your repository yet, you will create it in further steps. Flux will synchronize the cluster with the content of this folder on the `main` branch. + + ```bash + export GH_USER="{USERNAME}" + export GH_REPO="{REPOSITORY_NAME}" + export GH_EMAIL="{EMAIL_OF_YOUR_GH_ACCOUNT}" + export GH_FOLDER="{GIT_REPO_FOLDER_FOR_FUNCTION_RESOURCES}" + ``` + +4. Run this command to apply CRDs of the Flux operator to the `flux` namespace on your cluster: + + ```bash + fluxctl install \ + --git-user=${GH_USER} \ + --git-email=${GH_EMAIL} \ + --git-url=git@github.com:${GH_USER}/${GH_REPO}.git \ + --git-path=${GH_FOLDER} \ + --namespace=flux | kubectl apply -f - + ``` + + You will see that Flux created these CRDs: + + ```bash + serviceaccount/flux created + clusterrole.rbac.authorization.k8s.io/flux created + clusterrolebinding.rbac.authorization.k8s.io/flux created + deployment.apps/flux created + secret/flux-git-deploy created + deployment.apps/memcached created + service/memcached created + ``` + +5. List all Pods in the `flux` namespace to make sure that the one for Flux is in the `Running` state: + + ```bash + kubectl get pods --namespace flux + ``` + + Expect a response similar to this one: + + ```bash + NAME READY STATUS RESTARTS AGE + flux-75758595b9-m4885 1/1 Running 0 32m + ``` + +6. Obtain the certificate (SSH key) that Flux generated: + + ```bash + fluxctl identity --k8s-fwd-ns flux + ``` + +7. Run this command to copy the SSH key to the clipboard: + + ```bash + fluxctl identity --k8s-fwd-ns flux | pbcopy + ``` + +8. Go to **Settings** in your GitHub account: + + ![GitHub account settings](../../assets/svls-settings.png) + +9. Go to the **SSH and GPG keys** section and select the **New SSH key** button: + + ![Create a new SSH key](../../assets/svls-create-ssh-key.png) + +10. Provide the new key name, paste the previously copied SSH key, and confirm changes by selecting the **Add SSH Key** button: + + ![Add a new SSH key](../../assets/svls-add-ssh-key.png) + +### Create a Function + +Now that Flux is authenticated to pull changes from your Git repository, you can start creating CRs from your local workspace files. + +In this section, you will create a sample inline Function. + +1. Back in the terminal, clone this GitHub repository to your current workspace location: + + ```bash + git clone https://github.com/${GH_USER}/${GH_REPO}.git + ``` + + > [!NOTE] + > You can also clone the repository using SSH. To do that, you need to [generate a new SSH key and add it to the ssh-agent](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent). + +2. Go to the repository folder: + + ```bash + cd ${GH_REPO} + ``` + +3. If the folder you specified during the Flux configuration does not exist yet in the Git repository, create it: + + ```bash + mkdir ${GH_FOLDER} + ``` + +4. Run the `apply` Kyma CLI command to create a Function CR in the YAML format in your remote GitHub repository. This command will generate the output in the `my-function.yaml` file. + + ```bash + kyma apply function --filename {FULL_PATH_TO_LOCAL_WORKSPACE_FOLDER}/config.yaml --output yaml --dry-run > ./${GH_FOLDER}/my-function.yaml + ``` + +5. Push the local changes to the remote repository: + + ```bash + git add . # Stage changes for the commit + git commit -m 'Add my-function' # Add a commit message + git push origin main # Push changes to the "main" branch of your Git repository. If you have a repository with the "main" branch, use this command instead: git push origin main + ``` + +6. Go to the GitHub repository to check that the changes were pushed. + +7. By default, Flux pulls CRs from the Git repository and pushes them to the cluster in 5-minute intervals. To enforce immediate synchronization, run this command from the terminal: + + ```bash + fluxctl sync --k8s-fwd-ns flux + ``` + +8. Make sure that the Function CR was applied by Flux to the cluster: + + ```bash + kubectl get functions + ``` + +You can see that Flux synchronized the resource and the new Function CR was added to your cluster. + +## Reverting Feature + +Once you set it up, Flux will keep monitoring the given Git repository folder for any changes. If you modify the existing resources directly in the cluster, Flux will automatically revert these changes and update the given resource back to its version on the `main` branch of the Git repository. diff --git a/docs/user/tutorials/01-60-set-external-registry.md b/docs/user/tutorials/01-60-set-external-registry.md new file mode 100644 index 00000000..e257e686 --- /dev/null +++ b/docs/user/tutorials/01-60-set-external-registry.md @@ -0,0 +1,244 @@ +# Set an External Docker Registry + +By default, you install Kyma with Serverless that uses the internal Docker registry running in a cluster. This tutorial shows how to override this default setup with an external Docker registry from one of these cloud providers: + +- [Docker Hub](https://hub.docker.com/) +- [Google Artifact Registry (GAR)](https://cloud.google.com/artifact-registry) +- [Azure Container Registry (ACR)](https://azure.microsoft.com/en-us/services/container-registry/) + +> [!WARNING] +> Function images are not cached in the Docker Hub. The reason is that this registry is not compatible with the caching logic defined in [Kaniko](https://cloud.google.com/cloud-build/docs/kaniko-cache) that Serverless uses for building images. + +## Prerequisites + + + +#### **Docker Hub** + +- [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) + +#### **GAR** + +- [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) +- [gcloud](https://cloud.google.com/sdk/gcloud/) +- [Google Cloud Platform (GCP)](https://cloud.google.com) project + +#### **ACR** + +- [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) +- [Azure CLI](https://docs.microsoft.com/en-us/cli/azure) +- [Microsoft Azure](http://azure.com) subscription + + + +## Steps + +### Create Required Cloud Resources + + + +#### **Docker Hub** + +1. Run the `export {VARIABLE}={value}` command to set up these environment variables, where: + + - **USER_NAME** is the name of the account in the Docker Hub. + - **PASSWORD** is the password for the account in the Docker Hub. + - **SERVER_ADDRESS** is the server address of the Docker Hub. At the moment, Kyma only supports the `https://index.docker.io/v1/` server address. + - **REGISTRY_ADDRESS** is the registry address in the Docker Hub. + + > [!TIP] + > Usually, the Docker registry address is the same as the account name. + + Example: + + ```bash + export USER_NAME=kyma-rocks + export PASSWORD=admin123 + export SERVER_ADDRESS=https://index.docker.io/v1/ + export REGISTRY_ADDRESS=kyma-rocks + ``` + +#### **GAR** + +To use GAR, create a Google service account that has a private key and the **Storage Admin** role permissions. Follow these steps: + +1. Run the `export {VARIABLE}={value}` command to set up these environment variables, where: + + - **SA_NAME** is the name of the service account. + - **SA_DISPLAY_NAME** is the display name of the service account. + - **PROJECT** is the GCP project ID. + - **SECRET_FILE** is the path to the private key. + - **ROLE** is the **Storage Admin** role bound to the service account. + - **SERVER_ADDRESS** is the server address of the Docker registry. + + Example: + + ```bash + export SA_NAME=my-service-account + export SA_DISPLAY_NAME=service-account + export PROJECT=test-project-012345 + export SECRET_FILE=my-private-key-path + export ROLE=roles/storage.admin + export SERVER_ADDRESS=gar.io + ``` + +2. When you communicate with Google Cloud for the first time, set the context for your Google Cloud project. Run this command: + + ```bash + gcloud config set project ${PROJECT} + ``` + +3. Create a service account. Run: + + ```bash + gcloud iam service-accounts create ${SA_NAME} --display-name ${SA_DISPLAY_NAME} + ``` + +4. Add a policy binding for the **Storage Admin** role to the service account. Run: + + ```bash + gcloud projects add-iam-policy-binding ${PROJECT} --member=serviceAccount:${SA_NAME}@${PROJECT}.iam.gserviceaccount.com --role=${ROLE} + ``` + +5. Create a private key for the service account: + + ```bash + gcloud iam service-accounts keys create ${SECRET_FILE} --iam-account=${SA_NAME}@${PROJECT}.iam.gserviceaccount.com + ``` + +6. Export the private key as an environment variable: + + ```bash + export GCS_KEY_JSON=$(< "$SECRET_FILE" base64 | tr -d '\n') + ``` + +#### **ACR** + +Create an ACR and a service principal. Follow these steps: + +1. Run the `export {VARIABLE}={value}` command to set up these environment variables, where: + + - **AZ_REGISTRY_NAME** is the name of the ACR. + - **AZ_RESOURCE_GROUP** is the name of the resource group. + - **AZ_RESOURCE_GROUP_LOCATION** is the location of the resource group. + - **AZ_SUBSCRIPTION_ID** is the ID of the Azure subscription. + - **AZ_SERVICE_PRINCIPAL_NAME** is the name of the Azure service principal. + - **ROLE** is the **acrpush** role bound to the service principal. + - **SERVER_ADDRESS** is the server address of the Docker registry. + + Example: + + ```bash + export AZ_REGISTRY_NAME=registry + export AZ_RESOURCE_GROUP=my-resource-group + export AZ_RESOURCE_GROUP_LOCATION=westeurope + export AZ_SUBSCRIPTION_ID=123456-123456-123456-1234567 + export AZ_SERVICE_PRINCIPAL_NAME=acr-service-principal + export ROLE=acrpush + export SERVER_ADDRESS=azurecr.io + ``` + +2. When you communicate with Microsoft Azure for the first time, log into your Azure account. Run this command: + + ```bash + az login + ``` + +3. Create a resource group. Run: + + ```bash + az group create --name ${AZ_RESOURCE_GROUP} --location ${AZ_RESOURCE_GROUP_LOCATION} --subscription ${AZ_SUBSCRIPTION_ID} + ``` + +4. Create an ACR. Run: + + ```bash + az acr create --name ${AZ_REGISTRY_NAME} --resource-group ${AZ_RESOURCE_GROUP} --subscription ${AZ_SUBSCRIPTION_ID} --sku {Basic, Classic, Premium, Standard} + ``` + +5. Obtain the full ACR ID. Run: + + ```bash + export AZ_REGISTRY_ID=$(az acr show --name ${AZ_REGISTRY_NAME} --query id --output tsv) + ``` + +6. Create a service principal with rights scoped to the ACR. Run: + + ```bash + export SP_PASSWORD=$(az ad sp create-for-rbac --name http://${AZ_SERVICE_PRINCIPAL_NAME} --scopes ${AZ_REGISTRY_ID} --role ${ROLE} --query password --output tsv) + export SP_APP_ID=$(az ad sp show --id http://${AZ_SERVICE_PRINCIPAL_NAME} --query appId --output tsv) + ``` + + Alternatively, assign the desired role to the existing service principal. Run: + + ```bash + export SP_APP_ID=$(az ad sp show --id http://${AZ_SERVICE_PRINCIPAL_NAME} --query appId --output tsv) + export SP_PASSWORD=$(az ad sp show --id http://${AZ_SERVICE_PRINCIPAL_NAME} --query password --output tsv) + az role assignment create --assignee ${SP_APP_ID} --scope ${AZ_REGISTRY_ID} --role ${ROLE} + ``` + + + +### Override Serverless Configuration + +Prepare yaml file with overrides that match your Docker registry provider: + + + +#### **Docker Hub** + +```bash +cat > docker-registry-overrides.yaml < docker-registry-overrides.yaml < docker-registry-overrides.yaml < + +> [!WARNING] +> If you want to set an external Docker registry before you install Kyma, manually apply the Secret to the cluster before you run the installation script. + +### Apply Configuration + +Deploy Kyma with different configuration for Docker registry . Run: + +```bash +kyma deploy --values-file docker-registry-overrides.yaml +``` + +> [!NOTE] +> To learn more, read about [changing Kyma configuration](https://kyma-project.io/docs/kyma/latest/04-operation-guides/operations/03-change-kyma-config-values). diff --git a/docs/user/tutorials/01-80-log-into-private-packages-registry.md b/docs/user/tutorials/01-80-log-into-private-packages-registry.md new file mode 100644 index 00000000..6e3b7c84 --- /dev/null +++ b/docs/user/tutorials/01-80-log-into-private-packages-registry.md @@ -0,0 +1,118 @@ +# Log Into a Private Package Registry Using Credentials from a Secret + +Serverless allows you to consume private packages in your Functions. This tutorial shows how you can log into a private package registry by defining credentials in a Secret custom resource (CR). + +## Steps + +### Create a Secret + +Create a Secret CR for your Node.js or Python Functions. You can also create one combined Secret CR for both runtimes. + + + +#### **Node.js** + +1. Export these variables: + + ```bash + export REGISTRY={ADDRESS_TO_REGISTRY} + export TOKEN={TOKEN_TO_REGISTRY} + export NAMESPACE={FUNCTION_NAMESPACE} + ``` + +2. Create a Secret: + + ```bash + cat < + +### Test the Package Registry Switch + +[Create a Function](01-10-create-inline-function.md) with dependencies from the external registry. Check if your Function was created and all conditions are set to `True`: + +```bash +kubectl get functions -n $NAMESPACE +``` + +You should get a result similar to the this example: + +```bash +NAME CONFIGURED BUILT RUNNING RUNTIME VERSION AGE +test-function True True True nodejs20 1 96s +``` + +> [!WARNING] +> If you want to create a cluster-wide Secret, you must create it in the `kyma-system` namespace and add the `serverless.kyma-project.io/config: credentials` label. diff --git a/docs/user/tutorials/01-90-set-asynchronous-connection.md b/docs/user/tutorials/01-90-set-asynchronous-connection.md new file mode 100644 index 00000000..840d2712 --- /dev/null +++ b/docs/user/tutorials/01-90-set-asynchronous-connection.md @@ -0,0 +1,146 @@ +# Set Asynchronous Communication Between Functions + +This tutorial demonstrates how to connect two Functions asynchronously. It is based on the [in-cluster Eventing example](https://github.com/kyma-project/serverless/tree/main/examples/incluster_eventing). + +The example provides a very simple scenario of asynchronous communication between two Functions. The first Function accepts the incoming traffic via HTTP, sanitizes the payload, and publishes the content as an in-cluster event using [Kyma Eventing](https://kyma-project.io/docs/kyma/latest/01-overview/eventing/). +The second Function is a message receiver. It subscribes to the given event type and stores the payload. + +This tutorial shows only one possible use case. There are many more use cases on how to orchestrate your application logic into specialized Functions and benefit from decoupled, re-usable components and event-driven architecture. + +## Prerequisites + +- [Kyma CLI](https://github.com/kyma-project/cli) +- [Eventing and Istio components installed](https://kyma-project.io/docs/kyma/latest/04-operation-guides/operations/02-install-kyma/#install-specific-components) + +## Steps + +1. Export the `KUBECONFIG` variable: + + ```bash + export KUBECONFIG={KUBECONFIG_PATH} + ``` + +2. Create the `emitter` and `receiver` folders in your project. + +### Create the Emitter Function + +1. Go to the `emitter` folder and run Kyma CLI `init` command to initialize the scaffold for your first Function: + + ```bash + kyma init function + ``` + + The `init` command creates these files in your workspace folder: + + - `config.yaml` with the Function's configuration + + > [!NOTE] + > See the detailed description of all fields available in the [`config.yaml` file](../technical-reference/07-60-function-configuration-file.md). + + - `handler.js` with the Function's code and the simple "Hello Serverless" logic + + - `package.json` with the Function's dependencies + +2. In the `config.yaml` file, configure an APIRule to expose your Function to the incoming traffic over HTTP. Provide the subdomain name in the `host` property: + + ```yaml + apiRules: + - name: incoming-http-trigger + service: + host: incoming + rules: + - methods: + - GET + accessStrategies: + - handler: allow + ``` + +3. Provide your Function logic in the `handler.js` file: + + > [!NOTE] + > In this example, there's no sanitization logic. The `sanitize` Function is just a placeholder. + + ```js + module.exports = { + main: async function (event, context) { + let sanitisedData = sanitise(event.data) + + const eventType = "sap.kyma.custom.acme.payload.sanitised.v1"; + const eventSource = "kyma"; + + return await event.emitCloudEvent(eventType, eventSource, sanitisedData) + .then(resp => { + return "Event sent"; + }).catch(err=> { + console.error(err) + return err; + }); + } + } + let sanitise = (data)=>{ + console.log(`sanitising data...`) + console.log(data) + return data + } + ``` + + The `sap.kyma.custom.acme.payload.sanitised.v1` is a sample event type that the emitter Function declares when publishing events. You can choose a different one that better suits your use case. Keep in mind the constraints described on the [Event names](https://kyma-project.io/docs/kyma/latest/05-technical-reference/evnt-01-event-names/) page. The receiver subscribes to the event type to consume the events. + + The event object provides convenience functions to build and publish events. To send the event, build the Cloud Event. To learn more, read [Function's specification](../technical-reference/07-70-function-specification.md#event-object-sdk). In addition, your **eventOut.source** key must point to `“kyma”` to use Kyma in-cluster Eventing. + There is a `require('axios')` line even though the Function code is not using it directly. This is needed for the auto-instrumentation to properly handle the outgoing requests sent using the `publishCloudEvent` method (which uses `axios` library under the hood). Without the `axios` import the Function still works, but the published events are not reflected in the trace backend. + +4. Apply your emitter Function: + + ```bash + kyma apply function + ``` + + Your Function is now built and deployed in Kyma runtime. Kyma exposes it through the APIRule. The incoming payloads are processed by your emitter Function. It then sends the sanitized content to the workload that subscribes to the selected event type. In our case, it's the receiver Function. + +5. Test the first Function. Send the payload and see if your HTTP traffic is accepted: + + ```bash + export KYMA_DOMAIN={KYMA_DOMAIN_VARIABLE} + + curl -X POST https://incoming.${KYMA_DOMAIN} -H 'Content-Type: application/json' -d '{"foo":"bar"}' + ``` + +### Create the Receiver Function + +1. Go to your `receiver` folder and run Kyma CLI `init` command to initialize the scaffold for your second Function: + + ```bash + kyma init function + ``` + + The `init` command creates the same files as in the `emitter` folder. + +2. In the `config.yaml` file, configure event types your Function will subscribe to: + + ```yaml + name: event-receiver + namespace: default + runtime: nodejs20 + source: + sourceType: inline + subscriptions: + - name: event-receiver + typeMatching: exact + source: "" + types: + - sap.kyma.custom.acme.payload.sanitised.v1 + schemaVersion: v1 + ``` + +3. Apply your receiver Function: + + ```bash + kyma apply function + ``` + + The Function is configured, built, and deployed in Kyma runtime. The Subscription becomes active and all events with the selected type are processed by the Function. + +### Test the Whole Setup + +Send a payload to the first Function. For example, use the POST request mentioned above. As the Functions are joined by the in-cluster Eventing, the payload is processed in sequence by both of your Functions. +In the Function's logs, you can see that both sanitization logic (using the first Function) and the storing logic (using the second Function) are executed. diff --git a/docs/user/tutorials/README.md b/docs/user/tutorials/README.md new file mode 100644 index 00000000..9edc9149 --- /dev/null +++ b/docs/user/tutorials/README.md @@ -0,0 +1,3 @@ +# Tutorials + +This section will help you understand how the Serverless Function works and how to use it in different scenarios. You can also learn how to set and switch a Docker registry. diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..5d6848ec --- /dev/null +++ b/go.mod @@ -0,0 +1,164 @@ +module github.com/kyma-project/docker-registry + +go 1.21 + +toolchain go1.21.3 + +require ( + github.com/onsi/ginkgo/v2 v2.17.1 + github.com/onsi/gomega v1.32.0 + github.com/pkg/errors v0.9.1 + github.com/stretchr/testify v1.9.0 + github.com/vrischmann/envconfig v1.3.0 + go.uber.org/zap v1.27.0 + gopkg.in/yaml.v3 v3.0.1 + helm.sh/helm/v3 v3.14.3 + k8s.io/api v0.29.4 + k8s.io/apiextensions-apiserver v0.29.4 + k8s.io/apimachinery v0.29.4 + k8s.io/client-go v0.29.4 + k8s.io/utils v0.0.0-20230726121419-3b25d923346b + sigs.k8s.io/controller-runtime v0.17.3 +) + +require ( + github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/BurntSushi/toml v1.3.2 // indirect + github.com/MakeNowJust/heredoc v1.0.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.2.1 // indirect + github.com/Masterminds/sprig/v3 v3.2.3 // indirect + github.com/Masterminds/squirrel v1.5.4 // indirect + github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/chai2010/gettext-go v1.0.2 // indirect + github.com/containerd/containerd v1.7.12 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/docker/cli v24.0.6+incompatible // indirect + github.com/docker/distribution v2.8.2+incompatible // indirect + github.com/docker/docker v24.0.7+incompatible // indirect + github.com/docker/docker-credential-helpers v0.7.0 // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-metrics v0.0.1 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/evanphx/json-patch v5.7.0+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.8.0 // indirect + github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect + github.com/fatih/color v1.13.0 // indirect + github.com/felixge/httpsnoop v1.0.3 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-errors/errors v1.4.2 // indirect + github.com/go-gorp/gorp/v3 v3.1.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/btree v1.0.1 // indirect + github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/mux v1.8.0 // indirect + github.com/gorilla/websocket v1.5.0 // indirect + github.com/gosuri/uitable v0.0.4 // indirect + github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/huandu/xstrings v1.4.0 // indirect + github.com/imdario/mergo v0.3.13 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jmoiron/sqlx v1.3.5 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.16.0 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/lib/pq v1.10.9 // indirect + github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/moby/locker v1.0.1 // indirect + github.com/moby/spdystream v0.2.0 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0-rc5 // indirect + github.com/peterbourgon/diskv v2.0.1+incompatible // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.19.0 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.48.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/rubenv/sql-migrate v1.5.2 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/shopspring/decimal v1.3.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/cobra v1.8.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + github.com/xlab/treeprint v1.2.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect + go.opentelemetry.io/otel v1.19.0 // indirect + go.opentelemetry.io/otel/metric v1.19.0 // indirect + go.opentelemetry.io/otel/trace v1.19.0 // indirect + go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.22.0 // indirect + golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect + golang.org/x/net v0.24.0 // indirect + golang.org/x/oauth2 v0.16.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.19.0 // indirect + golang.org/x/term v0.19.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect + golang.org/x/tools v0.17.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/grpc v1.58.3 // indirect + google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + k8s.io/apiserver v0.29.4 // indirect + k8s.io/cli-runtime v0.29.4 // indirect + k8s.io/component-base v0.29.4 // indirect + k8s.io/klog/v2 v2.110.1 // indirect + k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect + k8s.io/kubectl v0.29.0 // indirect + oras.land/oras-go v1.2.4 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect + sigs.k8s.io/kustomize/kyaml v0.16.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..a6ad8721 --- /dev/null +++ b/go.sum @@ -0,0 +1,602 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= +github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= +github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= +github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= +github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= +github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= +github.com/containerd/containerd v1.7.12 h1:+KQsnv4VnzyxWcfO9mlxxELaoztsDEjOuCMPAuPqgU0= +github.com/containerd/containerd v1.7.12/go.mod h1:/5OMpE1p0ylxtEUGY8kuCYkDRzJm9NO1TFMWjUpdevk= +github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= +github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= +github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI= +github.com/docker/cli v24.0.6+incompatible h1:fF+XCQCgJjjQNIMjzaSmiKJSCcfcXb3TWTcc7GAneOY= +github.com/docker/cli v24.0.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= +github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= +github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= +github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= +github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.8.0 h1:lRj6N9Nci7MvzrXuX6HFzU8XjmhPiXPlsKEy1u0KQro= +github.com/evanphx/json-patch/v5 v5.8.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= +github.com/foxcpp/go-mockdns v1.0.0/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= +github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= +github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= +github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= +github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY= +github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XEWlY= +github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= +github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= +github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= +github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= +github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= +github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= +github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= +github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= +github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= +github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY= +github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= +github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= +github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.1.25 h1:dFwPR6SfLtrSwgDcIq2bcU/gVutB4sNApq2HBdqcakg= +github.com/miekg/dns v1.1.25/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= +github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8= +github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= +github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk= +github.com/onsi/gomega v1.32.0/go.mod h1:a4x4gW6Pz2yK1MAmvluYme5lvYTn61afQ2ETw/8n4Lg= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= +github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= +github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= +github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= +github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0= +github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/vrischmann/envconfig v1.3.0 h1:4XIvQTXznxmWMnjouj0ST5lFo/WAYf5Exgl3x82crEk= +github.com/vrischmann/envconfig v1.3.0/go.mod h1:bbvxFYJdRSpXrhS63mBFtKJzkDiNkyArOLXtY6q0kuI= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q= +go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= +go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= +golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= +golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= +google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= +gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= +helm.sh/helm/v3 v3.14.3 h1:HmvRJlwyyt9HjgmAuxHbHv3PhMz9ir/XNWHyXfmnOP4= +helm.sh/helm/v3 v3.14.3/go.mod h1:v6myVbyseSBJTzhmeE39UcPLNv6cQK6qss3dvgAySaE= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/api v0.29.4 h1:WEnF/XdxuCxdG3ayHNRR8yH3cI1B/llkWBma6bq4R3w= +k8s.io/api v0.29.4/go.mod h1:DetSv0t4FBTcEpfA84NJV3g9a7+rSzlUHk5ADAYHUv0= +k8s.io/apiextensions-apiserver v0.29.4 h1:M7hbuHU/ckbibR7yPbe6DyNWgTFKNmZDbdZKD8q1Smk= +k8s.io/apiextensions-apiserver v0.29.4/go.mod h1:TTDC9fB+0kHY2rogf5hgBR03KBKCwED+GHUsXGpR7SM= +k8s.io/apimachinery v0.29.4 h1:RaFdJiDmuKs/8cm1M6Dh1Kvyh59YQFDcFuFTSmXes6Q= +k8s.io/apimachinery v0.29.4/go.mod h1:i3FJVwhvSp/6n8Fl4K97PJEP8C+MM+aoDq4+ZJBf70Y= +k8s.io/apiserver v0.29.4 h1:wPwGOO58GQOpRiZu59P5eRoDcB7QtV+QBglkRiXwCiM= +k8s.io/apiserver v0.29.4/go.mod h1:VqTF9t98HVfhKZVRohCPezsdUt9u2g3bHKftxGcXoRo= +k8s.io/cli-runtime v0.29.4 h1:QvUrddBxVX6XFJ6z64cGpEk7e4bQduKweqbqq+qBd9g= +k8s.io/cli-runtime v0.29.4/go.mod h1:NmklYuZ4DLfOr2XEIT8Nzl883KMZUCv7KMj3wMHayCA= +k8s.io/client-go v0.29.4 h1:79ytIedxVfyXV8rpH3jCBW0u+un0fxHDwX5F9K8dPR8= +k8s.io/client-go v0.29.4/go.mod h1:kC1thZQ4zQWYwldsfI088BbK6RkxK+aF5ebV8y9Q4tk= +k8s.io/component-base v0.29.4 h1:xeKzuuHI/1tjleu5jycDAcYbhAxeGHCQBZUY2eRIkOo= +k8s.io/component-base v0.29.4/go.mod h1:pYjt+oEZP9gtmwSikwAJgfSBikqKX2gOqRat0QjmQt0= +k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= +k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/kubectl v0.29.0 h1:Oqi48gXjikDhrBF67AYuZRTcJV4lg2l42GmvsP7FmYI= +k8s.io/kubectl v0.29.0/go.mod h1:0jMjGWIcMIQzmUaMgAzhSELv5WtHo2a8pq67DtviAJs= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +oras.land/oras-go v1.2.4 h1:djpBY2/2Cs1PV87GSJlxv4voajVOMZxqqtq9AB8YNvY= +oras.land/oras-go v1.2.4/go.mod h1:DYcGfb3YF1nKjcezfX2SNlDAeQFKSXmf+qrFmrh4324= +sigs.k8s.io/controller-runtime v0.17.3 h1:65QmN7r3FWgTxDMz9fvGnO1kbf2nu+acg9p2R9oYYYk= +sigs.k8s.io/controller-runtime v0.17.3/go.mod h1:N0jpP5Lo7lMTF9aL56Z/B2oWBJjey6StQM0jRbKQXtY= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKUdc5jW3t5jwY7Bo7dcRm+tFxT+NfgY0= +sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3/go.mod h1:9n16EZKMhXBNSiUC5kSdFQJkdH3zbxS/JoO619G1VAY= +sigs.k8s.io/kustomize/kyaml v0.16.0 h1:6J33uKSoATlKZH16unr2XOhDI+otoe2sR3M8PDzW3K0= +sigs.k8s.io/kustomize/kyaml v0.16.0/go.mod h1:xOK/7i+vmE14N2FdFyugIshB8eF6ALpy7jI87Q2nRh4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/hack/Makefile b/hack/Makefile new file mode 100644 index 00000000..078eba60 --- /dev/null +++ b/hack/Makefile @@ -0,0 +1,7 @@ +# This Makefile is used to add context to the `gardener.mk` file by adding the PROJECT_ROOT variable +# this is needed to use targets in the gardener.mk file from a prompt (e.g. `make -C hack provision-gardener`) + +PROJECT_ROOT=.. + +include ${PROJECT_ROOT}/hack/help.mk +include ${PROJECT_ROOT}/hack/gardener.mk diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt new file mode 100755 index 00000000..29c55ecd --- /dev/null +++ b/hack/boilerplate.go.txt @@ -0,0 +1,15 @@ +/* +Copyright 2022. + +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. +*/ \ No newline at end of file diff --git a/hack/gardener.mk b/hack/gardener.mk new file mode 100644 index 00000000..54522978 --- /dev/null +++ b/hack/gardener.mk @@ -0,0 +1,27 @@ +ifndef PROJECT_ROOT +$(error PROJECT_ROOT is undefined) +endif +include ${PROJECT_ROOT}/hack/tools.mk + +##@ Gardener + +GARDENER_INFRASTRUCTURE = az +HIBERNATION_HOUR=$(shell echo $$(( ( $(shell date +%H | sed s/^0//g) + 5 ) % 24 ))) +GIT_COMMIT_SHA=$(shell git rev-parse --short=8 HEAD) +ifneq (,$(GARDENER_SA_PATH)) +GARDENER_K8S_VERSION=$(shell kubectl --kubeconfig=${GARDENER_SA_PATH} get cloudprofiles.core.gardener.cloud ${GARDENER_INFRASTRUCTURE} -o=jsonpath='{.spec.kubernetes.versions[0].version}') +else +GARDENER_K8S_VERSION=1.27.4 +endif +#Overwrite default kyma cli gardenlinux version because it's not supported. +GARDENER_LINUX_VERSION=1312.3.0 + +.PHONY: provision-gardener +provision-gardener: kyma ## Provision gardener cluster with latest k8s version + ${KYMA} provision gardener ${GARDENER_INFRASTRUCTURE} -c ${GARDENER_SA_PATH} -n test-${GIT_COMMIT_SHA} -p ${GARDENER_PROJECT} -s ${GARDENER_SECRET_NAME} -k ${GARDENER_K8S_VERSION}\ + --gardenlinux-version=$(GARDENER_LINUX_VERSION) --hibernation-start="00 ${HIBERNATION_HOUR} * * ?" + +.PHONY: deprovision-gardener +deprovision-gardener: kyma ## Deprovision gardener cluster + kubectl --kubeconfig=${GARDENER_SA_PATH} annotate shoot test-${GIT_COMMIT_SHA} confirmation.gardener.cloud/deletion=true + kubectl --kubeconfig=${GARDENER_SA_PATH} delete shoot test-${GIT_COMMIT_SHA} --wait=false diff --git a/hack/get_kyma_file_name.sh b/hack/get_kyma_file_name.sh new file mode 100755 index 00000000..f6d792c8 --- /dev/null +++ b/hack/get_kyma_file_name.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +function get_kyma_file_name () { + + local _OS_TYPE=$1 + local _OS_ARCH=$2 + + [ "$_OS_TYPE" == "Linux" ] && [ "$_OS_ARCH" == "x86_64" ] && echo "kyma-linux" || + [ "$_OS_TYPE" == "Linux" ] && [ "$_OS_ARCH" == "arm64" ] && echo "kyma-linux-arm" || + [ "$_OS_TYPE" == "Windows" ] && [ "$_OS_ARCH" == "x86_64" ] && echo "kyma.exe" || + [ "$_OS_TYPE" == "Windows" ] && [ "$_OS_ARCH" == "arm64" ] && echo "kyma-arm.exe" || + [ "$_OS_TYPE" == "Darwin" ] && [ "$_OS_ARCH" == "x86_64" ] && echo "kyma-darwin" || + [ "$_OS_TYPE" == "Darwin" ] && [ "$_OS_ARCH" == "arm64" ] && echo "kyma-darwin-arm" +} + +get_kyma_file_name "$@" diff --git a/hack/help.mk b/hack/help.mk new file mode 100644 index 00000000..a7a9d8f5 --- /dev/null +++ b/hack/help.mk @@ -0,0 +1,5 @@ +##@ General +.DEFAULT_GOAL=help +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) diff --git a/hack/k3d.mk b/hack/k3d.mk new file mode 100644 index 00000000..7eab3ad0 --- /dev/null +++ b/hack/k3d.mk @@ -0,0 +1,26 @@ +CLUSTER_NAME ?= kyma +REGISTRY_PORT ?= 5001 +REGISTRY_NAME ?= ${CLUSTER_NAME}-registry + +ifndef PROJECT_ROOT +$(error PROJECT_ROOT is undefined) +endif +include $(PROJECT_ROOT)/hack/tools.mk + +##@ K3D + +.PHONY: create-k3d +create-k3d: kyma ## Create k3d with kyma CRDs. + ${KYMA} provision k3d --registry-port ${REGISTRY_PORT} --name ${CLUSTER_NAME} --ci -p 6080:8080@loadbalancer -p 6433:8433@loadbalancer + kubectl create namespace kyma-system + +.PHONY: delete-k3d +delete-k3d: delete-k3d-cluster delete-k3d-registry ## Delete k3d registry & cluster. + +.PHONY: delete-k3d-registry +delete-k3d-registry: ## Delete k3d kyma registry. + -k3d registry delete ${REGISTRY_NAME} + +.PHONY: delete-k3d-cluster +delete-k3d-cluster: ## Delete k3d kyma cluster. + -k3d cluster delete ${CLUSTER_NAME} diff --git a/hack/makefile-strategy.md b/hack/makefile-strategy.md new file mode 100644 index 00000000..4560b4d0 --- /dev/null +++ b/hack/makefile-strategy.md @@ -0,0 +1,40 @@ +# Makefile architecture + +The goal is to develop a way to extend Makefile targets in the most readable way, without keeping all targets in one file. + +Pros of the architecture: + +* targets are well organized +* single responsibility +* extensibility + +## Dependencies description +* `Makefile` - The main makefile that allows for installing and running the Serverless Operator. It's a high-level target to run the module without knowing its internals. It's the first contact point for the Serverless module users. +* `hack/Makefile` - High-level API that contains all targets that may be used by any CI/CD system. It has dependencies on the `hack/*.mk` makefiles. +* `hack/*.mk` - Contains common targets that may be used by other makefiles (they are included and shouldn't be run directly). Targets are grouped by functionality. They should contain helpers' targets. +* `components/operator/Makefile` - Contains all basic operations on Serverless Operator like builds, tests, etc., used during development. It's also used by `Makefile`. +* `components/serverless/Makefile` - Contains all basic operations on Serverless like builds, tests, etc., used during development. + +## Good practices + +Every makefile (`Makefile` and `*.mk`) must contain a few pieces, making the file more useful and human-readable: + +* include `hack/help.mk` - this file provides the `help` target describing what is inside `Makefile` and what we can do with it. +* before `include` you must define the `PROJECT_ROOT` environment variable pointing to the project root directory. + +Additionally, `Makefile` (but not `*.mk`) can also contain the following: + +* Description - helps understand what the target does and shows it in the help. (`## description` after target name). +* Sections - allows for separations of targets based on their destination. (`##@`). + +Example of target that includes all good practices: + +```Makefile +PROJECT_ROOT=. +include ${PROJECT_ROOT}/hack/help.mk + +##@ General + +.PHONY: run +run: create-k3d install-serverless-main ## Create k3d cluster and install serverless from main +``` \ No newline at end of file diff --git a/hack/tools.mk b/hack/tools.mk new file mode 100644 index 00000000..751d843f --- /dev/null +++ b/hack/tools.mk @@ -0,0 +1,77 @@ +## Location to install dependencies to +ifndef PROJECT_ROOT +$(error PROJECT_ROOT is undefined) +endif +LOCALBIN ?= $(realpath $(PROJECT_ROOT))/bin +$(LOCALBIN): + mkdir -p $(LOCALBIN) + +# Operating system architecture +OS_ARCH=$(shell uname -m) +# Operating system type +OS_TYPE=$(shell uname) + +##@ Tools + +########## Kyma CLI ########### +KYMA_STABILITY ?= unstable + +define os_error +$(error Error: unsuported platform OS_TYPE:$1, OS_ARCH:$2; to mitigate this problem set variable KYMA with absolute path to kyma-cli binary compatible with your operating system and architecture) +endef + +KYMA ?= $(LOCALBIN)/kyma-$(KYMA_STABILITY) +kyma: $(LOCALBIN) $(KYMA) ## Download kyma locally if necessary. +$(KYMA): + $(eval KYMA_FILE_NAME=$(shell ${PROJECT_ROOT}/hack/get_kyma_file_name.sh ${OS_TYPE} ${OS_ARCH})) + ## Detect if operating system + $(if $(KYMA_FILE_NAME),,$(call os_error, ${OS_TYPE}, ${OS_ARCH})) + test -f $@ || curl -s -Lo $(KYMA) https://storage.googleapis.com/kyma-cli-$(KYMA_STABILITY)/$(KYMA_FILE_NAME) + chmod +x $(KYMA) + +########## Kustomize ########### +KUSTOMIZE ?= $(LOCALBIN)/kustomize +KUSTOMIZE_VERSION ?= v4.5.5 +KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" + +.PHONY: kustomize +kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. +$(KUSTOMIZE): $(LOCALBIN) + test -s $(LOCALBIN)/kustomize || { curl -Ss $(KUSTOMIZE_INSTALL_SCRIPT) | bash -s -- $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN); } + +########## Controller-Gen ########### +CONTROLLER_TOOLS_VERSION ?= v0.14.0 +CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen + +.PHONY: controller-gen +controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. +$(CONTROLLER_GEN): $(LOCALBIN) + test "$(${LOCALBIN}/controller-gen --version)" = "Version: ${CONTROLLER_TOOLS_VERSION}" || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION) + +########## Envtest ########### +ENVTEST ?= $(LOCALBIN)/setup-envtest +KUBEBUILDER_ASSETS=$(LOCALBIN)/k8s/kubebuilder_assets + +define path_error +$(error Error: path is empty: $1) +endef + +# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. +ENVTEST_K8S_VERSION = 1.27.1 + +.PHONY: envtest +envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. +$(ENVTEST): $(LOCALBIN) + test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest + +# Envtest download binaries to k8s/(K8S_Version)-(arch)-(os) directory which is different on every machine. +# To use the same `envtest` binaries on CI and during local development this target moves it to upfront known directory. +# Additionaly `OS-ARCH` return X86_64, but envtest uses `amd64` name. +.PHONY: kubebuilder-assets +kubebuilder-assets: envtest + $(eval DOWNLOADED_ASSETS=$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)) + $(if $(DOWNLOADED_ASSETS),,$(call path_error, ${DOWNLOADED_ASSETS})) + chmod -R 755 $(DOWNLOADED_ASSETS) + mkdir -p $(LOCALBIN)/k8s/kubebuilder_assets/ + mv $(DOWNLOADED_ASSETS)/* $(LOCALBIN)/k8s/kubebuilder_assets/ + rm -d $(DOWNLOADED_ASSETS) diff --git a/markdown_heading_capitalization.js b/markdown_heading_capitalization.js new file mode 100644 index 00000000..7077d5c1 --- /dev/null +++ b/markdown_heading_capitalization.js @@ -0,0 +1,31 @@ +// This file is used to trigger the custom rule that checks if all markdown headings (words longer than 4 characters) are written in the title case. To run this check, you must include the check in the markdownlint command. +// For example, if you want to run the check on the `docs` folder, run the following command: `markdownlint -r ./markdown_heading_capitalization.js docs/`. +module.exports = [{ + "names": [ "custom/capitalize-headings" ], + "description": "Heading words longer than 4 characters should be capitalized", + "tags": [ "formatting" ], + "function": function rule(params, onError) { + params.tokens.filter(function filterToken(token) { + return token.type === "heading_open"; + }).forEach(function forToken(heading) { + var headingTokenContent = heading.line.trim(); + var wordsInHeading = headingTokenContent.split(' '); + + for (var i = 0; i < wordsInHeading.length; i++) { + if (wordsInHeading[i].length > 4 && wordsInHeading[i] && + wordsInHeading[i].charAt(0) !== wordsInHeading[i].charAt(0).toUpperCase()) { + var capitalizedWord = wordsInHeading[i].charAt(0).toUpperCase() + wordsInHeading[i].slice(1); + var detailMessage = "Change " + "'" + wordsInHeading[i] + "'" + " to " + "'" + capitalizedWord + "'"; + + onError({ + "lineNumber": heading.lineNumber, + "detail": detailMessage, + "context": headingTokenContent, // Show the whole heading as context + "range": [headingTokenContent.indexOf(wordsInHeading[i]), wordsInHeading[i].length] // Underline the word which needs a change + }); + } + } + }); + } + }]; + \ No newline at end of file diff --git a/module-config-template.yaml b/module-config-template.yaml new file mode 100644 index 00000000..96e034e0 --- /dev/null +++ b/module-config-template.yaml @@ -0,0 +1,8 @@ +name: {{.Name}} +channel: {{.Channel}} +version: {{.Version}} +defaultCR: config/samples/default-dockerregistry-cr.yaml +manifest: dockerregistry-operator.yaml +annotations: + "operator.kyma-project.io/doc-url": "https://kyma-project.io/#/docker-registry/user/README" +moduleRepo: https://github.com/kyma-project/docker-registry.git diff --git a/sec-scanners-config.yaml b/sec-scanners-config.yaml new file mode 100644 index 00000000..13036cc1 --- /dev/null +++ b/sec-scanners-config.yaml @@ -0,0 +1,11 @@ +module-name: docker-registry +rc-tag: 1.4.0 +protecode: + - europe-docker.pkg.dev/kyma-project/prod/dockerregistry-operator:main + - europe-docker.pkg.dev/kyma-project/prod/tpi/registry:2.8.1-1ae4c190 +whitesource: + language: golang-mod + subprojects: false + exclude: + - "**/test/**" + - "**/*_test.go"