From 9186e2cf687a02da931de889dff04db89d7240ec Mon Sep 17 00:00:00 2001 From: Nisan Itzhakov Date: Wed, 15 Nov 2023 10:35:40 +0200 Subject: [PATCH] first commit --- .dockerignore | 4 + .editorconfig | 2 + .github/workflows/kuttl_workflow.yaml | 59 + .github/workflows/multiple-versions.yaml | 21 + .github/workflows/test.yaml | 36 + .gitignore | 35 + .golangci.yaml | 18 + .run/Build & Run.run.xml | 14 + .run/Make Build.run.xml | 10 + .run/Manifests.run.xml | 8 + .run/PreTest.run.xml | 8 + .run/Test Handlers.run.xml | 17 + .run/Test all.run.xml | 17 + .run/Test v1alpha1 (api_v1alpha1).run.xml | 17 + .run/Test.run.xml | 8 + ...d_dynamic-environment_pkg_handlers.run.xml | 14 + .tool-versions | 7 + Dockerfile | 29 + Makefile | 304 + PROJECT | 23 + README.md | 247 +- api/v1alpha1/condition_types.go | 33 + api/v1alpha1/dynamicenv_types.go | 385 ++ api/v1alpha1/dynamicenv_types_test.go | 115 + api/v1alpha1/dynamicenv_webhook.go | 245 + api/v1alpha1/dynamicenv_webhook_test.go | 480 ++ ...owed-modifications-modified-image-new.yaml | 26 + ...owed-modifications-modified-image-old.yaml | 26 + ...cations-modified-order-of-subsets-new.yaml | 23 + ...cations-modified-order-of-subsets-old.yaml | 23 + ...ions-modifying-number-of-replicas-new.yaml | 17 + ...ions-modifying-number-of-replicas-old.yaml | 16 + ...without-containers-or-init-containers.yaml | 19 + ...ple-containers-with-conflicting-names.yaml | 22 + ...lowed-modifications-adding-subset-new.yaml | 21 + ...lowed-modifications-adding-subset-old.yaml | 16 + ...ications-modifying-container-name-new.yaml | 16 + ...ications-modifying-container-name-old.yaml | 16 + ...cations-modifying-default-version-new.yaml | 17 + ...cations-modifying-default-version-old.yaml | 17 + ...ons-modifying-init_container-name-new.yaml | 16 + ...ons-modifying-init_container-name-old.yaml | 16 + ...difications-modifying-subset-name-new.yaml | 16 + ...difications-modifying-subset-name-old.yaml | 16 + ...tions-modifying-subsets-namespace-new.yaml | 16 + ...tions-modifying-subsets-namespace-old.yaml | 16 + ...ations-modifying-to-zero-replicas-new.yaml | 17 + ...ations-modifying-to-zero-replicas-old.yaml | 16 + ...d-modifications-removing-consumer-new.yaml | 20 + ...d-modifications-removing-consumer-old.yaml | 26 + ...wed-modifications-removing-subset-new.yaml | 16 + ...wed-modifications-removing-subset-old.yaml | 21 + api/v1alpha1/groupversion_info.go | 36 + api/v1alpha1/webhook_suite_test.go | 133 + api/v1alpha1/zz_generated.deepcopy.go | 450 ++ config/certmanager/certificate.yaml | 25 + config/certmanager/kustomization.yaml | 5 + config/certmanager/kustomizeconfig.yaml | 16 + .../crd/bases/riskified.com_dynamicenvs.yaml | 1021 ++++ config/crd/kustomization.yaml | 21 + config/crd/kustomizeconfig.yaml | 19 + .../patches/cainjection_in_dynamicenvs.yaml | 7 + .../crd/patches/webhook_in_dynamicenvs.yaml | 16 + config/crd/riskified.com_dynamicenvs.yaml | 83 + config/default/kustomization.yaml | 74 + config/default/manager_auth_proxy_patch.yaml | 34 + config/default/manager_config_patch.yaml | 20 + config/default/manager_webhook_patch.yaml | 23 + config/default/webhookcainjection_patch.yaml | 8 + config/manager/controller_manager_config.yaml | 11 + config/manager/kustomization.yaml | 16 + config/manager/manager.yaml | 66 + ...onment-operator.clusterserviceversion.yaml | 45 + config/manifests/kustomization.yaml | 27 + config/prometheus/kustomization.yaml | 2 + config/prometheus/monitor.yaml | 20 + .../rbac/auth_proxy_client_clusterrole.yaml | 9 + config/rbac/auth_proxy_role.yaml | 17 + config/rbac/auth_proxy_role_binding.yaml | 12 + config/rbac/auth_proxy_service.yaml | 15 + config/rbac/dynamicenv_editor_role.yaml | 24 + config/rbac/dynamicenv_viewer_role.yaml | 20 + config/rbac/kustomization.yaml | 18 + config/rbac/leader_election_role.yaml | 37 + config/rbac/leader_election_role_binding.yaml | 12 + config/rbac/role.yaml | 67 + config/rbac/role_binding.yaml | 12 + config/rbac/service_account.yaml | 5 + config/samples/kustomization.yaml | 4 + .../riskified_dynamicenv_initContainer.yaml | 18 + .../riskified_v1alpha1_dynamicenv.yaml | 38 + config/scorecard/bases/config.yaml | 7 + config/scorecard/kustomization.yaml | 16 + config/scorecard/patches/basic.config.yaml | 10 + config/scorecard/patches/kuttl.config.yaml | 7 + config/scorecard/patches/olm.config.yaml | 50 + config/webhook/kustomization.yaml | 6 + config/webhook/kustomizeconfig.yaml | 18 + config/webhook/manifests.yaml | 27 + config/webhook/service.yaml | 13 + controllers/dynamicenv_controller.go | 532 ++ controllers/suite_test.go | 92 + crd-docs/config.yaml | 19 + crd-docs/crd.md | 218 + crd-docs/templates/markdown/gv_details.tpl | 19 + crd-docs/templates/markdown/gv_list.tpl | 15 + crd-docs/templates/markdown/type.tpl | 33 + crd-docs/templates/markdown/type_members.tpl | 8 + dev-resources/0-namespace.yaml | 6 + dev-resources/1-bookinfo.yaml | 286 + dev-resources/2-destination-rule-all.yaml | 44 + dev-resources/3-virtual-service-all-v1.yaml | 52 + .../dependencies/cert-manager_1.8.yaml | 5388 +++++++++++++++++ .../dependencies/istio/destinationrule.yaml | 2541 ++++++++ .../dependencies/istio/virtualservice.yaml | 1643 +++++ e2e-testing/env | 1 + .../00-assert.yaml | 45 + .../00-full-bookinfo-details.yaml | 376 ++ ...elete-deployment-and-destination-rule.yaml | 11 + .../01-errors.yaml | 11 + .../02-delete-dynamic-env.yaml | 6 + .../02-errors.yaml | 29 + .../accidental-resources-deletion/Readme.md | 4 + .../00-assert.yaml | 60 + .../alternative-default-version/00-base.yaml | 378 ++ .../alternative-default-version/Readme.md | 12 + .../00-assert.yaml | 20 + .../00-bookinfo-details.yaml | 148 + .../01-delete-dynamic-environment.yaml | 8 + .../01-errors.yaml | 7 + .../Readme.md | 7 + .../delegate-virtual-service/00-assert.yaml | 140 + .../delegate-virtual-service/00-base.yaml | 181 + .../delegate-virtual-service/01-assert.yaml | 75 + .../01-delete-dynamic-env.yaml | 8 + .../kuttl/delegate-virtual-service/Readme.md | 10 + .../00-assert.yaml | 17 + .../00-bookinfo-details.yaml | 106 + .../global-virtual-service-errors/Readme.md | 5 + e2e-testing/kuttl/kuttl-test.yaml | 5 + .../modify-valid-httproutes/00-base.yaml | 73 + .../00-dynamic-env.yaml | 19 + .../00-virtual-service.yaml | 27 + .../modify-valid-httproutes/01-assert.yaml | 42 + .../modify-valid-httproutes/02-assert.yaml | 21 + .../02-delete-dynamic-env.yaml | 8 + .../kuttl/modify-valid-httproutes/Readme.md | 14 + .../kuttl/multiple-containers/00-assert.yaml | 26 + .../00-bookinfo-details.yaml | 125 + .../kuttl/multiple-containers/Readme.md | 3 + .../kuttl/multiple-matches/00-assert.yaml | 47 + .../multiple-matches/00-bookinfo-details.yaml | 110 + e2e-testing/kuttl/multiple-matches/Readme.md | 6 + .../00-assert.yaml | 28 + .../00-bookinfo-details.yaml | 119 + .../Readme.md | 3 + .../00-assert.yaml | 46 + .../00-bookinfo-details.yaml | 121 + .../00-assert.yaml | 28 + .../00-bookinfo-details.yaml | 133 + .../01-remove-base-destination-rule.yaml | 8 + .../02-assert.yaml | 28 + .../02-remove-DE-destination-rule.yaml | 8 + ...3-remove-second-base-destination-rule.yaml | 8 + .../04-assert.yaml | 31 + .../04-remove-DE-second-destination-rule.yaml | 8 + .../05-add-virtual-service.yaml | 15 + .../05-assert.yaml | 31 + .../05-restore-all-destination-rules.yaml | 25 + .../multiple-services-scenarios/Readme.md | 10 + .../kuttl/reconcile-flow/00-assert.yaml | 15 + e2e-testing/kuttl/reconcile-flow/00-base.yaml | 16 + e2e-testing/kuttl/reconcile-flow/Readme.md | 4 + e2e-testing/kuttl/replica-size/00-assert.yaml | 33 + .../replica-size/00-bookinfo-details.yaml | 144 + e2e-testing/kuttl/replica-size/01-assert.yaml | 22 + .../kuttl/replica-size/01-dynamicenv.yaml | 30 + .../replica-size/02-delete-dynamic-env.yaml | 8 + e2e-testing/kuttl/replica-size/02-errors.yaml | 17 + e2e-testing/kuttl/replica-size/Readme.md | 10 + .../kuttl/response-headers/00-assert.yaml | 137 + .../kuttl/response-headers/00-base.yaml | 176 + e2e-testing/kuttl/response-headers/Readme.md | 4 + e2e-testing/kuttl/simple-test/00-assert.yaml | 37 + .../simple-test/00-bookinfo-details.yaml | 106 + .../simple-test/01-delete-dynamic-env.yaml | 8 + e2e-testing/kuttl/simple-test/01-errors.yaml | 17 + e2e-testing/kuttl/simple-test/Readme.md | 8 + .../kuttl/status-updates/00-assert.yaml | 23 + .../status-updates/00-bookinfo-details.yaml | 106 + e2e-testing/kuttl/status-updates/Readme.md | 4 + .../update-subset-resources/00-assert.yaml | 31 + .../00-bookinfo-details.yaml | 106 + .../update-subset-resources/01-assert.yaml | 28 + .../01-update-subset.yaml | 18 + .../kuttl/update-subset-resources/Readme.md | 8 + e2e-testing/kuttl/valid-state/00-assert.yaml | 16 + .../valid-state/00-bookinfo-details.yaml | 106 + e2e-testing/kuttl/valid-state/Readme.md | 7 + .../vs-with-multiple-services/00-assert.yaml | 77 + .../00-full-bookinfo-details.yaml | 363 ++ .../kuttl/vs-with-multiple-services/Readme.md | 12 + .../kuttl/watch-annotations/00-assert.yaml | 34 + .../00-bookinfo-details.yaml | 106 + .../kuttl/watch-annotations/01-assert.yaml | 21 + .../kuttl/watch-annotations/01-update.yaml | 20 + .../kuttl/watch-annotations/02-assert.yaml | 8 + .../02-delete-dynamic-env.yaml | 8 + .../kuttl/watch-annotations/02-errors.yaml | 17 + e2e-testing/kuttl/watch-annotations/Readme.md | 10 + .../kuttl/weighted-routes/00-assert.yaml | 276 + .../kuttl/weighted-routes/00-base.yaml | 241 + .../kuttl/weighted-routes/01-assert.yaml | 126 + .../weighted-routes/01-delete-dynamicenv.yaml | 7 + .../kuttl/weighted-routes/01-errors.yaml | 13 + .../kuttl/weighted-routes/02-assert.yaml | 230 + .../02-canary-virtual-service.yaml | 150 + e2e-testing/kuttl/weighted-routes/Readme.md | 12 + e2e-testing/scripts/setup.sh | 89 + extensions/deployment_extensions.go | 25 + extensions/doc.go | 9 + go.mod | 83 + go.sum | 642 ++ hack/boilerplate.go.txt | 15 + helm/dynamic-environment/.helmignore | 22 + helm/dynamic-environment/Chart.yaml | 18 + .../templates/_helpers.tpl | 113 + helm/dynamic-environment/templates/crd.yaml | 779 +++ .../templates/deployment.yaml | 119 + helm/dynamic-environment/templates/pdb.yaml | 14 + helm/dynamic-environment/templates/rbac.yaml | 141 + .../templates/service.yaml | 37 + .../templates/serviceaccount.yaml | 13 + .../templates/servicemonitor.yaml | 20 + .../templates/webhook.yaml | 74 + helm/dynamic-environment/values.yaml | 116 + main.go | 162 + pkg/handlers/common_utils_private_test.go | 23 + pkg/handlers/deployment_handler.go | 297 + .../deployment_handler_private_test.go | 200 + pkg/handlers/deployment_handler_test.go | 195 + pkg/handlers/destinationrule_handler.go | 211 + .../destinationrule_handler_private_test.go | 65 + pkg/handlers/destinationrule_handler_test.go | 125 + pkg/handlers/dynamicenv_status_handler.go | 239 + ...tination-rule-with-unrelated-hostname.yaml | 13 + pkg/handlers/fixtures/simple-dynamicenv.yaml | 22 + pkg/handlers/handler.go | 40 + pkg/handlers/handlers_suite_test.go | 49 + pkg/handlers/status_handler_test.go | 359 ++ pkg/handlers/virtualservice_handler.go | 331 + .../virtualservice_handler_private_test.go | 210 + pkg/handlers/virtualservice_handler_test.go | 131 + pkg/helpers/helpers.go | 112 + pkg/helpers/helpers_test.go | 52 + pkg/helpers/k8s_helpers.go | 63 + pkg/helpers/k8s_helpers_test.go | 48 + pkg/metrics/collector.go | 77 + pkg/names/constants.go | 18 + pkg/watches/enqueue_annotations.go | 109 + 260 files changed, 27475 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 .editorconfig create mode 100644 .github/workflows/kuttl_workflow.yaml create mode 100644 .github/workflows/multiple-versions.yaml create mode 100644 .github/workflows/test.yaml create mode 100644 .gitignore create mode 100644 .golangci.yaml create mode 100644 .run/Build & Run.run.xml create mode 100644 .run/Make Build.run.xml create mode 100644 .run/Manifests.run.xml create mode 100644 .run/PreTest.run.xml create mode 100644 .run/Test Handlers.run.xml create mode 100644 .run/Test all.run.xml create mode 100644 .run/Test v1alpha1 (api_v1alpha1).run.xml create mode 100644 .run/Test.run.xml create mode 100644 .run/TestAPIs in github.com_riskified_dynamic-environment_pkg_handlers.run.xml create mode 100644 .tool-versions create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 PROJECT create mode 100644 api/v1alpha1/condition_types.go create mode 100644 api/v1alpha1/dynamicenv_types.go create mode 100644 api/v1alpha1/dynamicenv_types_test.go create mode 100644 api/v1alpha1/dynamicenv_webhook.go create mode 100644 api/v1alpha1/dynamicenv_webhook_test.go create mode 100644 api/v1alpha1/fixtures/allowed-modifications-modified-image-new.yaml create mode 100644 api/v1alpha1/fixtures/allowed-modifications-modified-image-old.yaml create mode 100644 api/v1alpha1/fixtures/allowed-modifications-modified-order-of-subsets-new.yaml create mode 100644 api/v1alpha1/fixtures/allowed-modifications-modified-order-of-subsets-old.yaml create mode 100644 api/v1alpha1/fixtures/allowed-modifications-modifying-number-of-replicas-new.yaml create mode 100644 api/v1alpha1/fixtures/allowed-modifications-modifying-number-of-replicas-old.yaml create mode 100644 api/v1alpha1/fixtures/create-rejects-deployments-without-containers-or-init-containers.yaml create mode 100644 api/v1alpha1/fixtures/deployment-with-multiple-containers-with-conflicting-names.yaml create mode 100644 api/v1alpha1/fixtures/disallowed-modifications-adding-subset-new.yaml create mode 100644 api/v1alpha1/fixtures/disallowed-modifications-adding-subset-old.yaml create mode 100644 api/v1alpha1/fixtures/disallowed-modifications-modifying-container-name-new.yaml create mode 100644 api/v1alpha1/fixtures/disallowed-modifications-modifying-container-name-old.yaml create mode 100644 api/v1alpha1/fixtures/disallowed-modifications-modifying-default-version-new.yaml create mode 100644 api/v1alpha1/fixtures/disallowed-modifications-modifying-default-version-old.yaml create mode 100644 api/v1alpha1/fixtures/disallowed-modifications-modifying-init_container-name-new.yaml create mode 100644 api/v1alpha1/fixtures/disallowed-modifications-modifying-init_container-name-old.yaml create mode 100644 api/v1alpha1/fixtures/disallowed-modifications-modifying-subset-name-new.yaml create mode 100644 api/v1alpha1/fixtures/disallowed-modifications-modifying-subset-name-old.yaml create mode 100644 api/v1alpha1/fixtures/disallowed-modifications-modifying-subsets-namespace-new.yaml create mode 100644 api/v1alpha1/fixtures/disallowed-modifications-modifying-subsets-namespace-old.yaml create mode 100644 api/v1alpha1/fixtures/disallowed-modifications-modifying-to-zero-replicas-new.yaml create mode 100644 api/v1alpha1/fixtures/disallowed-modifications-modifying-to-zero-replicas-old.yaml create mode 100644 api/v1alpha1/fixtures/disallowed-modifications-removing-consumer-new.yaml create mode 100644 api/v1alpha1/fixtures/disallowed-modifications-removing-consumer-old.yaml create mode 100644 api/v1alpha1/fixtures/disallowed-modifications-removing-subset-new.yaml create mode 100644 api/v1alpha1/fixtures/disallowed-modifications-removing-subset-old.yaml create mode 100644 api/v1alpha1/groupversion_info.go create mode 100644 api/v1alpha1/webhook_suite_test.go create mode 100644 api/v1alpha1/zz_generated.deepcopy.go create mode 100644 config/certmanager/certificate.yaml create mode 100644 config/certmanager/kustomization.yaml create mode 100644 config/certmanager/kustomizeconfig.yaml create mode 100644 config/crd/bases/riskified.com_dynamicenvs.yaml create mode 100644 config/crd/kustomization.yaml create mode 100644 config/crd/kustomizeconfig.yaml create mode 100644 config/crd/patches/cainjection_in_dynamicenvs.yaml create mode 100644 config/crd/patches/webhook_in_dynamicenvs.yaml create mode 100644 config/crd/riskified.com_dynamicenvs.yaml create mode 100644 config/default/kustomization.yaml create mode 100644 config/default/manager_auth_proxy_patch.yaml create mode 100644 config/default/manager_config_patch.yaml create mode 100644 config/default/manager_webhook_patch.yaml create mode 100644 config/default/webhookcainjection_patch.yaml create mode 100644 config/manager/controller_manager_config.yaml create mode 100644 config/manager/kustomization.yaml create mode 100644 config/manager/manager.yaml create mode 100644 config/manifests/bases/dynamic-environment-operator.clusterserviceversion.yaml create mode 100644 config/manifests/kustomization.yaml create mode 100644 config/prometheus/kustomization.yaml create mode 100644 config/prometheus/monitor.yaml create mode 100644 config/rbac/auth_proxy_client_clusterrole.yaml create mode 100644 config/rbac/auth_proxy_role.yaml create mode 100644 config/rbac/auth_proxy_role_binding.yaml create mode 100644 config/rbac/auth_proxy_service.yaml create mode 100644 config/rbac/dynamicenv_editor_role.yaml create mode 100644 config/rbac/dynamicenv_viewer_role.yaml create mode 100644 config/rbac/kustomization.yaml create mode 100644 config/rbac/leader_election_role.yaml create mode 100644 config/rbac/leader_election_role_binding.yaml create mode 100644 config/rbac/role.yaml create mode 100644 config/rbac/role_binding.yaml create mode 100644 config/rbac/service_account.yaml create mode 100644 config/samples/kustomization.yaml create mode 100644 config/samples/riskified_dynamicenv_initContainer.yaml create mode 100644 config/samples/riskified_v1alpha1_dynamicenv.yaml create mode 100644 config/scorecard/bases/config.yaml create mode 100644 config/scorecard/kustomization.yaml create mode 100644 config/scorecard/patches/basic.config.yaml create mode 100644 config/scorecard/patches/kuttl.config.yaml create mode 100644 config/scorecard/patches/olm.config.yaml create mode 100644 config/webhook/kustomization.yaml create mode 100644 config/webhook/kustomizeconfig.yaml create mode 100644 config/webhook/manifests.yaml create mode 100644 config/webhook/service.yaml create mode 100644 controllers/dynamicenv_controller.go create mode 100644 controllers/suite_test.go create mode 100644 crd-docs/config.yaml create mode 100644 crd-docs/crd.md create mode 100644 crd-docs/templates/markdown/gv_details.tpl create mode 100644 crd-docs/templates/markdown/gv_list.tpl create mode 100644 crd-docs/templates/markdown/type.tpl create mode 100644 crd-docs/templates/markdown/type_members.tpl create mode 100644 dev-resources/0-namespace.yaml create mode 100644 dev-resources/1-bookinfo.yaml create mode 100644 dev-resources/2-destination-rule-all.yaml create mode 100644 dev-resources/3-virtual-service-all-v1.yaml create mode 100644 e2e-testing/dependencies/cert-manager_1.8.yaml create mode 100644 e2e-testing/dependencies/istio/destinationrule.yaml create mode 100644 e2e-testing/dependencies/istio/virtualservice.yaml create mode 100644 e2e-testing/env create mode 100644 e2e-testing/kuttl/accidental-resources-deletion/00-assert.yaml create mode 100644 e2e-testing/kuttl/accidental-resources-deletion/00-full-bookinfo-details.yaml create mode 100644 e2e-testing/kuttl/accidental-resources-deletion/01-delete-deployment-and-destination-rule.yaml create mode 100644 e2e-testing/kuttl/accidental-resources-deletion/01-errors.yaml create mode 100644 e2e-testing/kuttl/accidental-resources-deletion/02-delete-dynamic-env.yaml create mode 100644 e2e-testing/kuttl/accidental-resources-deletion/02-errors.yaml create mode 100644 e2e-testing/kuttl/accidental-resources-deletion/Readme.md create mode 100644 e2e-testing/kuttl/alternative-default-version/00-assert.yaml create mode 100644 e2e-testing/kuttl/alternative-default-version/00-base.yaml create mode 100644 e2e-testing/kuttl/alternative-default-version/Readme.md create mode 100644 e2e-testing/kuttl/consumers-with-and-without-errors/00-assert.yaml create mode 100644 e2e-testing/kuttl/consumers-with-and-without-errors/00-bookinfo-details.yaml create mode 100644 e2e-testing/kuttl/consumers-with-and-without-errors/01-delete-dynamic-environment.yaml create mode 100644 e2e-testing/kuttl/consumers-with-and-without-errors/01-errors.yaml create mode 100644 e2e-testing/kuttl/consumers-with-and-without-errors/Readme.md create mode 100644 e2e-testing/kuttl/delegate-virtual-service/00-assert.yaml create mode 100644 e2e-testing/kuttl/delegate-virtual-service/00-base.yaml create mode 100644 e2e-testing/kuttl/delegate-virtual-service/01-assert.yaml create mode 100644 e2e-testing/kuttl/delegate-virtual-service/01-delete-dynamic-env.yaml create mode 100644 e2e-testing/kuttl/delegate-virtual-service/Readme.md create mode 100644 e2e-testing/kuttl/global-virtual-service-errors/00-assert.yaml create mode 100644 e2e-testing/kuttl/global-virtual-service-errors/00-bookinfo-details.yaml create mode 100644 e2e-testing/kuttl/global-virtual-service-errors/Readme.md create mode 100644 e2e-testing/kuttl/kuttl-test.yaml create mode 100644 e2e-testing/kuttl/modify-valid-httproutes/00-base.yaml create mode 100644 e2e-testing/kuttl/modify-valid-httproutes/00-dynamic-env.yaml create mode 100644 e2e-testing/kuttl/modify-valid-httproutes/00-virtual-service.yaml create mode 100644 e2e-testing/kuttl/modify-valid-httproutes/01-assert.yaml create mode 100644 e2e-testing/kuttl/modify-valid-httproutes/02-assert.yaml create mode 100644 e2e-testing/kuttl/modify-valid-httproutes/02-delete-dynamic-env.yaml create mode 100644 e2e-testing/kuttl/modify-valid-httproutes/Readme.md create mode 100644 e2e-testing/kuttl/multiple-containers/00-assert.yaml create mode 100644 e2e-testing/kuttl/multiple-containers/00-bookinfo-details.yaml create mode 100644 e2e-testing/kuttl/multiple-containers/Readme.md create mode 100644 e2e-testing/kuttl/multiple-matches/00-assert.yaml create mode 100644 e2e-testing/kuttl/multiple-matches/00-bookinfo-details.yaml create mode 100644 e2e-testing/kuttl/multiple-matches/Readme.md create mode 100644 e2e-testing/kuttl/multiple-services-scenarios-no-vs/00-assert.yaml create mode 100644 e2e-testing/kuttl/multiple-services-scenarios-no-vs/00-bookinfo-details.yaml create mode 100644 e2e-testing/kuttl/multiple-services-scenarios-no-vs/Readme.md create mode 100644 e2e-testing/kuttl/multiple-services-scenarios-no-working-single-host/00-assert.yaml create mode 100644 e2e-testing/kuttl/multiple-services-scenarios-no-working-single-host/00-bookinfo-details.yaml create mode 100644 e2e-testing/kuttl/multiple-services-scenarios/00-assert.yaml create mode 100644 e2e-testing/kuttl/multiple-services-scenarios/00-bookinfo-details.yaml create mode 100644 e2e-testing/kuttl/multiple-services-scenarios/01-remove-base-destination-rule.yaml create mode 100644 e2e-testing/kuttl/multiple-services-scenarios/02-assert.yaml create mode 100644 e2e-testing/kuttl/multiple-services-scenarios/02-remove-DE-destination-rule.yaml create mode 100644 e2e-testing/kuttl/multiple-services-scenarios/03-remove-second-base-destination-rule.yaml create mode 100644 e2e-testing/kuttl/multiple-services-scenarios/04-assert.yaml create mode 100644 e2e-testing/kuttl/multiple-services-scenarios/04-remove-DE-second-destination-rule.yaml create mode 100644 e2e-testing/kuttl/multiple-services-scenarios/05-add-virtual-service.yaml create mode 100644 e2e-testing/kuttl/multiple-services-scenarios/05-assert.yaml create mode 100644 e2e-testing/kuttl/multiple-services-scenarios/05-restore-all-destination-rules.yaml create mode 100644 e2e-testing/kuttl/multiple-services-scenarios/Readme.md create mode 100644 e2e-testing/kuttl/reconcile-flow/00-assert.yaml create mode 100644 e2e-testing/kuttl/reconcile-flow/00-base.yaml create mode 100644 e2e-testing/kuttl/reconcile-flow/Readme.md create mode 100644 e2e-testing/kuttl/replica-size/00-assert.yaml create mode 100644 e2e-testing/kuttl/replica-size/00-bookinfo-details.yaml create mode 100644 e2e-testing/kuttl/replica-size/01-assert.yaml create mode 100644 e2e-testing/kuttl/replica-size/01-dynamicenv.yaml create mode 100644 e2e-testing/kuttl/replica-size/02-delete-dynamic-env.yaml create mode 100644 e2e-testing/kuttl/replica-size/02-errors.yaml create mode 100644 e2e-testing/kuttl/replica-size/Readme.md create mode 100644 e2e-testing/kuttl/response-headers/00-assert.yaml create mode 100644 e2e-testing/kuttl/response-headers/00-base.yaml create mode 100644 e2e-testing/kuttl/response-headers/Readme.md create mode 100644 e2e-testing/kuttl/simple-test/00-assert.yaml create mode 100644 e2e-testing/kuttl/simple-test/00-bookinfo-details.yaml create mode 100644 e2e-testing/kuttl/simple-test/01-delete-dynamic-env.yaml create mode 100644 e2e-testing/kuttl/simple-test/01-errors.yaml create mode 100644 e2e-testing/kuttl/simple-test/Readme.md create mode 100644 e2e-testing/kuttl/status-updates/00-assert.yaml create mode 100644 e2e-testing/kuttl/status-updates/00-bookinfo-details.yaml create mode 100644 e2e-testing/kuttl/status-updates/Readme.md create mode 100644 e2e-testing/kuttl/update-subset-resources/00-assert.yaml create mode 100644 e2e-testing/kuttl/update-subset-resources/00-bookinfo-details.yaml create mode 100644 e2e-testing/kuttl/update-subset-resources/01-assert.yaml create mode 100644 e2e-testing/kuttl/update-subset-resources/01-update-subset.yaml create mode 100644 e2e-testing/kuttl/update-subset-resources/Readme.md create mode 100644 e2e-testing/kuttl/valid-state/00-assert.yaml create mode 100644 e2e-testing/kuttl/valid-state/00-bookinfo-details.yaml create mode 100644 e2e-testing/kuttl/valid-state/Readme.md create mode 100644 e2e-testing/kuttl/vs-with-multiple-services/00-assert.yaml create mode 100644 e2e-testing/kuttl/vs-with-multiple-services/00-full-bookinfo-details.yaml create mode 100644 e2e-testing/kuttl/vs-with-multiple-services/Readme.md create mode 100644 e2e-testing/kuttl/watch-annotations/00-assert.yaml create mode 100644 e2e-testing/kuttl/watch-annotations/00-bookinfo-details.yaml create mode 100644 e2e-testing/kuttl/watch-annotations/01-assert.yaml create mode 100644 e2e-testing/kuttl/watch-annotations/01-update.yaml create mode 100644 e2e-testing/kuttl/watch-annotations/02-assert.yaml create mode 100644 e2e-testing/kuttl/watch-annotations/02-delete-dynamic-env.yaml create mode 100644 e2e-testing/kuttl/watch-annotations/02-errors.yaml create mode 100644 e2e-testing/kuttl/watch-annotations/Readme.md create mode 100644 e2e-testing/kuttl/weighted-routes/00-assert.yaml create mode 100644 e2e-testing/kuttl/weighted-routes/00-base.yaml create mode 100644 e2e-testing/kuttl/weighted-routes/01-assert.yaml create mode 100644 e2e-testing/kuttl/weighted-routes/01-delete-dynamicenv.yaml create mode 100644 e2e-testing/kuttl/weighted-routes/01-errors.yaml create mode 100644 e2e-testing/kuttl/weighted-routes/02-assert.yaml create mode 100644 e2e-testing/kuttl/weighted-routes/02-canary-virtual-service.yaml create mode 100644 e2e-testing/kuttl/weighted-routes/Readme.md create mode 100755 e2e-testing/scripts/setup.sh create mode 100644 extensions/deployment_extensions.go create mode 100644 extensions/doc.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 hack/boilerplate.go.txt create mode 100644 helm/dynamic-environment/.helmignore create mode 100644 helm/dynamic-environment/Chart.yaml create mode 100644 helm/dynamic-environment/templates/_helpers.tpl create mode 100644 helm/dynamic-environment/templates/crd.yaml create mode 100644 helm/dynamic-environment/templates/deployment.yaml create mode 100644 helm/dynamic-environment/templates/pdb.yaml create mode 100644 helm/dynamic-environment/templates/rbac.yaml create mode 100644 helm/dynamic-environment/templates/service.yaml create mode 100644 helm/dynamic-environment/templates/serviceaccount.yaml create mode 100644 helm/dynamic-environment/templates/servicemonitor.yaml create mode 100644 helm/dynamic-environment/templates/webhook.yaml create mode 100644 helm/dynamic-environment/values.yaml create mode 100644 main.go create mode 100644 pkg/handlers/common_utils_private_test.go create mode 100644 pkg/handlers/deployment_handler.go create mode 100644 pkg/handlers/deployment_handler_private_test.go create mode 100644 pkg/handlers/deployment_handler_test.go create mode 100644 pkg/handlers/destinationrule_handler.go create mode 100644 pkg/handlers/destinationrule_handler_private_test.go create mode 100644 pkg/handlers/destinationrule_handler_test.go create mode 100644 pkg/handlers/dynamicenv_status_handler.go create mode 100644 pkg/handlers/fixtures/destination-rule-with-unrelated-hostname.yaml create mode 100644 pkg/handlers/fixtures/simple-dynamicenv.yaml create mode 100644 pkg/handlers/handler.go create mode 100644 pkg/handlers/handlers_suite_test.go create mode 100644 pkg/handlers/status_handler_test.go create mode 100644 pkg/handlers/virtualservice_handler.go create mode 100644 pkg/handlers/virtualservice_handler_private_test.go create mode 100644 pkg/handlers/virtualservice_handler_test.go create mode 100644 pkg/helpers/helpers.go create mode 100644 pkg/helpers/helpers_test.go create mode 100644 pkg/helpers/k8s_helpers.go create mode 100644 pkg/helpers/k8s_helpers_test.go create mode 100644 pkg/metrics/collector.go create mode 100644 pkg/names/constants.go create mode 100644 pkg/watches/enqueue_annotations.go diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0f04682 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file +# Ignore build and test binaries. +bin/ +testbin/ diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1ae7d4d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,2 @@ +[*.md] +max_line_length = 100 diff --git a/.github/workflows/kuttl_workflow.yaml b/.github/workflows/kuttl_workflow.yaml new file mode 100644 index 0000000..ff55a6d --- /dev/null +++ b/.github/workflows/kuttl_workflow.yaml @@ -0,0 +1,59 @@ +name: kuttl_workflow +on: + workflow_call: + inputs: + istio_ver: + required: true + type: string + kind_image: + required: true + type: string + +jobs: + kuttl: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Versions from .tool-versions + uses: endorama/asdf-parse-tool-versions@v1 + id: versions + - name: Install asdf & tools + uses: asdf-vm/actions/install@v2 + - name: custom environment variables + uses: krisalay/export-env@v1.0.0 + with: + filename: e2e-testing/env + - uses: helm/kind-action@v1.4.0 + if: inputs.kind_image == '' + with: + node_image: "${{ env.KIND_CUSTOM_IMAGE }}" + version: "v${{ env.KIND_VERSION }}" + cluster_name: kuttl + - uses: helm/kind-action@v1.4.0 + if: inputs.kind_image != '' + with: + node_image: ${{ inputs.kind_image }} + version: "v${{ env.KIND_VERSION }}" + cluster_name: kuttl + - name: add to PATH + run: | + mkdir -p ${HOME}/bin + echo "${HOME}/bin" >> "$GITHUB_PATH" + - name: install istio (custom version) + run: | + asdf install istioctl ${{ inputs.istio_ver }} + asdf local istioctl ${{ inputs.istio_ver }} + istioctl version --remote=false + if: inputs.istio_ver != '' + - name: Setup Cluster + run: ./e2e-testing/scripts/setup.sh up + env: + KIND_CLUSTER_NAME: kuttl + # Uncomment the next step to access a shell on github actions. + # - name: tmate debug session + # uses: mxschmitt/action-tmate@v3 + - name: Run kuttl tests + run: | + # Let the system enough time to start + sleep 30 + make kuttl diff --git a/.github/workflows/multiple-versions.yaml b/.github/workflows/multiple-versions.yaml new file mode 100644 index 0000000..4add36d --- /dev/null +++ b/.github/workflows/multiple-versions.yaml @@ -0,0 +1,21 @@ +name: multiple-versions +on: + schedule: + - cron: '0 0 * * 0' + workflow_dispatch: + +jobs: + kuttl: + strategy: + matrix: + versions: + - kind_image: "kindest/node:v1.25.3" + istio: "1.16.2" + - kind_image: "kindest/node:v1.23.13" + istio: "1.14.6" + - kind_image: "kindest/node:v1.21.10" + istio: "1.12.2" + uses: ./.github/workflows/kuttl_workflow.yaml + with: + istio_ver: ${{ matrix.versions.istio }} + kind_image: ${{ matrix.versions.kind_image }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..8f4cf89 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,36 @@ +name: tests +on: + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + kuttl: + uses: ./.github/workflows/kuttl_workflow.yaml + with: + istio_ver: '' + kind_image: '' + tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install asdf & tools + uses: asdf-vm/actions/install@v2 + - run: make test lint + pre-merge-tasks: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install asdf & tools + uses: asdf-vm/actions/install@v2 + - name: Verify pre-merge tasks are committed. + run: | + make prepare + make pre-merge + if [[ `git status --porcelain` ]]; then + echo "There seem to be changes after 'pre-merge' tasks, did you run these tasks before committing?" + exit 1 + else + echo "Pre-merge tasks are committed :)" + fi diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2179489 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +bin +testbin/* + +# 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.* + +# editor and IDE paraphernalia +.idea +*.swp +*.swo +*~ +.vscode + +# generated during make bundle +/bundle.Dockerfile +/kubeconfig + +# terraform +.terraform/ + +*.tfstate* diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..601ad27 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,18 @@ +# Our default golangci-lint configuration +# If these linters fail then it should fail the build. +run: + timeout: 5m +linters: + disable-all: true + # these are the default list of enabled + enable: + - errcheck + - gosimple + - govet + - ineffassign + - staticcheck + - typecheck + - unused + presets: + - bugs + - unused diff --git a/.run/Build & Run.run.xml b/.run/Build & Run.run.xml new file mode 100644 index 0000000..ec38e49 --- /dev/null +++ b/.run/Build & Run.run.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.run/Make Build.run.xml b/.run/Make Build.run.xml new file mode 100644 index 0000000..23fada8 --- /dev/null +++ b/.run/Make Build.run.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.run/Manifests.run.xml b/.run/Manifests.run.xml new file mode 100644 index 0000000..479c15b --- /dev/null +++ b/.run/Manifests.run.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.run/PreTest.run.xml b/.run/PreTest.run.xml new file mode 100644 index 0000000..904bae1 --- /dev/null +++ b/.run/PreTest.run.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.run/Test Handlers.run.xml b/.run/Test Handlers.run.xml new file mode 100644 index 0000000..1e4154c --- /dev/null +++ b/.run/Test Handlers.run.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.run/Test all.run.xml b/.run/Test all.run.xml new file mode 100644 index 0000000..d423365 --- /dev/null +++ b/.run/Test all.run.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.run/Test v1alpha1 (api_v1alpha1).run.xml b/.run/Test v1alpha1 (api_v1alpha1).run.xml new file mode 100644 index 0000000..c5b132a --- /dev/null +++ b/.run/Test v1alpha1 (api_v1alpha1).run.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.run/Test.run.xml b/.run/Test.run.xml new file mode 100644 index 0000000..d24fb5d --- /dev/null +++ b/.run/Test.run.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.run/TestAPIs in github.com_riskified_dynamic-environment_pkg_handlers.run.xml b/.run/TestAPIs in github.com_riskified_dynamic-environment_pkg_handlers.run.xml new file mode 100644 index 0000000..544c08f --- /dev/null +++ b/.run/TestAPIs in github.com_riskified_dynamic-environment_pkg_handlers.run.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..0cadd5c --- /dev/null +++ b/.tool-versions @@ -0,0 +1,7 @@ +golang 1.19.8 +act 0.2.26 +kuttl 0.15.0 +operator-sdk 1.28.0 +kind 0.18.0 +istioctl 1.17.2 +golangci-lint 1.53.2 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..73f3ef0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +# Build the manager binary +FROM golang:1.19 as builder + +WORKDIR /workspace +# Copy the Go Modules manifests +COPY go.mod go.mod +COPY go.sum go.sum +# cache deps before building and copying source so that we don't need to re-download as much +# and so that source changes don't invalidate our downloaded layer +RUN go mod download + +# Copy the go source +COPY main.go main.go +COPY api/ api/ +COPY controllers/ controllers/ +COPY pkg/ pkg/ +COPY extensions/ extensions/ + +# Build +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o manager main.go + +# Use distroless as minimal base image to package the manager binary +# Refer to https://github.com/GoogleContainerTools/distroless for more details +FROM gcr.io/distroless/static:nonroot +WORKDIR / +COPY --from=builder /workspace/manager . +USER 65532:65532 + +ENTRYPOINT ["/manager"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4fd8f02 --- /dev/null +++ b/Makefile @@ -0,0 +1,304 @@ +# VERSION defines the project version for the bundle. +# Update this value when you upgrade the version of your project. +# To re-generate a bundle for another specific version without changing the standard setup, you can: +# - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2) +# - use environment variables to overwrite this value (e.g export VERSION=0.0.2) +VERSION ?= 0.0.1 + +# CHANNELS define the bundle channels used in the bundle. +# Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable") +# To re-generate a bundle for other specific channels without changing the standard setup, you can: +# - use the CHANNELS as arg of the bundle target (e.g make bundle CHANNELS=candidate,fast,stable) +# - use environment variables to overwrite this value (e.g export CHANNELS="candidate,fast,stable") +ifneq ($(origin CHANNELS), undefined) +BUNDLE_CHANNELS := --channels=$(CHANNELS) +endif + +# DEFAULT_CHANNEL defines the default channel used in the bundle. +# Add a new line here if you would like to change its default config. (E.g DEFAULT_CHANNEL = "stable") +# To re-generate a bundle for any other default channel without changing the default setup, you can: +# - use the DEFAULT_CHANNEL as arg of the bundle target (e.g make bundle DEFAULT_CHANNEL=stable) +# - use environment variables to overwrite this value (e.g export DEFAULT_CHANNEL="stable") +ifneq ($(origin DEFAULT_CHANNEL), undefined) +BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL) +endif +BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL) + +# IMAGE_TAG_BASE defines the docker.io namespace and part of the image name for remote images. +# This variable is used to construct full image tags for bundle and catalog images. +# +# For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both +# com/dynamic-environment-operator-bundle:$VERSION and com/dynamic-environment-operator-catalog:$VERSION. +IMAGE_TAG_BASE ?= com/dynamic-environment-operator + +# BUNDLE_IMG defines the image:tag used for the bundle. +# You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=/:) +BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION) + +# BUNDLE_GEN_FLAGS are the flags passed to the operator-sdk generate bundle command +BUNDLE_GEN_FLAGS ?= -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) + +# USE_IMAGE_DIGESTS defines if images are resolved via tags or digests +# You can enable this value if you would like to use SHA Based Digests +# To enable set flag to true +USE_IMAGE_DIGESTS ?= false +ifeq ($(USE_IMAGE_DIGESTS), true) + BUNDLE_GEN_FLAGS += --use-image-digests +endif + +# Image URL to use all building/pushing image targets +IMG ?= controller:latest +# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. +ENVTEST_K8S_VERSION = 1.26 + +# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) +ifeq (,$(shell go env GOBIN)) +GOBIN=$(shell go env GOPATH)/bin +else +GOBIN=$(shell go env GOBIN) +endif + +# Setting SHELL to bash allows bash commands to be executed by recipes. +# This is a requirement for 'setup-envtest.sh' in the test target. +# Options are set to exit when a recipe line exits non-zero or a piped command fails. +SHELL = /usr/bin/env bash -o pipefail +.SHELLFLAGS = -ec + +# crd-ref-docs version +CRD_REF_VERSION := v0.0.9 + +.PHONY: all +all: build + +##@ General + +# The help target prints out all targets with their descriptions organized +# beneath their categories. The categories are represented by '##@' and the +# target descriptions by '##'. The awk commands is responsible for reading the +# entire set of makefiles included in this invocation, looking for lines of the +# file as xyz: ## something, and then pretty-format the target and help. Then, +# if there's a line with ##@ something, that gets pretty-printed as a category. +# More info on the usage of ANSI control characters for terminal formatting: +# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters +# More info on the awk command: +# http://linuxcommand.org/lc3_adv_awk.php + +.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) + +##@ Development + +.PHONY: manifests +manifests: kustomize controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. + $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases + +.PHONY: generate +generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. + $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." + +.PHONY: fmt +fmt: ## Run go fmt against code. + go fmt ./... + +.PHONY: vet +vet: ## Run go vet against code. + go vet ./... + +.PHONY: test +test: manifests generate fmt vet envtest ## Run tests. + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./... -coverprofile cover.out + +.PHONY: lint +lint: ## Run various linters + golangci-lint run + +##@ Build + +.PHONY: build +build: manifests generate fmt vet ## Build manager binary. + go build -o bin/manager main.go + +.PHONY: run +run: manifests generate fmt vet ## Run a controller from your host. + ENABLE_WEBHOOKS=false go run ./main.go + +.PHONY: docker-build +docker-build: ## Build docker image with the manager. + docker build -t ${IMG} . + +.PHONY: docker-push +docker-push: ## Push docker image with the manager. + docker push ${IMG} + +synk: ## Snyk dependency check + wget https://golang.org/dl/go1.17.5.linux-amd64.tar.gz + tar -C /usr/local -xzf go1.17.5.linux-amd64.tar.gz + export PATH=${PATH}:/usr/local/go/bin + mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2 + echo ${PATH} + ls -la /usr/local/go/bin + /usr/local/go/bin/go version + eval $${SNYK_INTEGRATION} auto --file=go.mod + +# PLATFORMS defines the target platforms for the manager image be build to provide support to multiple +# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to: +# - able to use docker buildx . More info: https://docs.docker.com/build/buildx/ +# - have enable BuildKit, More info: https://docs.docker.com/develop/develop-images/build_enhancements/ +# - be able to push the image for your registry (i.e. if you do not inform a valid value via IMG=> than the export will fail) +# To properly provided solutions that supports more than one platform you should use this option. +PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le +.PHONY: docker-buildx +docker-buildx: test ## Build and push docker image for the manager for cross-platform support + # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile + sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross + - docker buildx create --name project-v3-builder + docker buildx use project-v3-builder + - docker buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross + - docker buildx rm project-v3-builder + rm Dockerfile.cross + +##@ Deployment + +ifndef ignore-not-found + ignore-not-found = false +endif + +.PHONY: install +install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. + $(KUSTOMIZE) build config/crd | kubectl apply -f - + +.PHONY: uninstall +uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. + $(KUSTOMIZE) build config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f - + +.PHONY: deploy +deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. + cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} + $(KUSTOMIZE) build config/default | kubectl apply -f - + +.PHONY: undeploy +undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. + $(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f - + +##@ Build Dependencies + +## Location to install dependencies to +LOCALBIN ?= $(shell pwd)/bin +$(LOCALBIN): + mkdir -p $(LOCALBIN) + +## Tool Binaries +KUSTOMIZE ?= $(LOCALBIN)/kustomize +CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen +ENVTEST ?= $(LOCALBIN)/setup-envtest + +## Tool Versions +KUSTOMIZE_VERSION ?= v3.8.7 +CONTROLLER_TOOLS_VERSION ?= v0.9.2 + +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) + @if test -x $(LOCALBIN)/kustomize && ! $(LOCALBIN)/kustomize version | grep -q $(KUSTOMIZE_VERSION); then \ + echo "$(LOCALBIN)/kustomize version is not expected $(KUSTOMIZE_VERSION). Removing it before installing."; \ + rm -rf $(LOCALBIN)/kustomize; \ + fi + test -s $(LOCALBIN)/kustomize || { curl -Ss $(KUSTOMIZE_INSTALL_SCRIPT) | bash -s -- $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN); } + +.PHONY: controller-gen +controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. +$(CONTROLLER_GEN): $(LOCALBIN) + test -s $(LOCALBIN)/controller-gen || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION) + +.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 + +.PHONY: bundle +bundle: manifests kustomize ## Generate bundle manifests and metadata, then validate generated files. + operator-sdk generate kustomize manifests -q + cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) + $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle $(BUNDLE_GEN_FLAGS) + operator-sdk bundle validate ./bundle + +.PHONY: bundle-build +bundle-build: ## Build the bundle image. + docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) . + +.PHONY: bundle-push +bundle-push: ## Push the bundle image. + $(MAKE) docker-push IMG=$(BUNDLE_IMG) + +.PHONY: opm +OPM = ./bin/opm +opm: ## Download opm locally if necessary. +ifeq (,$(wildcard $(OPM))) +ifeq (,$(shell which opm 2>/dev/null)) + @{ \ + set -e ;\ + mkdir -p $(dir $(OPM)) ;\ + OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ + curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v1.23.0/$${OS}-$${ARCH}-opm ;\ + chmod +x $(OPM) ;\ + } +else +OPM = $(shell which opm) +endif +endif + +# A comma-separated list of bundle images (e.g. make catalog-build BUNDLE_IMGS=example.com/operator-bundle:v0.1.0,example.com/operator-bundle:v0.2.0). +# These images MUST exist in a registry and be pull-able. +BUNDLE_IMGS ?= $(BUNDLE_IMG) + +# The image tag given to the resulting catalog image (e.g. make catalog-build CATALOG_IMG=example.com/operator-catalog:v0.2.0). +CATALOG_IMG ?= $(IMAGE_TAG_BASE)-catalog:v$(VERSION) + +# Set CATALOG_BASE_IMG to an existing catalog image tag to add $BUNDLE_IMGS to that image. +ifneq ($(origin CATALOG_BASE_IMG), undefined) +FROM_INDEX_OPT := --from-index $(CATALOG_BASE_IMG) +endif + +# Build a catalog image by adding bundle images to an empty catalog using the operator package manager tool, 'opm'. +# This recipe invokes 'opm' in 'semver' bundle add mode. For more information on add modes, see: +# https://github.com/operator-framework/community-operators/blob/7f1438c/docs/packaging-operator.md#updating-your-existing-operator +.PHONY: catalog-build +catalog-build: opm ## Build a catalog image. + $(OPM) index add --container-tool docker --mode semver --tag $(CATALOG_IMG) --bundles $(BUNDLE_IMGS) $(FROM_INDEX_OPT) + +# Push the catalog image. +.PHONY: catalog-push +catalog-push: ## Push a catalog image. + $(MAKE) docker-push IMG=$(CATALOG_IMG) + +# run kuttl tests on local configured *running* kind cluster +.PHONY: kuttl +kuttl: ## run kuttl tests on local configured *running* kind cluster + kuttl test --start-kind=false --config e2e-testing/kuttl/kuttl-test.yaml -n default ./e2e-testing/kuttl + +##@ Other Helpers + +.PHONY: prepare +prepare: install-crd-ref-docs ## Installs tools that are NOT required for building the app but are used for other tasks + +.PHONY: pre-merge +pre-merge: crd-ref helm-crd ## Tools to run before merging to main (to be used externally) + +.PHONY: install-crd-ref-docs +install-crd-ref-docs: ## Install the crd-ref-docs tool + GOBIN=$(LOCALBIN) go install github.com/elastic/crd-ref-docs@$(CRD_REF_VERSION) + +.PHONY: crd-ref +crd-ref: ## Generate CRD reference documentation + $(LOCALBIN)/crd-ref-docs \ + --config crd-docs/config.yaml \ + --source-path api/v1alpha1 \ + --templates-dir crd-docs/templates/markdown \ + --renderer markdown \ + --output-path crd-docs/crd.md + +.PHONY: helm-crd +helm-crd: manifests ## Generate helm CRD + $(KUSTOMIZE) build config/crd > helm/dynamic-environment/templates/crd.yaml \ No newline at end of file diff --git a/PROJECT b/PROJECT new file mode 100644 index 0000000..fd4c12e --- /dev/null +++ b/PROJECT @@ -0,0 +1,23 @@ +domain: com +layout: +- go.kubebuilder.io/v3 +plugins: + manifests.sdk.operatorframework.io/v2: {} + scorecard.sdk.operatorframework.io/v2: {} +projectName: dynamic-environment-operator +repo: github.com/riskified/dynamic-environment +resources: +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: com + group: riskified + kind: DynamicEnv + path: github.com/riskified/dynamic-environment/api/v1alpha1 + version: v1alpha1 + webhooks: + defaulting: true + validation: true + webhookVersion: v1 +version: "3" diff --git a/README.md b/README.md index e2f0b2f..2eefb78 100644 --- a/README.md +++ b/README.md @@ -1 +1,246 @@ -# dynamic-environment \ No newline at end of file +# Dynamic Environment Controller + +k8s operator which supports testing on multi-test workflows environment in a +transparency way. This operator will listen to DynamicEnv a new Riskified custom +resource which there we will configure the services that we want to create from +them a subset with new version and apply all the requirements for routing the +requests to match subsets. + +## Setting Development Environment + +This section describes how to create a working environment for developing _Dynamic Environment_. +While it's not mandatory to follow these instructions or use the same tools, it's highly recommended +to keep the versions and some install procedures (e.g, _Istio_ installation into kubernetes) to make +sure everything passes the tests correctly. + +### Tool Versions + +In order to have consistent results across development, tests, etc. we use a specific version of +each of the tools. The repository contains [asdf][] configuration for each of the tools we use +during development/testing. The tools and versions required are configured in the `.tool-versions` +file at the top of the repository. Below is the procedure to install the required tool via _asdf_: + +After [installing asdf][asdf-inst] install the required plugins (it's safe to run this command even +if some tools are already installed): + +```shell +for tool in `cat .tool-versions |cut -d" " -f1`; do asdf plugin add $tool; done +``` + +Then run the following command in the root of the repository (again, it's safe to run this even if +some tools are already installed): + +```shell +asdf install +``` + +You should now have the right versions of the tools installed. As long as you installed _asdf_ +correctly, and you're running from within the repository root you'll be using the right versions. + +Here are other tools that while not required can make life easier: + +* `kubectl`: Make sure it's a version that matches our cluster version according to the [version + skew policy][skew] (if you don't want to mess with multiple versions, and you're using minikube + you can use `minikube kubectl` instead). +* `kubectx` and `kubens`: Will help you set the context and default namespace easily. +* `k9s`: A great tool for interacting with k8s from the command line. Check the [website][k9s] for + usage. + +## Prepare task + +Some of the development requirements are installed via the `Makefile`'s `prepare` task. Run it to +install requirements: + +```shell +make prepare +``` + +### Kubernetes Setup for Development and Testing + +After we have the tools we need to install two kubernetes clusters. One for Development and one for +running tests locally. Below is an optional suggestion for installing and configuring these +clusters. Feel free to replace all the components except _KInd_ (so your test setup will be as +similar to the _CI_ as possible). + +#### Development Cluster + +We will use [minikube][] as our development cluster. Feel free to use whichever cluster suits you +best, but try to preserve the kubernetes version. + +After installing minikube we need to start a new +cluster. You can play a little with the _memory_ and the _cpus_ but since we're going to run +resources on it, it's better to give it more resources than the default: + +```shell +minikube start --kubernetes-version v1.26.3 --memory 8g --cpus 4 +``` + +For the rest of the command make sure you're _kubectl_ is operating on the minikube context: + +```shell +kubectx -c # should return 'minikube' +``` + +Next, install Istio: + +```shell +istioctl install --set profile=demo -y +``` + +If you want some basic building blocks to play with you can install a slightly modified version of +the _Istio_ _BookInfo_ application, so you'll have some application to experiment on. This command +will install _BookInfo_ environment on a newly created _services_ namespace. It will also apply +required labels (note that if you're not using these resources you have to apply the +`istio-injection=enabled` label on any namespace you want istio to operate on)`: + +```shell +kubectl apply -n services -f dev-resources + +# Alternatively, if you don't want to install these manifests you have to apply an istio label +# on each namespace you want istio to operate on +kubectl label namespace istio-injection=enabled +``` + +_Note: On Mac, minikube might start to use a lot of resources if left running for a couple of days. +It's better to stop minikube when you're done working for the day_. + +### Tests Cluster + +Some tests should run against a dedicated cluster. It is recommended that you'll have a +default named _Kind_ cluster for testing. + +A prerequisite for running _Kind_ is a docker service. Make sure you have one installed (e.g, +_Rancher Desktop_). Assuming you followed the instructions above you should have the right version +of _Kind_ installed. + +Create the default _Kind_ cluster: + +```shell +./e2e-testing/scripts/setup.sh create +``` + +Install required dependencies and controller docker image: + +```shell +./e2e-testing/scripts/setup.sh deps +``` + +` + +**From now on make sure your k8s context points to the test cluster.** + +Deploy the controller to the cluster (this step should be repeated every time you update the +controller code): + +```shell +./e2e-testing/scripts/setup.sh deploy +```` + +If you want to clean up the test cluster you can run one of the following commands: + +```shell +# Undeploy the controller and dependencies +./e2e-testing/scripts/setup.sh down + +# Completely delete the cluster +./e2e-testing/scripts/setup.sh delete +``` + +## Running the Operator Locally for Development + +The development cluster is meant to be used a target for out development process. As such it's +missing some components: + +* Validating webhook. +* Metrics + +To run our development operator: + +```shell +make install run +``` + +On another shell you can deploy a sample _dynamic-environment_ resource to see the effect: + +```shell +kubectl create -f config/samples/riskified_v1alpha1_dynamicenv.yaml +``` + +This will install a _dynamic-environment_ resource in the _default_ namespace. Check the status and +the various resources on the _services'_ namespace. + +## Writing Tests + +There are two types of tests for this controller: + +* [Unit Tests](#unit-tests) +* [End-to-End Tests](#e2e-tests) + +### Unit Tests + +These are the regular _go_ tests. + +### E2E Tests + +These are tests against a real cluster ([Kind][kind]). We write and run the tests using [kuttl][]. +Each _Kuttl_ test should be located each in its own directory under `./e2e-testing/kuttl`. Make +sure you follow the rules for creating tests: + +* Your first test manifest should create a new namespace (preferably named like your test directory) + and all test resources (deployments, destination rules, virtual services, etc.) should be deployed + to that namespace. If required create more than one namespace. This will prevent collisions + between test cases. +* Every namespace added (per the previous step) should contain an _Istio_ namespace: + ```yaml + apiVersion: v1 + kind: Namespace + metadata: + name: + labels: + istio-injection: enabled + ``` +* The tested _dynamic-environment_ yaml could be deployed to the default namespace (with resources + pointing to the namespaces above). +* If there are any leftover namespaces in the cluster (should not happen - this problem was fixed in + _Kuttl_) remove then manually. +* Add a `Readme.md` file in each test directory describing the test target (and optionally pointing + to the relevant ticket). + +You can run the specific test you're working on (`` is the directory of the test): + +```shell +kuttl test --start-kind=false -n default --timeout 20 ./e2e-testing/kuttl --test +``` + +## Running Tests + +* Run all unit tests with: `make test` +* Run all E2E tests with: `make kuttl` + +## Running Linters + +While we run default linters in during out _test_ stage, it's advisable to occasionally run other +linters. These should not break the build (and not included in our configuration) and might contain +a lot of false positives, however you should occasionally run them to manually search for errors: + +```shell +# get a list of linters (at the end of the output there are some convenient presets) +golangci-lint help linters + +# run specific presets (e.g. complexity and error) +golangci-lint run -p complexity,error +``` + +[asdf]: https://asdf-vm.com/ + +[asdf-inst]: https://asdf-vm.com/guide/getting-started.html + +[kind]: https://kind.sigs.k8s.io/ + +[kuttl]: https://kuttl.dev/ + +[minikube]: https://minikube.sigs.k8s.io/ + +[skew]: https://kubernetes.io/releases/version-skew-policy/ + +[k9s]: https://k9scli.io/ diff --git a/api/v1alpha1/condition_types.go b/api/v1alpha1/condition_types.go new file mode 100644 index 0000000..ed5e5ac --- /dev/null +++ b/api/v1alpha1/condition_types.go @@ -0,0 +1,33 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// ConditionType specifies the available conditions for the resource +type ConditionType string + +const ( + // ConditionReady specifies that the resource is ready. + // For long-running resources. + ConditionReady ConditionType = "Ready" + // ConditionActive specifies that the resource has finished. + // For resource which run to completion. + ConditionActive ConditionType = "Active" +) + +// Condition describes the state of a DynamicEnv at a certain point. +type Condition struct { + // Type of condition + Type ConditionType `json:"type"` + // Status of the condition, one of True, False, Unknown. + Status metav1.ConditionStatus `json:"status"` + // The last time this condition was updated. + LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty"` + // Last time the condition transitioned from one status to another. + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"` + // The reason for the condition's last transition. + Reason string `json:"reason,omitempty"` + // A human-readable message indicating details about the transition. + Message string `json:"message,omitempty"` +} diff --git a/api/v1alpha1/dynamicenv_types.go b/api/v1alpha1/dynamicenv_types.go new file mode 100644 index 0000000..3b0fc79 --- /dev/null +++ b/api/v1alpha1/dynamicenv_types.go @@ -0,0 +1,385 @@ +/* +Copyright 2021. + +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 ( + "encoding/json" + "fmt" + "reflect" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type LifeCycleStatus string +type GlobalReadyStatus string +type SubsetOrConsumer int + +const ( + // Various life cycle statuses + Unknown LifeCycleStatus = "unknown" // The default status + Initializing LifeCycleStatus = "initializing" + Running LifeCycleStatus = "running" + Failed LifeCycleStatus = "failed" + Missing LifeCycleStatus = "missing" + Updating LifeCycleStatus = "updating" + IgnoredMissingDR LifeCycleStatus = "ignored-missing-destination-rule" + IgnoredMissingVS LifeCycleStatus = "ignored-missing-virtual-service" + + // Statuses for the global readiness (argocd ready check) + Degraded GlobalReadyStatus = "degraded" + Ready GlobalReadyStatus = "ready" + Processing GlobalReadyStatus = "processing" + + // Whether it's consumer or subset. + SUBSET SubsetOrConsumer = iota + CONSUMER SubsetOrConsumer = iota +) + +func (s LifeCycleStatus) String() string { + defaultResult := string(Unknown) + switch s { + case Unknown: + return defaultResult + case Initializing: + return string(Initializing) + case Running: + return string(Running) + case Failed: + return string(Failed) + case Missing: + return string(Missing) + case Updating: + return string(Updating) + case IgnoredMissingDR: + return string(IgnoredMissingDR) + case IgnoredMissingVS: + return string(IgnoredMissingVS) + } + return defaultResult +} + +func ParseLifeCycleStatus(s string) LifeCycleStatus { + switch s { + case string(Initializing): + return Initializing + case string(Running): + return Running + case string(Failed): + return Failed + case string(Missing): + return Missing + case string(Updating): + return Updating + case string(IgnoredMissingDR): + return IgnoredMissingDR + case string(IgnoredMissingVS): + return IgnoredMissingVS + } + return Unknown +} + +func (s LifeCycleStatus) MarshalJSON() ([]byte, error) { + return json.Marshal(s.String()) +} + +func (s *LifeCycleStatus) UnmarshalJSON(data []byte) error { + var status string + if err := json.Unmarshal(data, &status); err != nil { + return err + } + *s = ParseLifeCycleStatus(status) + return nil +} + +func (s *LifeCycleStatus) IsFailedStatus() bool { + return *s == Missing || *s == Failed +} + +func (s *GlobalReadyStatus) String() string { + switch *s { + case Degraded: + return string(Degraded) + case Processing: + return string(Processing) + case Ready: + return string(Ready) + } + return string(Processing) +} + +func ParseGlobalReadyStatus(s string) GlobalReadyStatus { + switch s { + case "degraded": + return Degraded + case "ready": + return Ready + } + return Processing +} + +func (s GlobalReadyStatus) MarshalJSON() ([]byte, error) { + return json.Marshal(s.String()) +} + +func (s *GlobalReadyStatus) UnmarshalJSON(data []byte) error { + var status string + if err := json.Unmarshal(data, &status); err != nil { + return err + } + *s = ParseGlobalReadyStatus(status) + return nil +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:resource:path=dynamicenvs,scope=Namespaced,shortName=de +// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.state",description="Status of the DynamicEnv" +// +kubebuilder:printcolumn:name="Desired",type="integer",JSONPath=".status.totalCount",description="displays desired subsets and consumers count" +// +kubebuilder:printcolumn:name="Current",type="integer",JSONPath=".status.totalReady",description="displays how many subsets and consumers are available" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" + +// DynamicEnv is the Schema for the dynamicenvs API +type DynamicEnv struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec DynamicEnvSpec `json:"spec,omitempty"` + Status DynamicEnvStatus `json:"status,omitempty"` +} + +// DynamicEnvSpec defines the desired state of DynamicEnv +type DynamicEnvSpec struct { + // A list of matchers (partly corresponds to IstioMatch). Each match will have a rule of its + // own (merged with existing rules) ordered by their order here. + IstioMatches []IstioMatch `json:"istioMatches"` + + // Who should participate in the given dynamic environment + Subsets []Subset `json:"subsets"` + + // Consumers are like subsets but for deployments that do not open a service but connect to external resources for + // their work (e.g, offline workers). They are equivalent to subsets in the sense that they launch overriding + // deployments with custom image and/or settings. However, since they are only consumers no virtual service or + // destination route will be pointing to them. + Consumers []Subset `json:"consumers,omitempty"` +} + +// specifies a set of criterion to be met in order for the rule to be applied to the HTTP request +// This field is immutable after creation. +type IstioMatch struct { + // Header values are case-sensitive and formatted as follows:
+ // - `exact: "value"` for exact string match
+ // - `prefix: "value"` for prefix-based match
+ // - `regex: "value"` for RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax). + Headers map[string]StringMatch `json:"headers,omitempty"` + + // One or more labels that constrain the applicability of a rule to source (client) workloads + // with the given labels. + SourceLabels map[string]string `json:"sourceLabels,omitempty"` +} + +// Describes how to match a given string in HTTP headers. Match is case-sensitive. +// one and only one of the fields needs to be defined (oneof) +type StringMatch struct { + Exact string `json:"exact,omitempty"` + Prefix string `json:"prefix,omitempty"` + Regex string `json:"regex,omitempty"` +} + +// Subsets defines how to generate subsets from existing Deployments +type Subset struct { + // Deployment name (without namespace) + Name string `json:"name"` + + // Namespace where the deployment is deployed + Namespace string `json:"namespace"` + + // Labels to add to the pods of the deployment launched by this subset. Could be used in + // conjunction with 'SourceLabels' in the `IstioMatches`. + PodLabels map[string]string `json:"podLabels,omitempty"` + + // Number of deployment replicas. Default is 1. Note: 0 is *invalid*. + Replicas *int32 `json:"replicas,omitempty"` + + // A list of container overrides (at least one of Containers or InitContainers must not be empty) + Containers []ContainerOverrides `json:"containers,omitempty"` + + // A list of init container overrides (at least one of Containers or InitContainers must not be empty) + InitContainers []ContainerOverrides `json:"initContainers,omitempty"` + + // Default version for this subset (if different then the global default version). This is the + // version that will get the default route. + DefaultVersion string `json:"defaultVersion,omitempty"` +} + +// Defines the details of the container on which changes need to be made +// and the relevant overrides +type ContainerOverrides struct { + // Container name to override in multiple containers' environment. If not + // specified we will use the first container. + ContainerName string `json:"containerName"` + + // Docker image name overridden to the desired subset + // The Docker image found in the original deployment is used if this is not provided. + // +optional + Image string `json:"image,omitempty"` + + // Entrypoint array overridden to the desired subset + // The docker image's ENTRYPOINT is used if this is not provided. + // +optional + Command []string `json:"command,omitempty"` + + // Additional environment variable to the given deployment + // +optional + Env []v1.EnvVar `json:"env,omitempty"` +} + +// DynamicEnvStatus defines the observed state of DynamicEnv +type DynamicEnvStatus struct { + // Represents the latest available observations of a deployment's current state. + Conditions []Condition `json:"conditions,omitempty"` + SubsetsStatus map[string]SubsetStatus `json:"subsetsStatus"` + ConsumersStatus map[string]ConsumerStatus `json:"consumersStatus,omitempty"` + State GlobalReadyStatus `json:"state,omitempty"` + // desired subsets and consumers count + TotalCount int `json:"totalCount,omitempty"` + // number of available subsets and consumers + TotalReady int `json:"totalReady"` +} + +//+kubebuilder:object:root=true + +// DynamicEnvList contains a list of DynamicEnv +type DynamicEnvList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []DynamicEnv `json:"items"` +} + +// SubsetStatus Contains aggregation of all resources status connected to set subset. +type SubsetStatus struct { + // Status of the deployment that belongs to the subset + Deployment ResourceStatus `json:"deployment,omitempty"` + // Status of the destination-rule that belongs to the subset + DestinationRules []ResourceStatus `json:"destinationRules,omitempty"` + // Status of the virtual-service that belongs to the subset + VirtualServices []ResourceStatus `json:"virtualServices,omitempty"` + // A list of global errors related to subset resources + Errors *SubsetErrors `json:"subsetErrors,omitempty"` + // Hash of the current subset - for internal use + Hash int64 `json:"hash,omitempty"` +} + +// SubsetErrors contains all global errors related to set subset. +type SubsetErrors struct { + // Subset's deployment global errors. + Deployment []StatusError `json:"deployment,omitempty"` + // Subset's destination-rule global errors. + DestinationRule []StatusError `json:"destinationRule,omitempty"` + // Subset's virtual-services global errors. + VirtualServices []StatusError `json:"virtualServices,omitempty"` + // Errors related to subset but not to any of the launched resources + Subset []StatusError `json:"subset,omitempty"` +} + +type ConsumerStatus struct { + ResourceStatus `json:",inline"` + // Hash of the current consumer - for internal use + Hash int64 `json:"hash,omitempty"` + // List of errors related to the consumer + Errors []StatusError `json:"errors,omitempty"` +} + +// ResourceStatus shows the status of each item created/edited by DynamicEnv +type ResourceStatus struct { + // The name of the resource + Name string `json:"name"` + // The namespace where the resource is created + Namespace string `json:"namespace"` + // The life cycle status of the resource + Status LifeCycleStatus `json:"status"` +} + +func (rs ResourceStatus) IsEqual(other ResourceStatus) bool { + return rs.Name == other.Name && rs.Namespace == other.Namespace && rs.Status == other.Status +} + +// StatusError shows an error we want to display in the status with the last time it happened. This +// *does not* have to be the only time it happened. The idea is that a list of errors should only +// contain single occurrence of an error (just the last). +type StatusError struct { + // The error message + Error string `json:"error"` + // THe last occurrence of the error + LastOccurrence metav1.Time `json:"lastOccurrence"` +} + +// SubsetMessages contains a list of messages (errors) that occurred during Reconcile loop. At the +// end of each loop these messages should be synced to the matching subset status. +type SubsetMessages struct { + Deployment []string + DestinationRule []string + VirtualService []string + GlobalErrors []string +} + +func NewStatusError(error string) StatusError { + return StatusError{ + Error: error, + LastOccurrence: metav1.Now(), + } +} + +func (co ContainerOverrides) IsEmpty() bool { + return reflect.DeepEqual(co, ContainerOverrides{}) +} + +func (se *StatusError) SameError(other StatusError) bool { + return se.Error == other.Error +} + +func (se *StatusError) UpdateTime() { + se.LastOccurrence = metav1.Now() +} + +func init() { + SchemeBuilder.Register(&DynamicEnv{}, &DynamicEnvList{}) +} + +func (rls SubsetMessages) AppendDeploymentMsg(format string, a ...interface{}) SubsetMessages { + msg := fmt.Sprintf(format, a...) + rls.Deployment = append(rls.Deployment, msg) + return rls +} + +func (rls SubsetMessages) AppendDestinationRuleMsg(format string, a ...interface{}) SubsetMessages { + msg := fmt.Sprintf(format, a...) + rls.DestinationRule = append(rls.DestinationRule, msg) + return rls +} + +func (rls SubsetMessages) AppendVirtualServiceMsg(format string, a ...interface{}) SubsetMessages { + msg := fmt.Sprintf(format, a...) + rls.VirtualService = append(rls.VirtualService, msg) + return rls +} + +func (rls SubsetMessages) AppendGlobalMsg(format string, a ...interface{}) SubsetMessages { + msg := fmt.Sprintf(format, a...) + rls.GlobalErrors = append(rls.GlobalErrors, msg) + return rls +} diff --git a/api/v1alpha1/dynamicenv_types_test.go b/api/v1alpha1/dynamicenv_types_test.go new file mode 100644 index 0000000..669b086 --- /dev/null +++ b/api/v1alpha1/dynamicenv_types_test.go @@ -0,0 +1,115 @@ +package v1alpha1_test + +import ( + "encoding/json" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + riskifiedv1alpha1 "github.com/riskified/dynamic-environment/api/v1alpha1" +) + +var _ = Describe("Life Cycle Status", func() { + DescribeTable( + "string representation of life cycle status", + func(status riskifiedv1alpha1.LifeCycleStatus, s string) { + rs := riskifiedv1alpha1.ResourceStatus{Status: status} + Expect(rs.Status.String()).To(Equal(s)) + }, + Entry("unknown status", riskifiedv1alpha1.Unknown, "unknown"), + Entry("initializing status", riskifiedv1alpha1.Initializing, "initializing"), + Entry("running status", riskifiedv1alpha1.Running, "running"), + Entry("failed status", riskifiedv1alpha1.Failed, "failed"), + Entry("missing (not running)", riskifiedv1alpha1.Missing, "missing"), + Entry("updating status", riskifiedv1alpha1.Updating, "updating"), + Entry("ignored missing destination rule", riskifiedv1alpha1.IgnoredMissingDR, "ignored-missing-destination-rule"), + Entry("ignored missing virtual service", riskifiedv1alpha1.IgnoredMissingVS, "ignored-missing-virtual-service"), + ) + + It("invalid status produces unknown", func() { + rs := riskifiedv1alpha1.ResourceStatus{Status: "invalid"} + Expect(rs.Status.String()).To(Equal("unknown")) + }) + + Context("Converting to/from Json", func() { + It("converts invalid status to unknown when converting to json", func() { + rs := riskifiedv1alpha1.ResourceStatus{ + Name: "name", + Namespace: "namespace", + Status: "invalid", + } + result, err := json.Marshal(rs) + Expect(err).To(BeNil()) + Expect(string(result)).To(ContainSubstring("unknown")) + }) + + It("converts invalid status to unknown when converting to json", func() { + data := []byte(`{"name":"name","namespace":"namespace","status":"invalid"}`) + result := &riskifiedv1alpha1.ResourceStatus{} + err := json.Unmarshal(data, result) + Expect(err).To(BeNil()) + Expect(result.Status).To(Equal(riskifiedv1alpha1.Unknown)) + }) + }) +}) + +var _ = Describe("Global Ready Status", func() { + + It("Default status is processing", func() { + st := riskifiedv1alpha1.DynamicEnvStatus{ + State: "invalid", + } + Expect(st.State.String()).To(Equal("processing")) + }) + + Context("converting to/from JSON", func() { + It("converts invalid state to processing when converting to JSON", func() { + st := &riskifiedv1alpha1.DynamicEnvStatus{ + State: "invalid", + } + result, err := json.Marshal(st) + Expect(err).To(BeNil()) + Expect(result).To(ContainSubstring("processing")) + }) + + It("converts invalid state to processing when converting from JSON", func() { + data := []byte(`{"state":"processing"}`) + result := &riskifiedv1alpha1.DynamicEnvStatus{} + err := json.Unmarshal(data, result) + Expect(err).To(BeNil()) + Expect(result.State).To(Equal(riskifiedv1alpha1.Processing)) + }) + + DescribeTable( + "converts valid values correctly", + func(status riskifiedv1alpha1.GlobalReadyStatus, s string) { + st := &riskifiedv1alpha1.DynamicEnvStatus{ + State: status, + } + data, err := json.Marshal(st) + Expect(err).To(BeNil()) + Expect(data).To(ContainSubstring(s)) + st2 := &riskifiedv1alpha1.DynamicEnvStatus{} + err = json.Unmarshal(data, st2) + Expect(err).To(BeNil()) + Expect(st2.State).To(Equal(status)) + }, + Entry("degraded status", riskifiedv1alpha1.Degraded, "degraded"), + Entry("processing status", riskifiedv1alpha1.Processing, "processing"), + Entry("ready status", riskifiedv1alpha1.Ready, "ready"), + ) + }) + + DescribeTable( + "IsFailedStatus", + func(s riskifiedv1alpha1.LifeCycleStatus, expected bool) { + Expect(s.IsFailedStatus()).To(Equal(expected)) + }, + Entry("initializing is not failed", riskifiedv1alpha1.Initializing, false), + Entry("updating is not failed", riskifiedv1alpha1.Updating, false), + Entry("running is not failed", riskifiedv1alpha1.Running, false), + Entry("unknown is not failed", riskifiedv1alpha1.Unknown, false), + Entry("ignored missing DR is not failed", riskifiedv1alpha1.IgnoredMissingDR, false), + Entry("ignored missing VS is not failed", riskifiedv1alpha1.IgnoredMissingVS, false), + Entry("missing is failed", riskifiedv1alpha1.Missing, true), + Entry("failed is failed", riskifiedv1alpha1.Failed, true), + ) +}) diff --git a/api/v1alpha1/dynamicenv_webhook.go b/api/v1alpha1/dynamicenv_webhook.go new file mode 100644 index 0000000..4191d98 --- /dev/null +++ b/api/v1alpha1/dynamicenv_webhook.go @@ -0,0 +1,245 @@ +/* +Copyright 2021. + +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 ( + "fmt" + "reflect" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" +) + +// log is for logging in this package. +var dynamicenvlog = logf.Log.WithName("dynamicenv-resource") + +func (de *DynamicEnv) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(de). + Complete() +} + +//+kubebuilder:webhook:path=/validate-riskified-com-v1alpha1-dynamicenv,mutating=false,failurePolicy=fail,sideEffects=None,groups=riskified.com,resources=dynamicenvs,verbs=create;update,versions=v1alpha1,name=vdynamicenv.kb.io,admissionReviewVersions=v1 + +var _ webhook.Validator = &DynamicEnv{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +func (de *DynamicEnv) ValidateCreate() error { + dynamicenvlog.Info("validate create", "name", de.Name) + + if err := de.validateIstioMatchAnyOf(); err != nil { + return err + } + if err := de.validateSubsetsProperties(); err != nil { + return err + } + + return de.validateStringMatchOneOf() +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +func (de *DynamicEnv) ValidateUpdate(old runtime.Object) error { + dynamicenvlog.Info("validate update", "name", de.Name) + + if err := de.validateIstioMatchImmutable(old); err != nil { + return err + } + if err := de.validateSubsetsProperties(); err != nil { + return err + } + return de.validatePartialUpdateSubsets(old) +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type +func (de *DynamicEnv) ValidateDelete() error { + dynamicenvlog.Info("validate delete", "name", de.Name) + + return nil +} + +// validateStringMatchOneOf must validate one and only one schema (oneOf) of StringMatch is defined +func (de *DynamicEnv) validateStringMatchOneOf() error { + var allErrs field.ErrorList + for _, m := range de.Spec.IstioMatches { + for header, match := range m.Headers { + fields := reflect.ValueOf(match) + var alternatives int + for i := 0; i < fields.NumField(); i++ { + if fields.Field(i).Interface() != "" { + alternatives++ + } + } + + if alternatives != 1 { + errDetail := fmt.Sprintf("spec.istioMatch.headers.%s must validate one and only one schema (oneOf). Found %d valid alternatives", header, alternatives) + path := field.NewPath("spec").Child("istioMatch").Child("headers").Child(header) + allErrs = append(allErrs, field.Invalid(path, "header", errDetail)) + } + } + } + + if len(allErrs) != 0 { + return errors.NewInvalid( + schema.GroupKind{Group: "dynamicenv.riskified.com", Kind: "DynamicEnv"}, + de.Name, allErrs) + } + + return nil +} + +// validateIstioMatchAnyOf validates at least one field from IstioMatch is defined +func (de *DynamicEnv) validateIstioMatchAnyOf() error { + errDetail := "empty IstioMatch is invalid for DynamicEnv" + if len(de.Spec.IstioMatches) == 0 { + return field.Invalid(field.NewPath("spec"), de.Spec.IstioMatches, errDetail) + } + for _, match := range de.Spec.IstioMatches { + if len(match.Headers) == 0 && len(match.SourceLabels) == 0 { + return field.Invalid(field.NewPath("spec"), de.Spec.IstioMatches, errDetail) + } + } + + return nil +} + +// Validates certain aspects of the subset. Should be used both on create and update. +func (de *DynamicEnv) validateSubsetsProperties() error { + subsets := append(de.Spec.Subsets, de.Spec.Consumers...) + for _, s := range subsets { + if s.Replicas != nil && *s.Replicas == 0 { + msg := "It's illegal to use 0 replicas!" + return field.Invalid(field.NewPath("spec"), de.Spec, msg) + } + if len(s.Containers) == 0 && len(s.InitContainers) == 0 { + msg := "At least a single container or init-container must be specified" + return field.Invalid(field.NewPath("spec").Key(s.Name), s, msg) + } + if !validateUniqueContainerNames(s.Containers) { + msg := "It seems that not all container names are unique" + return field.Invalid(field.NewPath("spec").Child("Subsets").Key(s.Name).Child("Containers"), s.Containers, msg) + } + if !validateUniqueContainerNames(s.InitContainers) { + msg := "It seems that not all container names are unique" + return field.Invalid(field.NewPath("spec").Child("Subsets").Key(s.Name).Child("initContainers"), s.InitContainers, msg) + } + } + return nil +} + +// validateIstioMatchImmutable validates IstioMatch is immutable after creation. +func (de *DynamicEnv) validateIstioMatchImmutable(old runtime.Object) error { + oldIstioMatch := old.(*DynamicEnv).Spec.IstioMatches + newIstioMatch := de.Spec.IstioMatches + + if !reflect.DeepEqual(newIstioMatch, oldIstioMatch) { + errDetail := "spec.istioMatch field is immutable" + return field.Invalid(field.NewPath("spec"), newIstioMatch, errDetail) + } + + return nil +} + +// validatePartialUpdateSubsets verifies that update only occurs within a subset. The name/namespace +// of the subsets should not be updated (e.g. should not delete or create new subsets). +func (de *DynamicEnv) validatePartialUpdateSubsets(old runtime.Object) error { + oldSubsets := append(old.(*DynamicEnv).Spec.Subsets, old.(*DynamicEnv).Spec.Consumers...) + newSubsets := append(de.Spec.Subsets, de.Spec.Consumers...) + if len(oldSubsets) != len(newSubsets) { + desc := "Unsupported operation: add or remove subset" + return field.Invalid(field.NewPath("spec").Child("Subsets"), newSubsets, desc) + } + for _, subset := range oldSubsets { + if err := findMatchingSubset(subset, newSubsets); err != nil { + return err + } + } + return nil +} + +func findMatchingSubset(old Subset, subsets []Subset) error { + var foundMatching = false + for _, s := range subsets { + if old.Name == s.Name && old.Namespace == s.Namespace { + foundMatching = true + if err := compareContainers(old.Containers, s.Containers); err != nil { + desc := fmt.Sprintf("couldn't find matching container to existing container: %s", err.Error()) + return field.Invalid( + field.NewPath("spec").Child("Subsets").Key(s.Name).Child("Containers"), + s.Containers, desc) + } + if err := compareContainers(old.InitContainers, s.InitContainers); err != nil { + desc := fmt.Sprintf("couldn't find matching container to existing container: %s", err.Error()) + return field.Invalid( + field.NewPath("spec").Child("Subsets").Key(s.Name).Child("initContainers"), + s.InitContainers, desc) + } + if old.DefaultVersion != s.DefaultVersion { + desc := fmt.Sprintf( + "Changing default-version is not supported (modified '%s' to '%s')", old.DefaultVersion, s.DefaultVersion, + ) + return field.Invalid(field.NewPath("spec").Child("Subsets"), subsets, desc) + } + } + } + if !foundMatching { + desc := "Changing name/namespace of subset is forbidden" + return field.Invalid(field.NewPath("spec").Child("Subsets"), subsets, desc) + } + return nil +} + +func compareContainers(oldCOntainers, newContainers []ContainerOverrides) error { + if len(oldCOntainers) != len(newContainers) { + return fmt.Errorf("number of new containers (%d) does not match the number of old containers (%d)", + len(oldCOntainers), len(newContainers)) + } + for _, c := range oldCOntainers { + if err := findMatchingContainer(c, newContainers); err != nil { + return err + } + } + return nil +} + +func findMatchingContainer(oldContainer ContainerOverrides, containers []ContainerOverrides) error { + var foundMatching = false + + for _, c := range containers { + if c.ContainerName == oldContainer.ContainerName { + foundMatching = true + } + } + + if !foundMatching { + return fmt.Errorf("%s", oldContainer.ContainerName) + } + return nil +} + +func validateUniqueContainerNames(containers []ContainerOverrides) bool { + // I really hate go for not providing helpers for doing such things + m := make(map[string]bool) + for _, c := range containers { + m[c.ContainerName] = true + } + return len(m) == len(containers) +} diff --git a/api/v1alpha1/dynamicenv_webhook_test.go b/api/v1alpha1/dynamicenv_webhook_test.go new file mode 100644 index 0000000..2cf5331 --- /dev/null +++ b/api/v1alpha1/dynamicenv_webhook_test.go @@ -0,0 +1,480 @@ +package v1alpha1 + +import ( + "fmt" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "io" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "os" + "sigs.k8s.io/yaml" +) + +var zeroReplicas int32 = 0 + +func mkDynamicEnvFromYamlFile(fileName string) (de DynamicEnv, err error) { + sourceFile, err := os.Open(fileName) + if err != nil { + return de, fmt.Errorf("error opening fixture: %w", err) + } + data, err := io.ReadAll(sourceFile) + if err != nil { + return de, fmt.Errorf("error reading data from file: %w", err) + } + if err := yaml.UnmarshalStrict(data, &de); err != nil { + return de, fmt.Errorf("error strict unmarshaling fixture: %w", err) + } + return de, nil +} + +var _ = Describe("Validating Webhook", func() { + Context("Updating DynamicEnvironment Matchers", func() { + base := DynamicEnv{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-de", + Namespace: "default", + }, + Spec: DynamicEnvSpec{ + IstioMatches: []IstioMatch{ + { + Headers: map[string]StringMatch{ + "name": { + Exact: "my_name", + }, + }, + }, + }, + }, + } + + updated := DynamicEnv{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-de", + Namespace: "default", + }, + Spec: DynamicEnvSpec{ + IstioMatches: []IstioMatch{ + { + Headers: map[string]StringMatch{ + "another-name": { + Exact: "my_name", + }, + }, + }, + }, + }, + } + + It("Does not allow to update matchers", func() { + old := runtime.Object(&base) + err := updated.ValidateUpdate(old) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("field is immutable")) + }) + }) + + Context("Creating new DynamicEnvironment", func() { + + var ( + multiStringMatch1 = DynamicEnv{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-de", + Namespace: "default", + }, + Spec: DynamicEnvSpec{ + IstioMatches: []IstioMatch{ + { + Headers: map[string]StringMatch{ + "name": { + Exact: "my_name", + Prefix: "my", + }, + }, + }, + }, + }, + } + + multiStringMatch2 = DynamicEnv{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-de", + Namespace: "default", + }, + Spec: DynamicEnvSpec{ + IstioMatches: []IstioMatch{ + { + Headers: map[string]StringMatch{ + "name": { + Exact: "my_name", + }, + }, + }, + { // This item should fail + Headers: map[string]StringMatch{ + "name": { + Exact: "my_name", + Regex: "my", + }, + }, + }, + }, + }, + } + ) + + It("StringMatch only allows one of prefix/match/regex", func() { + testCases := []DynamicEnv{ + multiStringMatch1, + multiStringMatch2, + } + + for ind, item := range testCases { + By(fmt.Sprintf("multi stringMatch %d", ind)) + err := item.ValidateCreate() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("oneOf")) + } + }, + ) + + It("Requires at least one of headers or source label match", func() { + noMatchDe := DynamicEnv{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-de", + Namespace: "default", + }, + Spec: DynamicEnvSpec{}, + } + err := noMatchDe.ValidateCreate() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("empty IstioMatch")) + }) + + It("Accepts multiple correct matchers", func() { + de := DynamicEnv{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-de", + Namespace: "default", + }, + Spec: DynamicEnvSpec{ + IstioMatches: []IstioMatch{ + { + Headers: map[string]StringMatch{ + "name": { + Exact: "my_name", + }, + }, + }, + { + SourceLabels: map[string]string{ + "key": "value", + }, + }, + }, + }, + } + + err := de.ValidateCreate() + Expect(err).To(BeNil()) + }) + + It("Accepts subsets with specified number of replicas", func() { + var replicas int32 = 8 + de1 := DynamicEnv{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-de", + Namespace: "default", + }, + Spec: DynamicEnvSpec{ + IstioMatches: []IstioMatch{ + { + Headers: map[string]StringMatch{ + "name": { + Exact: "my_name", + }, + }, + }, + }, + Subsets: []Subset{ + { + Name: "somename", + Namespace: "ns", + Replicas: &replicas, + DefaultVersion: "version", + Containers: []ContainerOverrides{ + { + ContainerName: "a-container", + Image: "an-image:tag", + }, + }, + }, + }, + Consumers: nil, + }, + } + de2 := DynamicEnv{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-de", + Namespace: "default", + }, + Spec: DynamicEnvSpec{ + IstioMatches: []IstioMatch{ + { + Headers: map[string]StringMatch{ + "name": { + Exact: "my_name", + }, + }, + }, + }, + Consumers: []Subset{ + { + Name: "somename", + Namespace: "ns", + Replicas: &replicas, + DefaultVersion: "version", + Containers: []ContainerOverrides{ + { + ContainerName: "a-container", + Image: "an-image:tag", + }, + }, + }, + }, + Subsets: nil, + }, + } + + err1 := de1.ValidateCreate() + Expect(err1).To(BeNil(), "Should accept number of replicas in subsets") + err2 := de2.ValidateCreate() + Expect(err2).To(BeNil(), "Should accept number of replicas in consumers") + }) + + It("Create rejects deployments without containers or init-containers", func() { + de, err := mkDynamicEnvFromYamlFile("fixtures/create-rejects-deployments-without-containers-or-init-containers.yaml") + if err != nil { + Fail(err.Error()) + } + resultError := de.ValidateCreate() + Expect(resultError).To(HaveOccurred()) + Expect(resultError.Error()).To(ContainSubstring("At least a single container or init-container")) + }) + + It("Create rejects multiple container within single subset with conflicting container names", func() { + de, err := mkDynamicEnvFromYamlFile("fixtures/deployment-with-multiple-containers-with-conflicting-names.yaml") + if err != nil { + Fail(err.Error()) + } + resultError := de.ValidateCreate() + Expect(resultError).To(HaveOccurred()) + Expect(resultError.Error()).To(ContainSubstring("names are unique")) + }) + + DescribeTable("Create rejects invalid subset properties", + func(de *DynamicEnv, errMsg string) { + err := de.ValidateCreate() + Expect(err).To(Not(BeNil())) + Expect(err.Error()).To(ContainSubstring(errMsg)) + }, + Entry( + "0 replicas in subset", + &DynamicEnv{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-de", + Namespace: "default", + }, + Spec: DynamicEnvSpec{ + IstioMatches: []IstioMatch{ + { + Headers: map[string]StringMatch{ + "name": { + Exact: "my_name", + }, + }, + }, + }, + Subsets: []Subset{ + { + Name: "somename", + Namespace: "ns", + Replicas: &zeroReplicas, + DefaultVersion: "version", + }, + }, + Consumers: nil, + }, + }, + "0 replicas", + ), + Entry( + "0 replicas in consumer", + &DynamicEnv{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-de", + Namespace: "default", + }, + Spec: DynamicEnvSpec{ + IstioMatches: []IstioMatch{ + { + Headers: map[string]StringMatch{ + "name": { + Exact: "my_name", + }, + }, + }, + }, + Subsets: nil, + Consumers: []Subset{ + { + Name: "somename", + Namespace: "ns", + Replicas: &zeroReplicas, + DefaultVersion: "version", + }, + }, + }, + }, + "0 replicas", + ), + ) + + DescribeTable( + "it rejects empty matchers", + func(match IstioMatch) { + de := DynamicEnv{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-de", + Namespace: "default", + }, + Spec: DynamicEnvSpec{ + IstioMatches: []IstioMatch{ + match, + }, + }, + } + + err := de.ValidateCreate() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Invalid value")) + }, + Entry( + "empty headers", + IstioMatch{ + Headers: map[string]StringMatch{}, + }, + ), + Entry( + "empty source labels", + IstioMatch{ + SourceLabels: map[string]string{}, + }, + ), + ) + }) + + Context("Updating subsets", func() { + DescribeTable( + "Allowed modifications", + func(oldData, currentData string) { + old, err := mkDynamicEnvFromYamlFile(oldData) + if err != nil { + Fail("Error decoding oldData: " + err.Error()) + } + current, err := mkDynamicEnvFromYamlFile(currentData) + if err != nil { + Fail("Error decoding currentData") + } + errorResult := current.ValidateUpdate(runtime.Object(&old)) + Expect(errorResult).To(BeNil()) + }, + Entry( + "Modified image", + "fixtures/allowed-modifications-modified-image-old.yaml", + "fixtures/allowed-modifications-modified-image-new.yaml", + ), + Entry( + "Modified order of subsets", + "fixtures/allowed-modifications-modified-order-of-subsets-old.yaml", + "fixtures/allowed-modifications-modified-order-of-subsets-new.yaml", + ), + Entry( + "Modifying number of replicas", + "fixtures/allowed-modifications-modifying-number-of-replicas-old.yaml", + "fixtures/allowed-modifications-modifying-number-of-replicas-new.yaml", + ), + ) + + DescribeTable( + "Disallowed modifications", + func(oldData, currentData, partialError string) { + old, err := mkDynamicEnvFromYamlFile(oldData) + if err != nil { + Fail("Error decoding oldData: " + err.Error()) + } + current, err := mkDynamicEnvFromYamlFile(currentData) + if err != nil { + Fail("Error decoding currentData") + } + errorResult := current.ValidateUpdate(runtime.Object(&old)) + Expect(errorResult).To(HaveOccurred()) + Expect(errorResult.Error()).To(ContainSubstring(partialError)) + }, + Entry( + "Removing Subset", + "fixtures/disallowed-modifications-removing-subset-old.yaml", + "fixtures/disallowed-modifications-removing-subset-new.yaml", + "add or remove", + ), + Entry( + "Removing Consumer", + "fixtures/disallowed-modifications-removing-consumer-old.yaml", + "fixtures/disallowed-modifications-removing-consumer-new.yaml", + "add or remove", + ), + Entry( + "Adding Subset", + "fixtures/disallowed-modifications-adding-subset-old.yaml", + "fixtures/disallowed-modifications-adding-subset-new.yaml", + "add or remove", + ), + Entry( + "modifying subsets name", + "fixtures/disallowed-modifications-modifying-subset-name-old.yaml", + "fixtures/disallowed-modifications-modifying-subset-name-new.yaml", + "name/namespace of subset", + ), + Entry( + "modifying subsets namespace", + "fixtures/disallowed-modifications-modifying-subsets-namespace-old.yaml", + "fixtures/disallowed-modifications-modifying-subsets-namespace-new.yaml", + "name/namespace of subset", + ), + Entry( + "modifying container name", + "fixtures/disallowed-modifications-modifying-container-name-old.yaml", + "fixtures/disallowed-modifications-modifying-container-name-new.yaml", + "couldn't find matching container to existing container: details", + ), + Entry( + "modified initContainer name", + "fixtures/disallowed-modifications-modifying-init_container-name-old.yaml", + `fixtures/disallowed-modifications-modifying-init_container-name-new.yaml`, + "couldn't find matching container to existing container: details", + ), + Entry( + "modifying default version", + "fixtures/disallowed-modifications-modifying-default-version-old.yaml", + "fixtures/disallowed-modifications-modifying-default-version-new.yaml", + "(modified 'shared' to 'modified')", + ), + Entry( + "Modifying to '0' replicas", + "fixtures/disallowed-modifications-modifying-to-zero-replicas-old.yaml", + "fixtures/disallowed-modifications-modifying-to-zero-replicas-new.yaml", + "0 replicas", + ), + ) + }) +}) diff --git a/api/v1alpha1/fixtures/allowed-modifications-modified-image-new.yaml b/api/v1alpha1/fixtures/allowed-modifications-modified-image-new.yaml new file mode 100644 index 0000000..cf6857a --- /dev/null +++ b/api/v1alpha1/fixtures/allowed-modifications-modified-image-new.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: "riskified.com/v1alpha1" +kind: "DynamicEnv" +metadata: + name: "dynamicenv-vs-with-multiple-services" + namespace: "default" +spec: + istioMatches: + - headers: + end-user: + prefix: "jason" + subsets: + - name: "details" + namespace: "vs-with-multiple-services" + containers: + - containerName: "details" + image: "alternative-image:v1" + env: + - name: "TOPIC_NAME" + value: "test" + consumers: + - name: "details-worker" + namespace: "vs-with-multiple-services" + containers: + - containerName: "details" + image: "docker.io/istio/examples-bookinfo-details-v2:1.16.2" diff --git a/api/v1alpha1/fixtures/allowed-modifications-modified-image-old.yaml b/api/v1alpha1/fixtures/allowed-modifications-modified-image-old.yaml new file mode 100644 index 0000000..c0dad3d --- /dev/null +++ b/api/v1alpha1/fixtures/allowed-modifications-modified-image-old.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: "riskified.com/v1alpha1" +kind: "DynamicEnv" +metadata: + name: "dynamicenv-vs-with-multiple-services" + namespace: "default" +spec: + istioMatches: + - headers: + end-user: + prefix: "jason" + subsets: + - name: "details" + namespace: "vs-with-multiple-services" + containers: + - containerName: "details" + image: "docker.io/istio/examples-bookinfo-details-v2:1.16.2" + env: + - name: "TOPIC_NAME" + value: "test" + consumers: + - name: "details-worker" + namespace: "vs-with-multiple-services" + containers: + - containerName: "details" + image: "alternative-image:v1" diff --git a/api/v1alpha1/fixtures/allowed-modifications-modified-order-of-subsets-new.yaml b/api/v1alpha1/fixtures/allowed-modifications-modified-order-of-subsets-new.yaml new file mode 100644 index 0000000..a63d02e --- /dev/null +++ b/api/v1alpha1/fixtures/allowed-modifications-modified-order-of-subsets-new.yaml @@ -0,0 +1,23 @@ +--- +apiVersion: "riskified.com/v1alpha1" +kind: "DynamicEnv" +metadata: + name: "dynamicenv-vs-with-multiple-services" + namespace: "default" +spec: + istioMatches: + - headers: + end-user: + prefix: "jason" + subsets: + - name: "reviews" + namespace: "vs-with-multiple-services" + containers: + - containerName: "reviews" + image: "docker.io/istio/examples-bookinfo-reviews-v3:1.16.2" + - name: "details" + namespace: "vs-with-multiple-services" + defaultVersion: "shared" + containers: + - containerName: "details" + image: "docker.io/istio/examples-bookinfo-details-v2:1.16.2" diff --git a/api/v1alpha1/fixtures/allowed-modifications-modified-order-of-subsets-old.yaml b/api/v1alpha1/fixtures/allowed-modifications-modified-order-of-subsets-old.yaml new file mode 100644 index 0000000..1f2534f --- /dev/null +++ b/api/v1alpha1/fixtures/allowed-modifications-modified-order-of-subsets-old.yaml @@ -0,0 +1,23 @@ +--- +apiVersion: "riskified.com/v1alpha1" +kind: "DynamicEnv" +metadata: + name: "dynamicenv-vs-with-multiple-services" + namespace: "default" +spec: + istioMatches: + - headers: + end-user: + prefix: "jason" + subsets: + - name: "details" + namespace: "vs-with-multiple-services" + defaultVersion: "shared" + containers: + - containerName: "details" + image: "docker.io/istio/examples-bookinfo-details-v2:1.16.2" + - name: "reviews" + namespace: "vs-with-multiple-services" + containers: + - containerName: "reviews" + image: "docker.io/istio/examples-bookinfo-reviews-v3:1.16.2" diff --git a/api/v1alpha1/fixtures/allowed-modifications-modifying-number-of-replicas-new.yaml b/api/v1alpha1/fixtures/allowed-modifications-modifying-number-of-replicas-new.yaml new file mode 100644 index 0000000..ec492ba --- /dev/null +++ b/api/v1alpha1/fixtures/allowed-modifications-modifying-number-of-replicas-new.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: "riskified.com/v1alpha1" +kind: "DynamicEnv" +metadata: + name: "dynamicenv-vs-with-multiple-services" +spec: + istioMatches: + - headers: + end-user: + prefix: "jason" + subsets: + - name: "details" + namespace: "vs-with-multiple-services" + replicas: 8 + containers: + - containerName: "details" + image: "docker.io/istio/examples-bookinfo-details-v2:1.16.2" diff --git a/api/v1alpha1/fixtures/allowed-modifications-modifying-number-of-replicas-old.yaml b/api/v1alpha1/fixtures/allowed-modifications-modifying-number-of-replicas-old.yaml new file mode 100644 index 0000000..b0c540a --- /dev/null +++ b/api/v1alpha1/fixtures/allowed-modifications-modifying-number-of-replicas-old.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: "riskified.com/v1alpha1" +kind: "DynamicEnv" +metadata: + name: "dynamicenv-vs-with-multiple-services" +spec: + istioMatches: + - headers: + end-user: + prefix: "jason" + subsets: + - name: "details" + namespace: "vs-with-multiple-services" + containers: + - containerName: "details" + image: "docker.io/istio/examples-bookinfo-details-v2:1.16.2" diff --git a/api/v1alpha1/fixtures/create-rejects-deployments-without-containers-or-init-containers.yaml b/api/v1alpha1/fixtures/create-rejects-deployments-without-containers-or-init-containers.yaml new file mode 100644 index 0000000..f15b8e3 --- /dev/null +++ b/api/v1alpha1/fixtures/create-rejects-deployments-without-containers-or-init-containers.yaml @@ -0,0 +1,19 @@ +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-vs-with-multiple-services + namespace: default +spec: + istioMatches: + - headers: + end-user: + prefix: jason + subsets: + - name: details + namespace: vs-with-multiple-services + consumers: + - name: details-worker + namespace: vs-with-multiple-services + containers: + - containerName: details + image: alternative-image:v1 diff --git a/api/v1alpha1/fixtures/deployment-with-multiple-containers-with-conflicting-names.yaml b/api/v1alpha1/fixtures/deployment-with-multiple-containers-with-conflicting-names.yaml new file mode 100644 index 0000000..2df33ba --- /dev/null +++ b/api/v1alpha1/fixtures/deployment-with-multiple-containers-with-conflicting-names.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: "riskified.com/v1alpha1" +kind: "DynamicEnv" +metadata: + name: "dynamicenv-vs-with-multiple-services" + namespace: "default" +spec: + istioMatches: + - headers: + end-user: + prefix: "jason" + subsets: + - name: "details" + namespace: "vs-with-multiple-services" + containers: + - containerName: "details" + image: "docker.io/istio/examples-bookinfo-details-v2:1.16.2" + env: + - name: "TOPIC_NAME" + value: "test" + - containerName: "details" + image: "docker.io/istio/examples-bookinfo-details-v3:1.16.2" diff --git a/api/v1alpha1/fixtures/disallowed-modifications-adding-subset-new.yaml b/api/v1alpha1/fixtures/disallowed-modifications-adding-subset-new.yaml new file mode 100644 index 0000000..8dd8895 --- /dev/null +++ b/api/v1alpha1/fixtures/disallowed-modifications-adding-subset-new.yaml @@ -0,0 +1,21 @@ +--- +apiVersion: "riskified.com/v1alpha1" +kind: "DynamicEnv" +metadata: + name: "dynamicenv-vs-with-multiple-services" +spec: + istioMatches: + - headers: + end-user: + prefix: "jason" + subsets: + - name: "details" + namespace: "vs-with-multiple-services" + containers: + - containerName: "details" + image: "docker.io/istio/examples-bookinfo-details-v2:1.16.2" + - name: "reviews" + namespace: "vs-with-multiple-services" + containers: + - containerName: "reviews" + image: "docker.io/istio/examples-bookinfo-reviews-v3:1.16.2" diff --git a/api/v1alpha1/fixtures/disallowed-modifications-adding-subset-old.yaml b/api/v1alpha1/fixtures/disallowed-modifications-adding-subset-old.yaml new file mode 100644 index 0000000..b0c540a --- /dev/null +++ b/api/v1alpha1/fixtures/disallowed-modifications-adding-subset-old.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: "riskified.com/v1alpha1" +kind: "DynamicEnv" +metadata: + name: "dynamicenv-vs-with-multiple-services" +spec: + istioMatches: + - headers: + end-user: + prefix: "jason" + subsets: + - name: "details" + namespace: "vs-with-multiple-services" + containers: + - containerName: "details" + image: "docker.io/istio/examples-bookinfo-details-v2:1.16.2" diff --git a/api/v1alpha1/fixtures/disallowed-modifications-modifying-container-name-new.yaml b/api/v1alpha1/fixtures/disallowed-modifications-modifying-container-name-new.yaml new file mode 100644 index 0000000..69028af --- /dev/null +++ b/api/v1alpha1/fixtures/disallowed-modifications-modifying-container-name-new.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: "riskified.com/v1alpha1" +kind: "DynamicEnv" +metadata: + name: "some name" +spec: + istioMatches: + - headers: + end-user: + prefix: "jason" + subsets: + - name: "details" + namespace: "vs-with-multiple-services" + containers: + - containerName: "modified" + image: "docker.io/istio/examples-bookinfo-details-v2:1.16.2" diff --git a/api/v1alpha1/fixtures/disallowed-modifications-modifying-container-name-old.yaml b/api/v1alpha1/fixtures/disallowed-modifications-modifying-container-name-old.yaml new file mode 100644 index 0000000..5f2256c --- /dev/null +++ b/api/v1alpha1/fixtures/disallowed-modifications-modifying-container-name-old.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: "riskified.com/v1alpha1" +kind: "DynamicEnv" +metadata: + name: "some name" +spec: + istioMatches: + - headers: + end-user: + prefix: "jason" + subsets: + - name: "details" + namespace: "vs-with-multiple-services" + containers: + - containerName: "details" + image: "docker.io/istio/examples-bookinfo-details-v2:1.16.2" diff --git a/api/v1alpha1/fixtures/disallowed-modifications-modifying-default-version-new.yaml b/api/v1alpha1/fixtures/disallowed-modifications-modifying-default-version-new.yaml new file mode 100644 index 0000000..11b4a42 --- /dev/null +++ b/api/v1alpha1/fixtures/disallowed-modifications-modifying-default-version-new.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: "riskified.com/v1alpha1" +kind: "DynamicEnv" +metadata: + name: "some name" +spec: + istioMatches: + - headers: + end-user: + prefix: "jason" + subsets: + - name: "details" + namespace: "vs-with-multiple-services" + defaultVersion: "modified" + containers: + - containerName: "details" + image: "docker.io/istio/examples-bookinfo-details-v2:1.16.2" diff --git a/api/v1alpha1/fixtures/disallowed-modifications-modifying-default-version-old.yaml b/api/v1alpha1/fixtures/disallowed-modifications-modifying-default-version-old.yaml new file mode 100644 index 0000000..b8b7663 --- /dev/null +++ b/api/v1alpha1/fixtures/disallowed-modifications-modifying-default-version-old.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: "riskified.com/v1alpha1" +kind: "DynamicEnv" +metadata: + name: "some name" +spec: + istioMatches: + - headers: + end-user: + prefix: "jason" + subsets: + - name: "details" + namespace: "vs-with-multiple-services" + defaultVersion: "shared" + containers: + - containerName: "details" + image: "docker.io/istio/examples-bookinfo-details-v2:1.16.2" diff --git a/api/v1alpha1/fixtures/disallowed-modifications-modifying-init_container-name-new.yaml b/api/v1alpha1/fixtures/disallowed-modifications-modifying-init_container-name-new.yaml new file mode 100644 index 0000000..93347dc --- /dev/null +++ b/api/v1alpha1/fixtures/disallowed-modifications-modifying-init_container-name-new.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: "riskified.com/v1alpha1" +kind: "DynamicEnv" +metadata: + name: "dynamicenv-simple-test" +spec: + istioMatches: + - headers: + end-user: + prefix: "jason" + subsets: + - name: "details" + namespace: "simple-test" + initContainers: + - containerName: "modified" + image: "docker.io/istio/examples-bookinfo-details-v2:1.16.3" diff --git a/api/v1alpha1/fixtures/disallowed-modifications-modifying-init_container-name-old.yaml b/api/v1alpha1/fixtures/disallowed-modifications-modifying-init_container-name-old.yaml new file mode 100644 index 0000000..15ac648 --- /dev/null +++ b/api/v1alpha1/fixtures/disallowed-modifications-modifying-init_container-name-old.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: "riskified.com/v1alpha1" +kind: "DynamicEnv" +metadata: + name: "dynamicenv-simple-test" +spec: + istioMatches: + - headers: + end-user: + prefix: "jason" + subsets: + - name: "details" + namespace: "simple-test" + initContainers: + - containerName: "details" + image: "docker.io/istio/examples-bookinfo-details-v2:1.16.2" diff --git a/api/v1alpha1/fixtures/disallowed-modifications-modifying-subset-name-new.yaml b/api/v1alpha1/fixtures/disallowed-modifications-modifying-subset-name-new.yaml new file mode 100644 index 0000000..f0fb51d --- /dev/null +++ b/api/v1alpha1/fixtures/disallowed-modifications-modifying-subset-name-new.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: "riskified.com/v1alpha1" +kind: "DynamicEnv" +metadata: + name: "dynamicenv-vs-with-multiple-services" +spec: + istioMatches: + - headers: + end-user: + prefix: "jason" + subsets: + - name: "details-modified" + namespace: "vs-with-multiple-services" + containers: + - containerName: "details" + image: "docker.io/istio/examples-bookinfo-details-v2:1.16.2" diff --git a/api/v1alpha1/fixtures/disallowed-modifications-modifying-subset-name-old.yaml b/api/v1alpha1/fixtures/disallowed-modifications-modifying-subset-name-old.yaml new file mode 100644 index 0000000..b0c540a --- /dev/null +++ b/api/v1alpha1/fixtures/disallowed-modifications-modifying-subset-name-old.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: "riskified.com/v1alpha1" +kind: "DynamicEnv" +metadata: + name: "dynamicenv-vs-with-multiple-services" +spec: + istioMatches: + - headers: + end-user: + prefix: "jason" + subsets: + - name: "details" + namespace: "vs-with-multiple-services" + containers: + - containerName: "details" + image: "docker.io/istio/examples-bookinfo-details-v2:1.16.2" diff --git a/api/v1alpha1/fixtures/disallowed-modifications-modifying-subsets-namespace-new.yaml b/api/v1alpha1/fixtures/disallowed-modifications-modifying-subsets-namespace-new.yaml new file mode 100644 index 0000000..3c750ff --- /dev/null +++ b/api/v1alpha1/fixtures/disallowed-modifications-modifying-subsets-namespace-new.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: "riskified.com/v1alpha1" +kind: "DynamicEnv" +metadata: + name: "some name" +spec: + istioMatches: + - headers: + end-user: + prefix: "jason" + subsets: + - name: "details" + namespace: "modified-ns" + containers: + - containerName: "details" + image: "docker.io/istio/examples-bookinfo-details-v2:1.16.2" diff --git a/api/v1alpha1/fixtures/disallowed-modifications-modifying-subsets-namespace-old.yaml b/api/v1alpha1/fixtures/disallowed-modifications-modifying-subsets-namespace-old.yaml new file mode 100644 index 0000000..5f2256c --- /dev/null +++ b/api/v1alpha1/fixtures/disallowed-modifications-modifying-subsets-namespace-old.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: "riskified.com/v1alpha1" +kind: "DynamicEnv" +metadata: + name: "some name" +spec: + istioMatches: + - headers: + end-user: + prefix: "jason" + subsets: + - name: "details" + namespace: "vs-with-multiple-services" + containers: + - containerName: "details" + image: "docker.io/istio/examples-bookinfo-details-v2:1.16.2" diff --git a/api/v1alpha1/fixtures/disallowed-modifications-modifying-to-zero-replicas-new.yaml b/api/v1alpha1/fixtures/disallowed-modifications-modifying-to-zero-replicas-new.yaml new file mode 100644 index 0000000..fd4567a --- /dev/null +++ b/api/v1alpha1/fixtures/disallowed-modifications-modifying-to-zero-replicas-new.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: "riskified.com/v1alpha1" +kind: "DynamicEnv" +metadata: + name: "dynamicenv-vs-with-multiple-services" +spec: + istioMatches: + - headers: + end-user: + prefix: "jason" + subsets: + - name: "details" + namespace: "vs-with-multiple-services" + replicas: 0 + containers: + - containerName: "details" + image: "docker.io/istio/examples-bookinfo-details-v2:1.16.2" diff --git a/api/v1alpha1/fixtures/disallowed-modifications-modifying-to-zero-replicas-old.yaml b/api/v1alpha1/fixtures/disallowed-modifications-modifying-to-zero-replicas-old.yaml new file mode 100644 index 0000000..b0c540a --- /dev/null +++ b/api/v1alpha1/fixtures/disallowed-modifications-modifying-to-zero-replicas-old.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: "riskified.com/v1alpha1" +kind: "DynamicEnv" +metadata: + name: "dynamicenv-vs-with-multiple-services" +spec: + istioMatches: + - headers: + end-user: + prefix: "jason" + subsets: + - name: "details" + namespace: "vs-with-multiple-services" + containers: + - containerName: "details" + image: "docker.io/istio/examples-bookinfo-details-v2:1.16.2" diff --git a/api/v1alpha1/fixtures/disallowed-modifications-removing-consumer-new.yaml b/api/v1alpha1/fixtures/disallowed-modifications-removing-consumer-new.yaml new file mode 100644 index 0000000..0a2094c --- /dev/null +++ b/api/v1alpha1/fixtures/disallowed-modifications-removing-consumer-new.yaml @@ -0,0 +1,20 @@ +--- +apiVersion: "riskified.com/v1alpha1" +kind: "DynamicEnv" +metadata: + name: "dynamicenv-vs-with-multiple-services" + namespace: "default" +spec: + istioMatches: + - headers: + end-user: + prefix: "jason" + subsets: + - name: "details" + namespace: "vs-with-multiple-services" + containers: + - containerName: "details" + image: "alternative-image:v1" + env: + - name: "TOPIC_NAME" + value: "test" diff --git a/api/v1alpha1/fixtures/disallowed-modifications-removing-consumer-old.yaml b/api/v1alpha1/fixtures/disallowed-modifications-removing-consumer-old.yaml new file mode 100644 index 0000000..21d1297 --- /dev/null +++ b/api/v1alpha1/fixtures/disallowed-modifications-removing-consumer-old.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: "riskified.com/v1alpha1" +kind: "DynamicEnv" +metadata: + name: "dynamicenv-vs-with-multiple-services" + namespace: "default" +spec: + istioMatches: + - headers: + end-user: + prefix: "jason" + subsets: + - name: "details" + namespace: "vs-with-multiple-services" + containers: + - containerName: "details" + image: "alternative-image:v1" + env: + - name: "TOPIC_NAME" + value: "test" + consumers: + - name: "details-worker" + namespace: "vs-with-multiple-services" + containers: + - containerName: "details" + image: "alternative-image:v1" diff --git a/api/v1alpha1/fixtures/disallowed-modifications-removing-subset-new.yaml b/api/v1alpha1/fixtures/disallowed-modifications-removing-subset-new.yaml new file mode 100644 index 0000000..b0c540a --- /dev/null +++ b/api/v1alpha1/fixtures/disallowed-modifications-removing-subset-new.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: "riskified.com/v1alpha1" +kind: "DynamicEnv" +metadata: + name: "dynamicenv-vs-with-multiple-services" +spec: + istioMatches: + - headers: + end-user: + prefix: "jason" + subsets: + - name: "details" + namespace: "vs-with-multiple-services" + containers: + - containerName: "details" + image: "docker.io/istio/examples-bookinfo-details-v2:1.16.2" diff --git a/api/v1alpha1/fixtures/disallowed-modifications-removing-subset-old.yaml b/api/v1alpha1/fixtures/disallowed-modifications-removing-subset-old.yaml new file mode 100644 index 0000000..8dd8895 --- /dev/null +++ b/api/v1alpha1/fixtures/disallowed-modifications-removing-subset-old.yaml @@ -0,0 +1,21 @@ +--- +apiVersion: "riskified.com/v1alpha1" +kind: "DynamicEnv" +metadata: + name: "dynamicenv-vs-with-multiple-services" +spec: + istioMatches: + - headers: + end-user: + prefix: "jason" + subsets: + - name: "details" + namespace: "vs-with-multiple-services" + containers: + - containerName: "details" + image: "docker.io/istio/examples-bookinfo-details-v2:1.16.2" + - name: "reviews" + namespace: "vs-with-multiple-services" + containers: + - containerName: "reviews" + image: "docker.io/istio/examples-bookinfo-reviews-v3:1.16.2" diff --git a/api/v1alpha1/groupversion_info.go b/api/v1alpha1/groupversion_info.go new file mode 100644 index 0000000..cd31fe3 --- /dev/null +++ b/api/v1alpha1/groupversion_info.go @@ -0,0 +1,36 @@ +/* +Copyright 2021. + +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 riskified v1alpha1 API group +// +kubebuilder:object:generate=true +// +groupName=riskified.com +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "riskified.com", Version: "v1alpha1"} + + // 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/api/v1alpha1/webhook_suite_test.go b/api/v1alpha1/webhook_suite_test.go new file mode 100644 index 0000000..12a3726 --- /dev/null +++ b/api/v1alpha1/webhook_suite_test.go @@ -0,0 +1,133 @@ +/* +Copyright 2021. + +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 ( + "context" + "crypto/tls" + "fmt" + "k8s.io/client-go/rest" + "net" + "path/filepath" + "testing" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + admissionv1beta1 "k8s.io/api/admission/v1beta1" + //+kubebuilder:scaffold:imports + "k8s.io/apimachinery/pkg/runtime" + 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" +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment +var ctx context.Context +var cancel context.CancelFunc + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Webhook Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + ctx, cancel = context.WithCancel(context.TODO()) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: false, + WebhookInstallOptions: envtest.WebhookInstallOptions{ + Paths: []string{filepath.Join("..", "..", "config", "webhook")}, + }, + } + + var err error + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + scheme := runtime.NewScheme() + err = AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + err = admissionv1beta1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + + // start webhook server using Manager + webhookInstallOptions := &testEnv.WebhookInstallOptions + mgr, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme, + Host: webhookInstallOptions.LocalServingHost, + Port: webhookInstallOptions.LocalServingPort, + CertDir: webhookInstallOptions.LocalServingCertDir, + LeaderElection: false, + MetricsBindAddress: "0", + }) + Expect(err).NotTo(HaveOccurred()) + + err = (&DynamicEnv{}).SetupWebhookWithManager(mgr) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:webhook + + go func() { + defer GinkgoRecover() + err = mgr.Start(ctx) + if err != nil { + Expect(err).NotTo(HaveOccurred()) + } + }() + + // wait for the webhook server to get ready + dialer := &net.Dialer{Timeout: time.Second} + addrPort := fmt.Sprintf("%s:%d", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort) + Eventually(func() error { + conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) // nolint:gosec + if err != nil { + return err + } + _ = conn.Close() + return nil + }).Should(Succeed()) + +}) + +var _ = AfterSuite(func() { + cancel() + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 0000000..95c1308 --- /dev/null +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,450 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 2021. + +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/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Condition) DeepCopyInto(out *Condition) { + *out = *in + in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime) + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition. +func (in *Condition) DeepCopy() *Condition { + if in == nil { + return nil + } + out := new(Condition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConsumerStatus) DeepCopyInto(out *ConsumerStatus) { + *out = *in + out.ResourceStatus = in.ResourceStatus + if in.Errors != nil { + in, out := &in.Errors, &out.Errors + *out = make([]StatusError, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConsumerStatus. +func (in *ConsumerStatus) DeepCopy() *ConsumerStatus { + if in == nil { + return nil + } + out := new(ConsumerStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ContainerOverrides) DeepCopyInto(out *ContainerOverrides) { + *out = *in + if in.Command != nil { + in, out := &in.Command, &out.Command + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Env != nil { + in, out := &in.Env, &out.Env + *out = make([]v1.EnvVar, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerOverrides. +func (in *ContainerOverrides) DeepCopy() *ContainerOverrides { + if in == nil { + return nil + } + out := new(ContainerOverrides) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DynamicEnv) DeepCopyInto(out *DynamicEnv) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DynamicEnv. +func (in *DynamicEnv) DeepCopy() *DynamicEnv { + if in == nil { + return nil + } + out := new(DynamicEnv) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DynamicEnv) 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 *DynamicEnvList) DeepCopyInto(out *DynamicEnvList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]DynamicEnv, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DynamicEnvList. +func (in *DynamicEnvList) DeepCopy() *DynamicEnvList { + if in == nil { + return nil + } + out := new(DynamicEnvList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DynamicEnvList) 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 *DynamicEnvSpec) DeepCopyInto(out *DynamicEnvSpec) { + *out = *in + if in.IstioMatches != nil { + in, out := &in.IstioMatches, &out.IstioMatches + *out = make([]IstioMatch, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Subsets != nil { + in, out := &in.Subsets, &out.Subsets + *out = make([]Subset, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Consumers != nil { + in, out := &in.Consumers, &out.Consumers + *out = make([]Subset, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DynamicEnvSpec. +func (in *DynamicEnvSpec) DeepCopy() *DynamicEnvSpec { + if in == nil { + return nil + } + out := new(DynamicEnvSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DynamicEnvStatus) DeepCopyInto(out *DynamicEnvStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.SubsetsStatus != nil { + in, out := &in.SubsetsStatus, &out.SubsetsStatus + *out = make(map[string]SubsetStatus, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } + if in.ConsumersStatus != nil { + in, out := &in.ConsumersStatus, &out.ConsumersStatus + *out = make(map[string]ConsumerStatus, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DynamicEnvStatus. +func (in *DynamicEnvStatus) DeepCopy() *DynamicEnvStatus { + if in == nil { + return nil + } + out := new(DynamicEnvStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IstioMatch) DeepCopyInto(out *IstioMatch) { + *out = *in + if in.Headers != nil { + in, out := &in.Headers, &out.Headers + *out = make(map[string]StringMatch, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.SourceLabels != nil { + in, out := &in.SourceLabels, &out.SourceLabels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IstioMatch. +func (in *IstioMatch) DeepCopy() *IstioMatch { + if in == nil { + return nil + } + out := new(IstioMatch) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceStatus) DeepCopyInto(out *ResourceStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceStatus. +func (in *ResourceStatus) DeepCopy() *ResourceStatus { + if in == nil { + return nil + } + out := new(ResourceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StatusError) DeepCopyInto(out *StatusError) { + *out = *in + in.LastOccurrence.DeepCopyInto(&out.LastOccurrence) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatusError. +func (in *StatusError) DeepCopy() *StatusError { + if in == nil { + return nil + } + out := new(StatusError) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StringMatch) DeepCopyInto(out *StringMatch) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StringMatch. +func (in *StringMatch) DeepCopy() *StringMatch { + if in == nil { + return nil + } + out := new(StringMatch) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Subset) DeepCopyInto(out *Subset) { + *out = *in + if in.PodLabels != nil { + in, out := &in.PodLabels, &out.PodLabels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Replicas != nil { + in, out := &in.Replicas, &out.Replicas + *out = new(int32) + **out = **in + } + if in.Containers != nil { + in, out := &in.Containers, &out.Containers + *out = make([]ContainerOverrides, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.InitContainers != nil { + in, out := &in.InitContainers, &out.InitContainers + *out = make([]ContainerOverrides, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Subset. +func (in *Subset) DeepCopy() *Subset { + if in == nil { + return nil + } + out := new(Subset) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubsetErrors) DeepCopyInto(out *SubsetErrors) { + *out = *in + if in.Deployment != nil { + in, out := &in.Deployment, &out.Deployment + *out = make([]StatusError, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.DestinationRule != nil { + in, out := &in.DestinationRule, &out.DestinationRule + *out = make([]StatusError, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.VirtualServices != nil { + in, out := &in.VirtualServices, &out.VirtualServices + *out = make([]StatusError, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Subset != nil { + in, out := &in.Subset, &out.Subset + *out = make([]StatusError, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubsetErrors. +func (in *SubsetErrors) DeepCopy() *SubsetErrors { + if in == nil { + return nil + } + out := new(SubsetErrors) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubsetMessages) DeepCopyInto(out *SubsetMessages) { + *out = *in + if in.Deployment != nil { + in, out := &in.Deployment, &out.Deployment + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.DestinationRule != nil { + in, out := &in.DestinationRule, &out.DestinationRule + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.VirtualService != nil { + in, out := &in.VirtualService, &out.VirtualService + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.GlobalErrors != nil { + in, out := &in.GlobalErrors, &out.GlobalErrors + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubsetMessages. +func (in *SubsetMessages) DeepCopy() *SubsetMessages { + if in == nil { + return nil + } + out := new(SubsetMessages) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubsetStatus) DeepCopyInto(out *SubsetStatus) { + *out = *in + out.Deployment = in.Deployment + if in.DestinationRules != nil { + in, out := &in.DestinationRules, &out.DestinationRules + *out = make([]ResourceStatus, len(*in)) + copy(*out, *in) + } + if in.VirtualServices != nil { + in, out := &in.VirtualServices, &out.VirtualServices + *out = make([]ResourceStatus, len(*in)) + copy(*out, *in) + } + if in.Errors != nil { + in, out := &in.Errors, &out.Errors + *out = new(SubsetErrors) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubsetStatus. +func (in *SubsetStatus) DeepCopy() *SubsetStatus { + if in == nil { + return nil + } + out := new(SubsetStatus) + in.DeepCopyInto(out) + return out +} diff --git a/config/certmanager/certificate.yaml b/config/certmanager/certificate.yaml new file mode 100644 index 0000000..52d8661 --- /dev/null +++ b/config/certmanager/certificate.yaml @@ -0,0 +1,25 @@ +# The following manifests contain a self-signed issuer CR and a certificate CR. +# More document can be found at https://docs.cert-manager.io +# WARNING: Targets CertManager v1.0. Check https://cert-manager.io/docs/installation/upgrading/ for breaking changes. +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: selfsigned-issuer + namespace: system +spec: + selfSigned: {} +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml + namespace: system +spec: + # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize + dnsNames: + - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc + - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc.cluster.local + issuerRef: + kind: Issuer + name: selfsigned-issuer + secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize diff --git a/config/certmanager/kustomization.yaml b/config/certmanager/kustomization.yaml new file mode 100644 index 0000000..bebea5a --- /dev/null +++ b/config/certmanager/kustomization.yaml @@ -0,0 +1,5 @@ +resources: +- certificate.yaml + +configurations: +- kustomizeconfig.yaml diff --git a/config/certmanager/kustomizeconfig.yaml b/config/certmanager/kustomizeconfig.yaml new file mode 100644 index 0000000..90d7c31 --- /dev/null +++ b/config/certmanager/kustomizeconfig.yaml @@ -0,0 +1,16 @@ +# This configuration is for teaching kustomize how to update name ref and var substitution +nameReference: +- kind: Issuer + group: cert-manager.io + fieldSpecs: + - kind: Certificate + group: cert-manager.io + path: spec/issuerRef/name + +varReference: +- kind: Certificate + group: cert-manager.io + path: spec/commonName +- kind: Certificate + group: cert-manager.io + path: spec/dnsNames diff --git a/config/crd/bases/riskified.com_dynamicenvs.yaml b/config/crd/bases/riskified.com_dynamicenvs.yaml new file mode 100644 index 0000000..e2f2bcc --- /dev/null +++ b/config/crd/bases/riskified.com_dynamicenvs.yaml @@ -0,0 +1,1021 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: dynamicenvs.riskified.com +spec: + group: riskified.com + names: + kind: DynamicEnv + listKind: DynamicEnvList + plural: dynamicenvs + shortNames: + - de + singular: dynamicenv + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Status of the DynamicEnv + jsonPath: .status.state + name: Status + type: string + - description: displays desired subsets and consumers count + jsonPath: .status.totalCount + name: Desired + type: integer + - description: displays how many subsets and consumers are available + jsonPath: .status.totalReady + name: Current + type: integer + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: DynamicEnv is the Schema for the dynamicenvs 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: DynamicEnvSpec defines the desired state of DynamicEnv + properties: + consumers: + description: Consumers are like subsets but for deployments that do + not open a service but connect to external resources for their work + (e.g, offline workers). They are equivalent to subsets in the sense + that they launch overriding deployments with custom image and/or + settings. However, since they are only consumers no virtual service + or destination route will be pointing to them. + items: + description: Subsets defines how to generate subsets from existing + Deployments + properties: + containers: + description: A list of container overrides (at least one of + Containers or InitContainers must not be empty) + items: + description: Defines the details of the container on which + changes need to be made and the relevant overrides + properties: + command: + description: Entrypoint array overridden to the desired + subset The docker image's ENTRYPOINT is used if this + is not provided. + items: + type: string + type: array + containerName: + description: Container name to override in multiple containers' + environment. If not specified we will use the first + container. + type: string + env: + description: Additional environment variable to the given + deployment + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. Must + be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are + expanded using the previously defined environment + variables in the container and any service environment + variables. If a variable cannot be resolved, the + reference in the input string will be unchanged. + Double $$ are reduced to a single $, which allows + for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults + to "".' + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More + info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: 'Selects a field of the pod: supports + metadata.name, metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, limits.ephemeral-storage, requests.cpu, + requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults to + "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in the + pod's namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More + info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + image: + description: Docker image name overridden to the desired + subset The Docker image found in the original deployment + is used if this is not provided. + type: string + required: + - containerName + type: object + type: array + defaultVersion: + description: Default version for this subset (if different then + the global default version). This is the version that will + get the default route. + type: string + initContainers: + description: A list of init container overrides (at least one + of Containers or InitContainers must not be empty) + items: + description: Defines the details of the container on which + changes need to be made and the relevant overrides + properties: + command: + description: Entrypoint array overridden to the desired + subset The docker image's ENTRYPOINT is used if this + is not provided. + items: + type: string + type: array + containerName: + description: Container name to override in multiple containers' + environment. If not specified we will use the first + container. + type: string + env: + description: Additional environment variable to the given + deployment + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. Must + be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are + expanded using the previously defined environment + variables in the container and any service environment + variables. If a variable cannot be resolved, the + reference in the input string will be unchanged. + Double $$ are reduced to a single $, which allows + for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults + to "".' + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More + info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: 'Selects a field of the pod: supports + metadata.name, metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, limits.ephemeral-storage, requests.cpu, + requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults to + "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in the + pod's namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More + info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + image: + description: Docker image name overridden to the desired + subset The Docker image found in the original deployment + is used if this is not provided. + type: string + required: + - containerName + type: object + type: array + name: + description: Deployment name (without namespace) + type: string + namespace: + description: Namespace where the deployment is deployed + type: string + podLabels: + additionalProperties: + type: string + description: Labels to add to the pods of the deployment launched + by this subset. Could be used in conjunction with 'SourceLabels' + in the `IstioMatches`. + type: object + replicas: + description: 'Number of deployment replicas. Default is 1. Note: + 0 is *invalid*.' + format: int32 + type: integer + required: + - name + - namespace + type: object + type: array + istioMatches: + description: A list of matchers (partly corresponds to IstioMatch). + Each match will have a rule of its own (merged with existing rules) + ordered by their order here. + items: + description: specifies a set of criterion to be met in order for + the rule to be applied to the HTTP request This field is immutable + after creation. + properties: + headers: + additionalProperties: + description: Describes how to match a given string in HTTP + headers. Match is case-sensitive. one and only one of the + fields needs to be defined (oneof) + properties: + exact: + type: string + prefix: + type: string + regex: + type: string + type: object + description: 'Header values are case-sensitive and formatted + as follows:
- `exact: "value"` for exact string match
+ - `prefix: "value"` for prefix-based match
- `regex: + "value"` for RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax).' + type: object + sourceLabels: + additionalProperties: + type: string + description: One or more labels that constrain the applicability + of a rule to source (client) workloads with the given labels. + type: object + type: object + type: array + subsets: + description: Who should participate in the given dynamic environment + items: + description: Subsets defines how to generate subsets from existing + Deployments + properties: + containers: + description: A list of container overrides (at least one of + Containers or InitContainers must not be empty) + items: + description: Defines the details of the container on which + changes need to be made and the relevant overrides + properties: + command: + description: Entrypoint array overridden to the desired + subset The docker image's ENTRYPOINT is used if this + is not provided. + items: + type: string + type: array + containerName: + description: Container name to override in multiple containers' + environment. If not specified we will use the first + container. + type: string + env: + description: Additional environment variable to the given + deployment + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. Must + be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are + expanded using the previously defined environment + variables in the container and any service environment + variables. If a variable cannot be resolved, the + reference in the input string will be unchanged. + Double $$ are reduced to a single $, which allows + for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults + to "".' + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More + info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: 'Selects a field of the pod: supports + metadata.name, metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, limits.ephemeral-storage, requests.cpu, + requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults to + "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in the + pod's namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More + info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + image: + description: Docker image name overridden to the desired + subset The Docker image found in the original deployment + is used if this is not provided. + type: string + required: + - containerName + type: object + type: array + defaultVersion: + description: Default version for this subset (if different then + the global default version). This is the version that will + get the default route. + type: string + initContainers: + description: A list of init container overrides (at least one + of Containers or InitContainers must not be empty) + items: + description: Defines the details of the container on which + changes need to be made and the relevant overrides + properties: + command: + description: Entrypoint array overridden to the desired + subset The docker image's ENTRYPOINT is used if this + is not provided. + items: + type: string + type: array + containerName: + description: Container name to override in multiple containers' + environment. If not specified we will use the first + container. + type: string + env: + description: Additional environment variable to the given + deployment + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. Must + be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are + expanded using the previously defined environment + variables in the container and any service environment + variables. If a variable cannot be resolved, the + reference in the input string will be unchanged. + Double $$ are reduced to a single $, which allows + for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults + to "".' + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More + info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: 'Selects a field of the pod: supports + metadata.name, metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, limits.ephemeral-storage, requests.cpu, + requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults to + "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in the + pod's namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More + info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + image: + description: Docker image name overridden to the desired + subset The Docker image found in the original deployment + is used if this is not provided. + type: string + required: + - containerName + type: object + type: array + name: + description: Deployment name (without namespace) + type: string + namespace: + description: Namespace where the deployment is deployed + type: string + podLabels: + additionalProperties: + type: string + description: Labels to add to the pods of the deployment launched + by this subset. Could be used in conjunction with 'SourceLabels' + in the `IstioMatches`. + type: object + replicas: + description: 'Number of deployment replicas. Default is 1. Note: + 0 is *invalid*.' + format: int32 + type: integer + required: + - name + - namespace + type: object + type: array + required: + - istioMatches + - subsets + type: object + status: + description: DynamicEnvStatus defines the observed state of DynamicEnv + properties: + conditions: + description: Represents the latest available observations of a deployment's + current state. + items: + description: Condition describes the state of a DynamicEnv at a + certain point. + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + lastUpdateTime: + description: The last time this condition was updated. + format: date-time + type: string + message: + description: A human-readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition + type: string + required: + - status + - type + type: object + type: array + consumersStatus: + additionalProperties: + properties: + errors: + description: List of errors related to the consumer + items: + description: StatusError shows an error we want to display + in the status with the last time it happened. This *does + not* have to be the only time it happened. The idea is that + a list of errors should only contain single occurrence of + an error (just the last). + properties: + error: + description: The error message + type: string + lastOccurrence: + description: THe last occurrence of the error + format: date-time + type: string + required: + - error + - lastOccurrence + type: object + type: array + hash: + description: Hash of the current consumer - for internal use + format: int64 + type: integer + name: + description: The name of the resource + type: string + namespace: + description: The namespace where the resource is created + type: string + status: + description: The life cycle status of the resource + type: string + required: + - name + - namespace + - status + type: object + type: object + state: + type: string + subsetsStatus: + additionalProperties: + description: SubsetStatus Contains aggregation of all resources + status connected to set subset. + properties: + deployment: + description: Status of the deployment that belongs to the subset + properties: + name: + description: The name of the resource + type: string + namespace: + description: The namespace where the resource is created + type: string + status: + description: The life cycle status of the resource + type: string + required: + - name + - namespace + - status + type: object + destinationRules: + description: Status of the destination-rule that belongs to + the subset + items: + description: ResourceStatus shows the status of each item + created/edited by DynamicEnv + properties: + name: + description: The name of the resource + type: string + namespace: + description: The namespace where the resource is created + type: string + status: + description: The life cycle status of the resource + type: string + required: + - name + - namespace + - status + type: object + type: array + hash: + description: Hash of the current subset - for internal use + format: int64 + type: integer + subsetErrors: + description: A list of global errors related to subset resources + properties: + deployment: + description: Subset's deployment global errors. + items: + description: StatusError shows an error we want to display + in the status with the last time it happened. This *does + not* have to be the only time it happened. The idea + is that a list of errors should only contain single + occurrence of an error (just the last). + properties: + error: + description: The error message + type: string + lastOccurrence: + description: THe last occurrence of the error + format: date-time + type: string + required: + - error + - lastOccurrence + type: object + type: array + destinationRule: + description: Subset's destination-rule global errors. + items: + description: StatusError shows an error we want to display + in the status with the last time it happened. This *does + not* have to be the only time it happened. The idea + is that a list of errors should only contain single + occurrence of an error (just the last). + properties: + error: + description: The error message + type: string + lastOccurrence: + description: THe last occurrence of the error + format: date-time + type: string + required: + - error + - lastOccurrence + type: object + type: array + subset: + description: Errors related to subset but not to any of + the launched resources + items: + description: StatusError shows an error we want to display + in the status with the last time it happened. This *does + not* have to be the only time it happened. The idea + is that a list of errors should only contain single + occurrence of an error (just the last). + properties: + error: + description: The error message + type: string + lastOccurrence: + description: THe last occurrence of the error + format: date-time + type: string + required: + - error + - lastOccurrence + type: object + type: array + virtualServices: + description: Subset's virtual-services global errors. + items: + description: StatusError shows an error we want to display + in the status with the last time it happened. This *does + not* have to be the only time it happened. The idea + is that a list of errors should only contain single + occurrence of an error (just the last). + properties: + error: + description: The error message + type: string + lastOccurrence: + description: THe last occurrence of the error + format: date-time + type: string + required: + - error + - lastOccurrence + type: object + type: array + type: object + virtualServices: + description: Status of the virtual-service that belongs to the + subset + items: + description: ResourceStatus shows the status of each item + created/edited by DynamicEnv + properties: + name: + description: The name of the resource + type: string + namespace: + description: The namespace where the resource is created + type: string + status: + description: The life cycle status of the resource + type: string + required: + - name + - namespace + - status + type: object + type: array + type: object + type: object + totalCount: + description: desired subsets and consumers count + type: integer + totalReady: + description: number of available subsets and consumers + type: integer + required: + - subsetsStatus + - totalReady + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml new file mode 100644 index 0000000..48323fc --- /dev/null +++ b/config/crd/kustomization.yaml @@ -0,0 +1,21 @@ +# 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/riskified.com_dynamicenvs.yaml +#+kubebuilder:scaffold:crdkustomizeresource + +patchesStrategicMerge: +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. +# patches here are for enabling the conversion webhook for each CRD +#- patches/webhook_in_dynamicenvs.yaml +#+kubebuilder:scaffold:crdkustomizewebhookpatch + +# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. +# patches here are for enabling the CA injection for each CRD +- patches/cainjection_in_dynamicenvs.yaml +#+kubebuilder:scaffold:crdkustomizecainjectionpatch + +# the following config is for teaching kustomize how to do kustomization for CRDs. +configurations: +- kustomizeconfig.yaml diff --git a/config/crd/kustomizeconfig.yaml b/config/crd/kustomizeconfig.yaml new file mode 100644 index 0000000..ec5c150 --- /dev/null +++ b/config/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/crd/patches/cainjection_in_dynamicenvs.yaml b/config/crd/patches/cainjection_in_dynamicenvs.yaml new file mode 100644 index 0000000..d60cd90 --- /dev/null +++ b/config/crd/patches/cainjection_in_dynamicenvs.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: dynamicenvs.riskified.com diff --git a/config/crd/patches/webhook_in_dynamicenvs.yaml b/config/crd/patches/webhook_in_dynamicenvs.yaml new file mode 100644 index 0000000..95140a4 --- /dev/null +++ b/config/crd/patches/webhook_in_dynamicenvs.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: dynamicenvs.riskified.com +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/riskified.com_dynamicenvs.yaml b/config/crd/riskified.com_dynamicenvs.yaml new file mode 100644 index 0000000..4cf97a4 --- /dev/null +++ b/config/crd/riskified.com_dynamicenvs.yaml @@ -0,0 +1,83 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: dynamicenvs.riskified.com +spec: + group: riskified.com + names: + kind: DynamicEnv + listKind: DynamicEnvList + plural: dynamicenvs + shortNames: + - de + singular: dynamicenv + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: DynamicEnv is the Schema for the dynamicenvs 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: DynamicEnvSpec defines the desired state of DynamicEnv + properties: + match: + description: specifies a set of criterion to be met in order for the + rule to be applied to the HTTP request + type: object + x-kubernetes-preserve-unknown-fields: true + subsets: + description: // Who should participate in the given session + items: + description: Subsets defines how to target a single Deployment or + DeploymentConfig. + properties: + args: + additionalProperties: + type: string + description: Additional arguments to the given strategy + type: object + name: + description: Deployment or DeploymentConfig name, could optionally + contain [Kind/]Name to be specific + type: string + strategy: + description: How this deployment should be handled, e.g. telepresence + or prepared-image + type: string + stype: object + type: array + required: + - match + type: object + status: + description: DynamicEnvStatus defines the observed state of DynamicEnv + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml new file mode 100644 index 0000000..c2a0713 --- /dev/null +++ b/config/default/kustomization.yaml @@ -0,0 +1,74 @@ +# Adds namespace to all resources. +namespace: dynamic-environment-operator-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: dynamic-environment-operator- + +# Labels to add to all resources and selectors. +#commonLabels: +# someName: someValue + +bases: +- ../crd +- ../rbac +- ../manager +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in +# crd/kustomization.yaml +- ../webhook +# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. +- ../certmanager +# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. +#- ../prometheus + +patchesStrategicMerge: +# Protect the /metrics endpoint by putting it behind auth. +# If you want your controller-manager to expose the /metrics +# endpoint w/o any authn/z, please comment the following line. +# - manager_auth_proxy_patch.yaml + +# Mount the controller config file for loading manager configurations +# through a ComponentConfig type +#- manager_config_patch.yaml + +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in +# crd/kustomization.yaml +- manager_webhook_patch.yaml + +# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. +# Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. +# 'CERTMANAGER' needs to be enabled to use ca injection +- webhookcainjection_patch.yaml + +# the following config is for teaching kustomize how to do var substitution +vars: +# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. +- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR + objref: + kind: Certificate + group: cert-manager.io + version: v1 + name: serving-cert # this name should match the one in certificate.yaml + fieldref: + fieldpath: metadata.namespace +- name: CERTIFICATE_NAME + objref: + kind: Certificate + group: cert-manager.io + version: v1 + name: serving-cert # this name should match the one in certificate.yaml +- name: SERVICE_NAMESPACE # namespace of the service + objref: + kind: Service + version: v1 + name: webhook-service + fieldref: + fieldpath: metadata.namespace +- name: SERVICE_NAME + objref: + kind: Service + version: v1 + name: webhook-service diff --git a/config/default/manager_auth_proxy_patch.yaml b/config/default/manager_auth_proxy_patch.yaml new file mode 100644 index 0000000..d727035 --- /dev/null +++ b/config/default/manager_auth_proxy_patch.yaml @@ -0,0 +1,34 @@ +# This patch inject a sidecar container which is a HTTP proxy for the +# controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + containers: + - name: kube-rbac-proxy + image: gcr.io/kubebuilder/kube-rbac-proxy:v0.13.1 + args: + - "--secure-listen-address=0.0.0.0:8443" + - "--upstream=http://127.0.0.1:8080/" + - "--logtostderr=true" + - "--v=0" + ports: + - containerPort: 8443 + protocol: TCP + name: https + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 5m + memory: 64Mi + - name: manager + args: + - "--health-probe-bind-address=:8081" + - "--metrics-bind-address=127.0.0.1:8080" + - "--leader-elect" diff --git a/config/default/manager_config_patch.yaml b/config/default/manager_config_patch.yaml new file mode 100644 index 0000000..6c40015 --- /dev/null +++ b/config/default/manager_config_patch.yaml @@ -0,0 +1,20 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + containers: + - name: manager + args: + - "--config=controller_manager_config.yaml" + volumeMounts: + - name: manager-config + mountPath: /controller_manager_config.yaml + subPath: controller_manager_config.yaml + volumes: + - name: manager-config + configMap: + name: manager-config diff --git a/config/default/manager_webhook_patch.yaml b/config/default/manager_webhook_patch.yaml new file mode 100644 index 0000000..738de35 --- /dev/null +++ b/config/default/manager_webhook_patch.yaml @@ -0,0 +1,23 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + containers: + - name: manager + ports: + - containerPort: 9443 + name: webhook-server + protocol: TCP + volumeMounts: + - mountPath: /tmp/k8s-webhook-server/serving-certs + name: cert + readOnly: true + volumes: + - name: cert + secret: + defaultMode: 420 + secretName: webhook-server-cert diff --git a/config/default/webhookcainjection_patch.yaml b/config/default/webhookcainjection_patch.yaml new file mode 100644 index 0000000..47ef1d1 --- /dev/null +++ b/config/default/webhookcainjection_patch.yaml @@ -0,0 +1,8 @@ +# This patch add annotation to admission webhook config and +# the variables $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize. +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: validating-webhook-configuration + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) diff --git a/config/manager/controller_manager_config.yaml b/config/manager/controller_manager_config.yaml new file mode 100644 index 0000000..d8b028f --- /dev/null +++ b/config/manager/controller_manager_config.yaml @@ -0,0 +1,11 @@ +apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 +kind: ControllerManagerConfig +health: + healthProbeBindAddress: :8081 +metrics: + bindAddress: 127.0.0.1:8080 +webhook: + port: 9443 +leaderElection: + leaderElect: true + resourceName: da709e5e.com diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml new file mode 100644 index 0000000..5e793dd --- /dev/null +++ b/config/manager/kustomization.yaml @@ -0,0 +1,16 @@ +resources: +- manager.yaml + +generatorOptions: + disableNameSuffixHash: true + +configMapGenerator: +- files: + - controller_manager_config.yaml + name: manager-config +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +images: +- name: controller + newName: controller + newTag: latest diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml new file mode 100644 index 0000000..1222e5d --- /dev/null +++ b/config/manager/manager.yaml @@ -0,0 +1,66 @@ +apiVersion: v1 +kind: Namespace +metadata: + labels: + control-plane: controller-manager + name: system +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system + labels: + control-plane: controller-manager +spec: + selector: + matchLabels: + control-plane: controller-manager + replicas: 1 + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + labels: + control-plane: controller-manager + spec: + securityContext: + runAsNonRoot: true + containers: + - command: + - /manager + args: + - --leader-elect + - --version-label + - version + - --default-version + - shared + # uncomment below (and edit to your liking) to specify labels to be removed (comma separated list) + #- --remove-labels + #- argocd.argoproj.io/instance + image: controller:latest + imagePullPolicy: IfNotPresent + name: manager + securityContext: + allowPrivilegeEscalation: false + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + limits: + cpu: 200m + memory: 100Mi + requests: + cpu: 100m + memory: 20Mi + serviceAccountName: controller-manager + terminationGracePeriodSeconds: 10 diff --git a/config/manifests/bases/dynamic-environment-operator.clusterserviceversion.yaml b/config/manifests/bases/dynamic-environment-operator.clusterserviceversion.yaml new file mode 100644 index 0000000..f1d9490 --- /dev/null +++ b/config/manifests/bases/dynamic-environment-operator.clusterserviceversion.yaml @@ -0,0 +1,45 @@ +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + annotations: + alm-examples: '[]' + capabilities: Basic Install + name: dynamic-environment-operator.v0.0.0 + namespace: placeholder +spec: + apiservicedefinitions: {} + customresourcedefinitions: + owned: + - description: DynamicEnv is the Schema for the dynamicenvs API + displayName: Dynamic Env + kind: DynamicEnv + name: dynamicenvs.riskified.com + version: v1alpha1 + description: Dynamic environment operator for Kubernetes + displayName: dynamic-environment-operator + icon: + - base64data: "" + mediatype: "" + install: + spec: + deployments: null + strategy: "" + installModes: + - supported: false + type: OwnNamespace + - supported: false + type: SingleNamespace + - supported: false + type: MultiNamespace + - supported: true + type: AllNamespaces + keywords: + - Istio + - Test + links: + - name: Dynamic Environment Operator + url: https://dynamic-environment-operator.domain + maturity: alpha + provider: + name: riskified + version: 0.0.0 diff --git a/config/manifests/kustomization.yaml b/config/manifests/kustomization.yaml new file mode 100644 index 0000000..a56db2f --- /dev/null +++ b/config/manifests/kustomization.yaml @@ -0,0 +1,27 @@ +# These resources constitute the fully configured set of manifests +# used to generate the 'manifests/' directory in a bundle. +resources: +- bases/dynamic-environment-operator.clusterserviceversion.yaml +- ../default +- ../samples +- ../scorecard + +# [WEBHOOK] To enable webhooks, uncomment all the sections with [WEBHOOK] prefix. +# Do NOT uncomment sections with prefix [CERTMANAGER], as OLM does not support cert-manager. +# These patches remove the unnecessary "cert" volume and its manager container volumeMount. +#patchesJson6902: +#- target: +# group: apps +# version: v1 +# kind: Deployment +# name: controller-manager +# namespace: system +# patch: |- +# # Remove the manager container's "cert" volumeMount, since OLM will create and mount a set of certs. +# # Update the indices in this path if adding or removing containers/volumeMounts in the manager's Deployment. +# - op: remove +# path: /spec/template/spec/containers/1/volumeMounts/0 +# # Remove the "cert" volume, since OLM will create and mount a set of certs. +# # Update the indices in this path if adding or removing volumes in the manager's Deployment. +# - op: remove +# path: /spec/template/spec/volumes/0 diff --git a/config/prometheus/kustomization.yaml b/config/prometheus/kustomization.yaml new file mode 100644 index 0000000..ed13716 --- /dev/null +++ b/config/prometheus/kustomization.yaml @@ -0,0 +1,2 @@ +resources: +- monitor.yaml diff --git a/config/prometheus/monitor.yaml b/config/prometheus/monitor.yaml new file mode 100644 index 0000000..d19136a --- /dev/null +++ b/config/prometheus/monitor.yaml @@ -0,0 +1,20 @@ + +# Prometheus Monitor Service (Metrics) +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + labels: + control-plane: controller-manager + name: controller-manager-metrics-monitor + namespace: system +spec: + endpoints: + - path: /metrics + port: https + scheme: https + bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token + tlsConfig: + insecureSkipVerify: true + selector: + matchLabels: + control-plane: controller-manager diff --git a/config/rbac/auth_proxy_client_clusterrole.yaml b/config/rbac/auth_proxy_client_clusterrole.yaml new file mode 100644 index 0000000..51a75db --- /dev/null +++ b/config/rbac/auth_proxy_client_clusterrole.yaml @@ -0,0 +1,9 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: metrics-reader +rules: +- nonResourceURLs: + - "/metrics" + verbs: + - get diff --git a/config/rbac/auth_proxy_role.yaml b/config/rbac/auth_proxy_role.yaml new file mode 100644 index 0000000..80e1857 --- /dev/null +++ b/config/rbac/auth_proxy_role.yaml @@ -0,0 +1,17 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: proxy-role +rules: +- apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create diff --git a/config/rbac/auth_proxy_role_binding.yaml b/config/rbac/auth_proxy_role_binding.yaml new file mode 100644 index 0000000..ec7acc0 --- /dev/null +++ b/config/rbac/auth_proxy_role_binding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: proxy-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: proxy-role +subjects: +- kind: ServiceAccount + name: controller-manager + namespace: system diff --git a/config/rbac/auth_proxy_service.yaml b/config/rbac/auth_proxy_service.yaml new file mode 100644 index 0000000..71f1797 --- /dev/null +++ b/config/rbac/auth_proxy_service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + control-plane: controller-manager + name: controller-manager-metrics-service + namespace: system +spec: + ports: + - name: https + port: 8443 + protocol: TCP + targetPort: https + selector: + control-plane: controller-manager diff --git a/config/rbac/dynamicenv_editor_role.yaml b/config/rbac/dynamicenv_editor_role.yaml new file mode 100644 index 0000000..23c27c9 --- /dev/null +++ b/config/rbac/dynamicenv_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit dynamicenvs. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: dynamicenv-editor-role +rules: +- apiGroups: + - riskified.com + resources: + - dynamicenvs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - riskified.com + resources: + - dynamicenvs/status + verbs: + - get diff --git a/config/rbac/dynamicenv_viewer_role.yaml b/config/rbac/dynamicenv_viewer_role.yaml new file mode 100644 index 0000000..01d6ced --- /dev/null +++ b/config/rbac/dynamicenv_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view dynamicenvs. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: dynamicenv-viewer-role +rules: +- apiGroups: + - riskified.com + resources: + - dynamicenvs + verbs: + - get + - list + - watch +- apiGroups: + - riskified.com + resources: + - dynamicenvs/status + verbs: + - get diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml new file mode 100644 index 0000000..731832a --- /dev/null +++ b/config/rbac/kustomization.yaml @@ -0,0 +1,18 @@ +resources: +# All RBAC will be applied under this service account in +# the deployment namespace. You may comment out this resource +# if your manager 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 +- leader_election_role.yaml +- leader_election_role_binding.yaml +# Comment the following 4 lines if you want to disable +# the auth proxy (https://github.com/brancz/kube-rbac-proxy) +# which protects your /metrics endpoint. +- auth_proxy_service.yaml +- auth_proxy_role.yaml +- auth_proxy_role_binding.yaml +- auth_proxy_client_clusterrole.yaml diff --git a/config/rbac/leader_election_role.yaml b/config/rbac/leader_election_role.yaml new file mode 100644 index 0000000..4190ec8 --- /dev/null +++ b/config/rbac/leader_election_role.yaml @@ -0,0 +1,37 @@ +# permissions to do leader election. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: leader-election-role +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch diff --git a/config/rbac/leader_election_role_binding.yaml b/config/rbac/leader_election_role_binding.yaml new file mode 100644 index 0000000..1d1321e --- /dev/null +++ b/config/rbac/leader_election_role_binding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: leader-election-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: leader-election-role +subjects: +- kind: ServiceAccount + name: controller-manager + namespace: system diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml new file mode 100644 index 0000000..cdc74c8 --- /dev/null +++ b/config/rbac/role.yaml @@ -0,0 +1,67 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: manager-role +rules: +- apiGroups: + - apps + resources: + - deployments + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch +- apiGroups: + - networking.istio.io + resources: + - '*' + verbs: + - '*' +- apiGroups: + - riskified.com + resources: + - dynamicenvs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - riskified.com + resources: + - dynamicenvs/finalizers + verbs: + - update +- apiGroups: + - riskified.com + resources: + - dynamicenvs/status + verbs: + - get + - patch + - update diff --git a/config/rbac/role_binding.yaml b/config/rbac/role_binding.yaml new file mode 100644 index 0000000..2070ede --- /dev/null +++ b/config/rbac/role_binding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: manager-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: manager-role +subjects: +- kind: ServiceAccount + name: controller-manager + namespace: system diff --git a/config/rbac/service_account.yaml b/config/rbac/service_account.yaml new file mode 100644 index 0000000..7cd6025 --- /dev/null +++ b/config/rbac/service_account.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: controller-manager + namespace: system diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml new file mode 100644 index 0000000..2de850f --- /dev/null +++ b/config/samples/kustomization.yaml @@ -0,0 +1,4 @@ +## Append samples you want in your CSV to this file as resources ## +resources: +- riskified_v1alpha1_dynamicenv.yaml +#+kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/samples/riskified_dynamicenv_initContainer.yaml b/config/samples/riskified_dynamicenv_initContainer.yaml new file mode 100644 index 0000000..f1aa2ab --- /dev/null +++ b/config/samples/riskified_dynamicenv_initContainer.yaml @@ -0,0 +1,18 @@ +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-sample +spec: + istioMatches: + - headers: + end-user: + exact: jason + subsets: + - name: "details" + namespace: "services" + initContainer: + containerName: install + image: docker.io/istio/busybox:latest + env: + - name: TOPIC_NAME + value: test \ No newline at end of file diff --git a/config/samples/riskified_v1alpha1_dynamicenv.yaml b/config/samples/riskified_v1alpha1_dynamicenv.yaml new file mode 100644 index 0000000..0315551 --- /dev/null +++ b/config/samples/riskified_v1alpha1_dynamicenv.yaml @@ -0,0 +1,38 @@ +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-sample +spec: + istioMatches: + - headers: + end-user: + exact: jason + - sourceLabels: + a-label: a-value + subsets: + - name: "details" + namespace: "services" + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test + - name: reviews + namespace: services + containers: + - containerName: reviews + command: ["/opt/ibm/wlp/bin/server", "run", "defaultServer"] + image: docker.io/istio/examples-bookinfo-reviews-v3:1.16.2 + env: + - name: TOPIC_NAME + value: test + consumers: + - name: "details-worker" + namespace: "services" + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test-worker diff --git a/config/scorecard/bases/config.yaml b/config/scorecard/bases/config.yaml new file mode 100644 index 0000000..c770478 --- /dev/null +++ b/config/scorecard/bases/config.yaml @@ -0,0 +1,7 @@ +apiVersion: scorecard.operatorframework.io/v1alpha3 +kind: Configuration +metadata: + name: config +stages: +- parallel: true + tests: [] diff --git a/config/scorecard/kustomization.yaml b/config/scorecard/kustomization.yaml new file mode 100644 index 0000000..c236dc3 --- /dev/null +++ b/config/scorecard/kustomization.yaml @@ -0,0 +1,16 @@ +resources: +- bases/config.yaml +patchesJson6902: +- path: patches/basic.config.yaml + target: + group: scorecard.operatorframework.io + version: v1alpha3 + kind: Configuration + name: config +- path: patches/kuttl.config.yaml + target: + group: scorecard.operatorframework.io + version: v1alpha3 + kind: Configuration + name: config +#+kubebuilder:scaffold:patchesJson6902 diff --git a/config/scorecard/patches/basic.config.yaml b/config/scorecard/patches/basic.config.yaml new file mode 100644 index 0000000..c19af06 --- /dev/null +++ b/config/scorecard/patches/basic.config.yaml @@ -0,0 +1,10 @@ +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - basic-check-spec + image: quay.io/operator-framework/scorecard-test:v1.15.0 + labels: + suite: basic + test: basic-check-spec-test diff --git a/config/scorecard/patches/kuttl.config.yaml b/config/scorecard/patches/kuttl.config.yaml new file mode 100644 index 0000000..453deec --- /dev/null +++ b/config/scorecard/patches/kuttl.config.yaml @@ -0,0 +1,7 @@ +- op: add + path: /stages/0/tests/- + value: + image: quay.io/operator-framework/scorecard-test-kuttl:v2.0.0 + labels: + suite: kuttlsuite + test: kuttlesuite1 diff --git a/config/scorecard/patches/olm.config.yaml b/config/scorecard/patches/olm.config.yaml new file mode 100644 index 0000000..0e4888c --- /dev/null +++ b/config/scorecard/patches/olm.config.yaml @@ -0,0 +1,50 @@ +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - olm-bundle-validation + image: quay.io/operator-framework/scorecard-test:v1.15.0 + labels: + suite: olm + test: olm-bundle-validation-test +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - olm-crds-have-validation + image: quay.io/operator-framework/scorecard-test:v1.15.0 + labels: + suite: olm + test: olm-crds-have-validation-test +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - olm-crds-have-resources + image: quay.io/operator-framework/scorecard-test:v1.15.0 + labels: + suite: olm + test: olm-crds-have-resources-test +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - olm-spec-descriptors + image: quay.io/operator-framework/scorecard-test:v1.15.0 + labels: + suite: olm + test: olm-spec-descriptors-test +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - olm-status-descriptors + image: quay.io/operator-framework/scorecard-test:v1.15.0 + labels: + suite: olm + test: olm-status-descriptors-test diff --git a/config/webhook/kustomization.yaml b/config/webhook/kustomization.yaml new file mode 100644 index 0000000..9cf2613 --- /dev/null +++ b/config/webhook/kustomization.yaml @@ -0,0 +1,6 @@ +resources: +- manifests.yaml +- service.yaml + +configurations: +- kustomizeconfig.yaml diff --git a/config/webhook/kustomizeconfig.yaml b/config/webhook/kustomizeconfig.yaml new file mode 100644 index 0000000..e809f78 --- /dev/null +++ b/config/webhook/kustomizeconfig.yaml @@ -0,0 +1,18 @@ +# the following config is for teaching kustomize where to look at when substituting vars. +# It requires kustomize v2.1.0 or newer to work properly. +nameReference: +- kind: Service + version: v1 + fieldSpecs: + - kind: ValidatingWebhookConfiguration + group: admissionregistration.k8s.io + path: webhooks/clientConfig/service/name + +namespace: +- kind: ValidatingWebhookConfiguration + group: admissionregistration.k8s.io + path: webhooks/clientConfig/service/namespace + create: true + +varReference: +- path: metadata/annotations diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml new file mode 100644 index 0000000..b56aa3f --- /dev/null +++ b/config/webhook/manifests.yaml @@ -0,0 +1,27 @@ +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + creationTimestamp: null + name: validating-webhook-configuration +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-riskified-com-v1alpha1-dynamicenv + failurePolicy: Fail + name: vdynamicenv.kb.io + rules: + - apiGroups: + - riskified.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - dynamicenvs + sideEffects: None diff --git a/config/webhook/service.yaml b/config/webhook/service.yaml new file mode 100644 index 0000000..3f638bd --- /dev/null +++ b/config/webhook/service.yaml @@ -0,0 +1,13 @@ + +apiVersion: v1 +kind: Service +metadata: + name: webhook-service + namespace: system +spec: + ports: + - port: 443 + protocol: TCP + targetPort: 9443 + selector: + control-plane: controller-manager diff --git a/controllers/dynamicenv_controller.go b/controllers/dynamicenv_controller.go new file mode 100644 index 0000000..37fe192 --- /dev/null +++ b/controllers/dynamicenv_controller.go @@ -0,0 +1,532 @@ +/* +Copyright 2021. + +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" + "fmt" + "sort" + "strings" + "time" + + istioapi "istio.io/api/networking/v1alpha3" + istionetwork "istio.io/client-go/pkg/apis/networking/v1alpha3" + appsv1 "k8s.io/api/apps/v1" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/source" + + riskifiedv1alpha1 "github.com/riskified/dynamic-environment/api/v1alpha1" + "github.com/riskified/dynamic-environment/pkg/handlers" + "github.com/riskified/dynamic-environment/pkg/helpers" + "github.com/riskified/dynamic-environment/pkg/names" + "github.com/riskified/dynamic-environment/pkg/watches" +) + +// DynamicEnvReconciler reconciles a DynamicEnv object +type DynamicEnvReconciler struct { + client.Client + Scheme *runtime.Scheme + VersionLabel string + DefaultVersion string + LabelsToRemove []string +} + +type ReconcileLoopStatus struct { + returnError error + subsetMessages map[string]riskifiedv1alpha1.SubsetMessages + consumerMessages map[string][]string + // Non ready consumers and subsets + nonReadyCS map[string]bool +} + +type SubsetType struct { + Type riskifiedv1alpha1.SubsetOrConsumer + Subset riskifiedv1alpha1.Subset +} + +func (rls *ReconcileLoopStatus) setErrorIfNotMasking(err error) { + if rls.returnError == nil { + rls.returnError = err + } +} + +func (rls *ReconcileLoopStatus) addDeploymentMessage(subset string, tpe riskifiedv1alpha1.SubsetOrConsumer, format string, a ...interface{}) { + if tpe == riskifiedv1alpha1.CONSUMER { + msg := fmt.Sprintf(format, a...) + rls.consumerMessages[subset] = append(rls.consumerMessages[subset], msg) + } else { + rls.subsetMessages[subset] = rls.subsetMessages[subset].AppendDeploymentMsg(format, a...) + } +} + +//+kubebuilder:rbac:groups=riskified.com,resources=dynamicenvs,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=riskified.com,resources=dynamicenvs/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=riskified.com,resources=dynamicenvs/finalizers,verbs=update +//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch +//+kubebuilder:rbac:groups=networking.istio.io,resources=*,verbs=* +//+kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch +// TODO: shrink istio permissions if possible. + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the DynamicEnv object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.10.0/pkg/reconcile +func (r *DynamicEnvReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := ctrllog.FromContext(ctx) + log.V(1).Info("Entered Reconcile Loop") + rls := ReconcileLoopStatus{ + subsetMessages: make(map[string]riskifiedv1alpha1.SubsetMessages), + consumerMessages: make(map[string][]string), + nonReadyCS: make(map[string]bool), + } + + dynamicEnv := &riskifiedv1alpha1.DynamicEnv{} + err := r.Client.Get(ctx, req.NamespacedName, dynamicEnv) + if err != nil { + if errors.IsNotFound(err) { + log.Info("DynamicEnv resource not found. Ignoring since object must be deleted") + return ctrl.Result{}, nil + } + log.Error(err, "Failed to fetch DynamicEnv") + return ctrl.Result{}, fmt.Errorf("failed to fetch dynamic environment: %w", err) + } + + uniqueVersion := helpers.UniqueDynamicEnvName(dynamicEnv.Name, dynamicEnv.Namespace) + + if markedForDeletion(dynamicEnv) { + return r.cleanDynamicEnvResources(ctx, dynamicEnv, uniqueVersion) + } + + if err := r.addFinalizersIfRequired(ctx, dynamicEnv); err != nil { + log.Error(err, "Error adding finalizers") + return ctrl.Result{}, err + } + + owner := types.NamespacedName{Name: dynamicEnv.Name, Namespace: dynamicEnv.Namespace} + var deploymentHandlers []handlers.SRHandler + var mrHandlers []handlers.MRHandler + nonReadyExists := false + degradedExists := false + + statusHandler := handlers.DynamicEnvStatusHandler{ + Client: r.Client, + Ctx: ctx, + DynamicEnv: dynamicEnv, + } + + subsetsAndConsumers := mergeSubsetsAndConsumers(dynamicEnv.Spec.Subsets, dynamicEnv.Spec.Consumers) + + for _, st := range subsetsAndConsumers { + s := st.Subset + uniqueName := s.Name + "-" + uniqueVersion + defaultVersionForSubset := r.DefaultVersion + if s.DefaultVersion != "" { + defaultVersionForSubset = s.DefaultVersion + } + + baseDeployment := &appsv1.Deployment{} + err := r.Get(ctx, types.NamespacedName{Name: s.Name, Namespace: s.Namespace}, baseDeployment) + if err != nil { + log.Error(err, "couldn't find the deployment we need to override", "deployment-name", s.Name, "namespace", s.Namespace) + msg := fmt.Sprintf("couldn't find the deployment we need to override (name: %s, ns: %s)", s.Name, s.Namespace) + rls.returnError = fmt.Errorf("%s: %w", msg, err) + rls.addDeploymentMessage(uniqueName, st.Type, "%s, %v", msg, err) + rls.nonReadyCS[uniqueName] = true + break + } + + deploymentHandler := handlers.DeploymentHandler{ + Client: r.Client, + UniqueName: uniqueName, + UniqueVersion: uniqueVersion, + Owner: owner, + BaseDeployment: baseDeployment, + DeploymentType: st.Type, + LabelsToRemove: r.LabelsToRemove, + VersionLabel: r.VersionLabel, + StatusHandler: &statusHandler, + Matches: dynamicEnv.Spec.IstioMatches, + Subset: s, + Log: log, + Ctx: ctx, + } + deploymentHandlers = append(deploymentHandlers, &deploymentHandler) + if err := deploymentHandler.Handle(); err != nil { + rls.returnError = err + rls.addDeploymentMessage(uniqueName, st.Type, err.Error()) + break + } + + if st.Type == riskifiedv1alpha1.SUBSET { + serviceHosts, err := r.locateMatchingServiceHostnames(ctx, s.Namespace, baseDeployment.Spec.Template.ObjectMeta.Labels) + if err != nil { + msg := fmt.Sprintf("locating service hostname for deployment '%s'", baseDeployment.Name) + rls.returnError = fmt.Errorf("%s: %w", msg, err) + rls.subsetMessages[uniqueName] = rls.subsetMessages[uniqueName].AppendGlobalMsg("%s: %v", msg, err) + break + } + + destinationRuleHandler := handlers.DestinationRuleHandler{ + Client: r.Client, + UniqueName: uniqueName, + UniqueVersion: uniqueVersion, + Namespace: s.Namespace, + VersionLabel: r.VersionLabel, + DefaultVersion: defaultVersionForSubset, + StatusHandler: &statusHandler, + ServiceHosts: serviceHosts, + Owner: owner, + Log: log, + Ctx: ctx, + } + mrHandlers = append(mrHandlers, &destinationRuleHandler) + if err := destinationRuleHandler.Handle(); err != nil { + rls.returnError = err + rls.subsetMessages[uniqueName] = rls.subsetMessages[uniqueName].AppendDestinationRuleMsg(err.Error()) + break + } + + virtualServiceHandler := handlers.VirtualServiceHandler{ + Client: r.Client, + UniqueName: uniqueName, + UniqueVersion: uniqueVersion, + RoutePrefix: helpers.CalculateVirtualServicePrefix(uniqueVersion, s.Name), + Namespace: s.Namespace, + ServiceHosts: serviceHosts, + DefaultVersion: defaultVersionForSubset, + DynamicEnv: dynamicEnv, + StatusHandler: &statusHandler, + Log: log, + Ctx: ctx, + } + + mrHandlers = append(mrHandlers, &virtualServiceHandler) + if err := virtualServiceHandler.Handle(); err != nil { + if errors.IsConflict(err) { + ctrl.Log.V(1).Info("ignoring update error due to version conflict", "error", err) + } else { + log.Error(err, "error updating virtual service for subset", "subset", s.Name) + msg := fmt.Sprintf("error updating virtual service for subset (%s)", uniqueName) + rls.returnError = fmt.Errorf("%s: %w", msg, err) + rls.subsetMessages[uniqueName] = rls.subsetMessages[uniqueName].AppendVirtualServiceMsg("%s: %s", msg, err) + } + } + + commonHostExists := helpers.CommonValueExists(destinationRuleHandler.GetHosts(), virtualServiceHandler.GetHosts()) + if !commonHostExists { + degradedExists = true + rls.nonReadyCS[uniqueName] = true + rls.subsetMessages[uniqueName] = rls.subsetMessages[uniqueName].AppendGlobalMsg("Couldn't find common active service hostname across DestinationRules and VirtualServices") + } + } + } + + for _, handler := range deploymentHandlers { + newStatus, err := handler.GetStatus() + if err != nil { + rls.setErrorIfNotMasking(err) + rls.subsetMessages[handler.GetSubset()] = rls.subsetMessages[handler.GetSubset()].AppendGlobalMsg("error fetching status: %s", err) + continue + } + log.Info("Handler returned status", "status", newStatus) + if err = handler.ApplyStatus(newStatus); err != nil { + log.Error(err, "error updating status", "status", newStatus) + rls.setErrorIfNotMasking(err) + rls.subsetMessages[handler.GetSubset()] = rls.subsetMessages[handler.GetSubset()].AppendGlobalMsg("error updating status: %s", err) + } + if newStatus.Status != riskifiedv1alpha1.Running { + nonReadyExists = true + rls.nonReadyCS[handler.GetSubset()] = true + } + if newStatus.Status.IsFailedStatus() { + degradedExists = true + } + } + for _, handler := range mrHandlers { + statuses, err := handler.GetStatus() + if err != nil { + rls.setErrorIfNotMasking(err) + rls.subsetMessages[handler.GetSubset()] = rls.subsetMessages[handler.GetSubset()].AppendGlobalMsg("error fetching status: %s", err) + continue + } + log.Info("MRHandler returned statuses", "statuses", statuses) + if err = handler.ApplyStatus(statuses); err != nil { + log.Error(err, "error updating status", "statuses", statuses) + rls.setErrorIfNotMasking(err) + rls.subsetMessages[handler.GetSubset()] = rls.subsetMessages[handler.GetSubset()].AppendGlobalMsg("error updating status: %s", err) + } + for _, s := range statuses { + if s.Status.IsFailedStatus() { + rls.nonReadyCS[handler.GetSubset()] = true + degradedExists = true + } + } + } + + globalState := riskifiedv1alpha1.Processing + if !nonReadyExists { + globalState = riskifiedv1alpha1.Ready + } + if degradedExists { + globalState = riskifiedv1alpha1.Degraded + } + if rls.returnError != nil { + globalState = riskifiedv1alpha1.Degraded + } + + statusHandler.SyncSubsetMessagesToStatus(rls.subsetMessages) + statusHandler.SyncConsumerMessagesToStatus(rls.consumerMessages) + if err := statusHandler.SetGlobalState(globalState, len(subsetsAndConsumers), len(rls.nonReadyCS)); err != nil { + log.Error(err, "error setting global state", "global-state", globalState, "subsetMessages", rls.subsetMessages) + rls.setErrorIfNotMasking(err) + } + + if nonReadyExists && rls.returnError == nil { + // Currently we don't get updates on resource's status changes, so we need to requeue. + log.V(1).Info("Requeue because of non running status") + return ctrl.Result{RequeueAfter: 2 * time.Second}, nil + } + return ctrl.Result{}, rls.returnError +} + +func (r *DynamicEnvReconciler) locateMatchingServiceHostnames(ctx context.Context, namespace string, ls labels.Set) (serviceHosts []string, err error) { + + services := v1.ServiceList{} + var matchingServices []v1.Service + if err := r.List(ctx, &services, client.InNamespace(namespace)); err != nil { + return serviceHosts, fmt.Errorf("error fetching services list for namespace %s: %w", namespace, err) + } + for _, service := range services.Items { + var matcher labels.Set = service.Spec.Selector + selector, err := matcher.AsValidatedSelector() + if err != nil { + return serviceHosts, fmt.Errorf("error converting service selector (from: %v): %w", matcher, err) + } + if selector.Matches(ls) { + matchingServices = append(matchingServices, service) + } + } + + switch len(matchingServices) { + case 0: + return serviceHosts, fmt.Errorf("couldn't find service with matching labels: (%v) in namespace: %s", ls, namespace) + default: + for _, sh := range matchingServices { + serviceHosts = append(serviceHosts, sh.Name) + } + // This is mainly to be able to test multiple services in Kuttl, but since I'm not expecting more than a few + // elements the effect is eligible. + sort.Strings(serviceHosts) + return serviceHosts, nil + } +} + +func (r *DynamicEnvReconciler) addFinalizersIfRequired(ctx context.Context, de *riskifiedv1alpha1.DynamicEnv) error { + if len(de.ObjectMeta.Finalizers) == 0 { + de.ObjectMeta.Finalizers = []string{names.DeleteDeployments, names.DeleteDestinationRules, names.CleanupVirtualServices} + if err := r.Update(ctx, de); err != nil { + return fmt.Errorf("could not update finalizers: %w", err) + } + } + return nil +} + +func (r *DynamicEnvReconciler) cleanDynamicEnvResources(ctx context.Context, de *riskifiedv1alpha1.DynamicEnv, version string) (ctrl.Result, error) { + var resources int + log := ctrllog.FromContext(ctx) + log.Info("Dynamic Env marked for deletion, cleaning up ...") + if helpers.StringSliceContains(names.DeleteDeployments, de.Finalizers) { + count, err := r.cleanupDeployments(ctx, de) + if err != nil { + log.Error(err, "error removing cleanupDeployments finalizer") + return ctrl.Result{}, err + } + resources += count + } + if helpers.StringSliceContains(names.DeleteDestinationRules, de.Finalizers) { + count, err := r.cleanupDestinationRules(ctx, de) + if err != nil { + log.Error(err, "error removing DeleteDestinationRules finalizer") + return ctrl.Result{}, err + } + resources += count + } + if helpers.StringSliceContains(names.CleanupVirtualServices, de.Finalizers) { + if err := r.cleanupVirtualServices(ctx, de, version); err != nil { + log.Error(err, "error removing CleanupVirtualServices finalizer") + return ctrl.Result{}, err + } + } + + if resources > 0 { + return ctrl.Result{RequeueAfter: 1 * time.Second}, nil + } + return ctrl.Result{}, nil +} + +// Deletes created deployments on controller deletion and deletes the finalizer once all the deployments are deleted. +// Returns the number of identified resources that are not yet deleted (and error if happened). +func (r *DynamicEnvReconciler) cleanupDeployments(ctx context.Context, de *riskifiedv1alpha1.DynamicEnv) (int, error) { + var deployments []riskifiedv1alpha1.ResourceStatus + var runningCount int + for _, s := range de.Status.SubsetsStatus { + deployments = append(deployments, s.Deployment) + } + for _, c := range de.Status.ConsumersStatus { + deployments = append(deployments, c.ResourceStatus) + } + for _, item := range deployments { + ctrl.Log.Info("Cleaning up deployment ...", "deployment", item) + found := appsv1.Deployment{} + if err := r.Get(ctx, types.NamespacedName{Name: item.Name, Namespace: item.Namespace}, &found); err != nil { // if not found assume deleted + if errors.IsNotFound(err) { + continue + } + return runningCount, fmt.Errorf("error searching for deployment (%v): %w", item, err) + } + runningCount += 1 + if err := r.Delete(ctx, &found); err != nil { + return runningCount, fmt.Errorf("error deleting deployment %v: %w", found, err) + } + } + if runningCount > 0 { + return runningCount, nil + } + return runningCount, r.deleteFinalizer(ctx, names.DeleteDeployments, de) +} + +// Deletes created destination rules on controller deletion. Deletes the finalizer once all DRs are deleted. Returns the +// number of running DRs and error. +func (r *DynamicEnvReconciler) cleanupDestinationRules(ctx context.Context, de *riskifiedv1alpha1.DynamicEnv) (int, error) { + var drs []riskifiedv1alpha1.ResourceStatus + var runningCount int + for _, s := range de.Status.SubsetsStatus { + drs = append(drs, s.DestinationRules...) + } + for _, item := range drs { + ctrl.Log.Info("Cleaning up destination rule ...", "destinationRule", item) + found := istionetwork.DestinationRule{} + if err := r.Get(ctx, types.NamespacedName{Name: item.Name, Namespace: item.Namespace}, &found); err != nil { + if errors.IsNotFound(err) { // if not found assume deleted + continue + } + return runningCount, fmt.Errorf("error searching for destination rule (%v): %w", item, err) + } + runningCount += 1 + if err := r.Delete(ctx, &found); err != nil { + return runningCount, fmt.Errorf("error deleting destination rule %v: %w", &found, err) + } + } + return runningCount, r.deleteFinalizer(ctx, names.DeleteDestinationRules, de) +} + +func (r *DynamicEnvReconciler) cleanupVirtualServices(ctx context.Context, de *riskifiedv1alpha1.DynamicEnv, version string) error { + vss := collectVirtualServices(de) + for _, item := range vss { + ctrl.Log.Info("Cleaning up Virtual Service ...", "virtual-service", item) + found := istionetwork.VirtualService{} + if err := r.Get(ctx, types.NamespacedName{Name: item.Name, Namespace: item.Namespace}, &found); err != nil { + if errors.IsNotFound(err) { + ctrl.Log.Info("Cleanup: Didn't find virtual service. Probably deleted", "virtual-service", item) + continue + } + ctrl.Log.Error(err, "error searching for virtual service during cleanup", "virtual-service", item) + return err + } + var newRoutes []*istioapi.HTTPRoute + for _, route := range found.Spec.Http { + if strings.HasPrefix(route.Name, helpers.CalculateVirtualServicePrefix(version, "")) { + ctrl.Log.V(1).Info("Found route to cleanup", "route", route) + continue + } + newRoutes = append(newRoutes, route) + } + found.Spec.Http = newRoutes + watches.RemoveFromAnnotation(types.NamespacedName{Name: de.Name, Namespace: de.Namespace}, &found) + if err := r.Update(ctx, &found); err != nil { + ctrl.Log.Error(err, "error updating virtual service after cleanup", "virtual-service", found.Name) + return err + } + } + return r.deleteFinalizer(ctx, names.CleanupVirtualServices, de) +} + +func (r *DynamicEnvReconciler) deleteFinalizer(ctx context.Context, finalizer string, de *riskifiedv1alpha1.DynamicEnv) error { + ctrl.Log.Info("Deleting finalizer from dynamic env", "finalizer", finalizer) + remainingFinalizers := helpers.RemoveItemFromStringSlice(finalizer, de.Finalizers) + de.Finalizers = remainingFinalizers + if err := r.Update(ctx, de); err != nil { + return fmt.Errorf("error removing %s finalizer: %w", finalizer, err) + } + return nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *DynamicEnvReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&riskifiedv1alpha1.DynamicEnv{}). + Watches(&source.Kind{Type: &appsv1.Deployment{}}, &watches.EnqueueRequestForAnnotation{}). + Watches(&source.Kind{Type: &istionetwork.DestinationRule{}}, &watches.EnqueueRequestForAnnotation{}). + Watches(&source.Kind{Type: &istionetwork.VirtualService{}}, &watches.EnqueueRequestForAnnotation{}). + WithEventFilter(predicate.GenerationChangedPredicate{}). + Complete(r) +} + +func mergeSubsetsAndConsumers(subsets, consumers []riskifiedv1alpha1.Subset) []SubsetType { + var result []SubsetType + for _, s := range subsets { + st := SubsetType{ + Type: riskifiedv1alpha1.SUBSET, + Subset: s, + } + result = append(result, st) + } + for _, s := range consumers { + st := SubsetType{ + Type: riskifiedv1alpha1.CONSUMER, + Subset: s, + } + result = append(result, st) + } + return result +} + +func collectVirtualServices(de *riskifiedv1alpha1.DynamicEnv) []riskifiedv1alpha1.ResourceStatus { + var result []riskifiedv1alpha1.ResourceStatus + for _, s := range de.Status.SubsetsStatus { + result = append(result, s.VirtualServices...) + } + return result +} + +func markedForDeletion(de *riskifiedv1alpha1.DynamicEnv) bool { + return de.DeletionTimestamp != nil +} diff --git a/controllers/suite_test.go b/controllers/suite_test.go new file mode 100644 index 0000000..5ab0559 --- /dev/null +++ b/controllers/suite_test.go @@ -0,0 +1,92 @@ +/* +Copyright 2021. + +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 ( + "k8s.io/client-go/rest" + "path/filepath" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/client-go/kubernetes/scheme" + "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" + + riskifiedv1alpha1 "github.com/riskified/dynamic-environment/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 cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment + +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", "crd", "bases")}, + ErrorIfCRDPathMissing: true, + } + + var err error + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = riskifiedv1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + // Uncomment the lines below to enable envtest + // err = istio_network.AddToScheme(scheme.Scheme) + // Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + + // Start test controller - Uncomment the lines below to enable envtest + // testManager, err := ctrl.NewManager(cfg, ctrl.Options{ + // Scheme: scheme.Scheme, + // }) + // Expect(err).ToNot(HaveOccurred()) + + // err = (&DynamicEnvReconciler{ + // Client: testManager.GetClient(), + // Scheme: testManager.GetScheme(), + // }).SetupWithManager(testManager) + // Expect(err).ToNot(HaveOccurred()) +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/crd-docs/config.yaml b/crd-docs/config.yaml new file mode 100644 index 0000000..ceb9d61 --- /dev/null +++ b/crd-docs/config.yaml @@ -0,0 +1,19 @@ +processor: + # RE2 regular expressions describing types that should be excluded from the generated documentation. + ignoreTypes: + - "DynamicEnvList$" + - "Condition$" + # RE2 regular expressions describing type fields that should be excluded from the generated documentation. + ignoreFields: + - "TypeMeta$" + - "conditions$" + + +render: + # Version of Kubernetes to use when generating links to Kubernetes API documentation. + kubernetesVersion: 1.26 + # Generate better link for known types + knownTypes: + - name: SecretObjectReference + package: sigs.k8s.io/gateway-api/apis/v1beta1 + link: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.SecretObjectReference diff --git a/crd-docs/crd.md b/crd-docs/crd.md new file mode 100644 index 0000000..8fd3927 --- /dev/null +++ b/crd-docs/crd.md @@ -0,0 +1,218 @@ +# DynamicEnv CRD Reference + +## Packages +- [riskified.com/v1alpha1](#riskifiedcomv1alpha1) + + +## riskified.com/v1alpha1 + +Package v1alpha1 contains API Schema definitions for the riskified v1alpha1 API group + +### Resource Types +- [DynamicEnv](#dynamicenv) + + + +#### ConsumerStatus + + + + + +_Appears in:_ +- [DynamicEnvStatus](#dynamicenvstatus) + +| Field | Description | +| --- | --- | +| `name` _string_ | The name of the resource | +| `namespace` _string_ | The namespace where the resource is created | +| `status` _LifeCycleStatus_ | The life cycle status of the resource | +| `hash` _integer_ | Hash of the current consumer - for internal use | +| `errors` _[StatusError](#statuserror) array_ | List of errors related to the consumer | + + +#### ContainerOverrides + + + +Defines the details of the container on which changes need to be made and the relevant overrides + +_Appears in:_ +- [Subset](#subset) + +| Field | Description | +| --- | --- | +| `containerName` _string_ | Container name to override in multiple containers' environment. If not specified we will use the first container. | +| `image` _string_ | Docker image name overridden to the desired subset The Docker image found in the original deployment is used if this is not provided. | +| `command` _string array_ | Entrypoint array overridden to the desired subset The docker image's ENTRYPOINT is used if this is not provided. | +| `env` _[EnvVar](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#envvar-v1-core) array_ | Additional environment variable to the given deployment | + + +#### DynamicEnv + + + +DynamicEnv is the Schema for the dynamicenvs API + + + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `riskified.com/v1alpha1` | +| `kind` _string_ | `DynamicEnv` | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[DynamicEnvSpec](#dynamicenvspec)_ | | +| `status` _[DynamicEnvStatus](#dynamicenvstatus)_ | | + + +#### DynamicEnvSpec + + + +DynamicEnvSpec defines the desired state of DynamicEnv + +_Appears in:_ +- [DynamicEnv](#dynamicenv) + +| Field | Description | +| --- | --- | +| `istioMatches` _[IstioMatch](#istiomatch) array_ | A list of matchers (partly corresponds to IstioMatch). Each match will have a rule of its own (merged with existing rules) ordered by their order here. | +| `subsets` _[Subset](#subset) array_ | Who should participate in the given dynamic environment | +| `consumers` _[Subset](#subset) array_ | Consumers are like subsets but for deployments that do not open a service but connect to external resources for their work (e.g, offline workers). They are equivalent to subsets in the sense that they launch overriding deployments with custom image and/or settings. However, since they are only consumers no virtual service or destination route will be pointing to them. | + + +#### DynamicEnvStatus + + + +DynamicEnvStatus defines the observed state of DynamicEnv + +_Appears in:_ +- [DynamicEnv](#dynamicenv) + +| Field | Description | +| --- | --- | +| `subsetsStatus` _object (keys:string, values:[SubsetStatus](#subsetstatus))_ | | +| `consumersStatus` _object (keys:string, values:[ConsumerStatus](#consumerstatus))_ | | +| `state` _GlobalReadyStatus_ | | +| `totalCount` _integer_ | desired subsets and consumers count | +| `totalReady` _integer_ | number of available subsets and consumers | + + +#### IstioMatch + + + +specifies a set of criterion to be met in order for the rule to be applied to the HTTP request This field is immutable after creation. + +_Appears in:_ +- [DynamicEnvSpec](#dynamicenvspec) + +| Field | Description | +| --- | --- | +| `headers` _object (keys:string, values:[StringMatch](#stringmatch))_ | Header values are case-sensitive and formatted as follows:
- `exact: "value"` for exact string match
- `prefix: "value"` for prefix-based match
- `regex: "value"` for RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax). | +| `sourceLabels` _object (keys:string, values:string)_ | One or more labels that constrain the applicability of a rule to source (client) workloads with the given labels. | + + +#### ResourceStatus + + + +ResourceStatus shows the status of each item created/edited by DynamicEnv + +_Appears in:_ +- [ConsumerStatus](#consumerstatus) +- [SubsetStatus](#subsetstatus) + +| Field | Description | +| --- | --- | +| `name` _string_ | The name of the resource | +| `namespace` _string_ | The namespace where the resource is created | +| `status` _LifeCycleStatus_ | The life cycle status of the resource | + + +#### StatusError + + + +StatusError shows an error we want to display in the status with the last time it happened. This *does not* have to be the only time it happened. The idea is that a list of errors should only contain single occurrence of an error (just the last). + +_Appears in:_ +- [ConsumerStatus](#consumerstatus) +- [SubsetErrors](#subseterrors) + + + +#### StringMatch + + + +Describes how to match a given string in HTTP headers. Match is case-sensitive. one and only one of the fields needs to be defined (oneof) + +_Appears in:_ +- [IstioMatch](#istiomatch) + +| Field | Description | +| --- | --- | +| `exact` _string_ | | +| `prefix` _string_ | | +| `regex` _string_ | | + + +#### Subset + + + +Subsets defines how to generate subsets from existing Deployments + +_Appears in:_ +- [DynamicEnvSpec](#dynamicenvspec) + +| Field | Description | +| --- | --- | +| `name` _string_ | Deployment name (without namespace) | +| `namespace` _string_ | Namespace where the deployment is deployed | +| `podLabels` _object (keys:string, values:string)_ | Labels to add to the pods of the deployment launched by this subset. Could be used in conjunction with 'SourceLabels' in the `IstioMatches`. | +| `replicas` _integer_ | Number of deployment replicas. Default is 1. Note: 0 is *invalid*. | +| `containers` _[ContainerOverrides](#containeroverrides) array_ | A list of container overrides (at least one of Containers or InitContainers must not be empty) | +| `initContainers` _[ContainerOverrides](#containeroverrides) array_ | A list of init container overrides (at least one of Containers or InitContainers must not be empty) | +| `defaultVersion` _string_ | Default version for this subset (if different then the global default version). This is the version that will get the default route. | + + +#### SubsetErrors + + + +SubsetErrors contains all global errors related to set subset. + +_Appears in:_ +- [SubsetStatus](#subsetstatus) + +| Field | Description | +| --- | --- | +| `deployment` _[StatusError](#statuserror) array_ | Subset's deployment global errors. | +| `destinationRule` _[StatusError](#statuserror) array_ | Subset's destination-rule global errors. | +| `virtualServices` _[StatusError](#statuserror) array_ | Subset's virtual-services global errors. | +| `subset` _[StatusError](#statuserror) array_ | Errors related to subset but not to any of the launched resources | + + + + +#### SubsetStatus + + + +SubsetStatus Contains aggregation of all resources status connected to set subset. + +_Appears in:_ +- [DynamicEnvStatus](#dynamicenvstatus) + +| Field | Description | +| --- | --- | +| `deployment` _[ResourceStatus](#resourcestatus)_ | Status of the deployment that belongs to the subset | +| `destinationRules` _[ResourceStatus](#resourcestatus) array_ | Status of the destination-rule that belongs to the subset | +| `virtualServices` _[ResourceStatus](#resourcestatus) array_ | Status of the virtual-service that belongs to the subset | +| `subsetErrors` _[SubsetErrors](#subseterrors)_ | A list of global errors related to subset resources | +| `hash` _integer_ | Hash of the current subset - for internal use | + + diff --git a/crd-docs/templates/markdown/gv_details.tpl b/crd-docs/templates/markdown/gv_details.tpl new file mode 100644 index 0000000..30ad0d7 --- /dev/null +++ b/crd-docs/templates/markdown/gv_details.tpl @@ -0,0 +1,19 @@ +{{- define "gvDetails" -}} +{{- $gv := . -}} + +## {{ $gv.GroupVersionString }} + +{{ $gv.Doc }} + +{{- if $gv.Kinds }} +### Resource Types +{{- range $gv.SortedKinds }} +- {{ $gv.TypeForKind . | markdownRenderTypeLink }} +{{- end }} +{{ end }} + +{{ range $gv.SortedTypes }} +{{ template "type" . }} +{{ end }} + +{{- end -}} diff --git a/crd-docs/templates/markdown/gv_list.tpl b/crd-docs/templates/markdown/gv_list.tpl new file mode 100644 index 0000000..c4e51e0 --- /dev/null +++ b/crd-docs/templates/markdown/gv_list.tpl @@ -0,0 +1,15 @@ +{{- define "gvList" -}} +{{- $groupVersions := . -}} + +# DynamicEnv CRD Reference + +## Packages +{{- range $groupVersions }} +- {{ markdownRenderGVLink . }} +{{- end }} + +{{ range $groupVersions }} +{{ template "gvDetails" . }} +{{ end }} + +{{- end -}} diff --git a/crd-docs/templates/markdown/type.tpl b/crd-docs/templates/markdown/type.tpl new file mode 100644 index 0000000..3306930 --- /dev/null +++ b/crd-docs/templates/markdown/type.tpl @@ -0,0 +1,33 @@ +{{- define "type" -}} +{{- $type := . -}} +{{- if markdownShouldRenderType $type -}} + +#### {{ $type.Name }} + +{{ if $type.IsAlias }}_Underlying type:_ `{{ markdownRenderTypeLink $type.UnderlyingType }}`{{ end }} + +{{ $type.Doc }} + +{{ if $type.References -}} +_Appears in:_ +{{- range $type.SortedReferences }} +- {{ markdownRenderTypeLink . }} +{{- end }} +{{- end }} + +{{ if $type.Members -}} +| Field | Description | +| --- | --- | +{{ if $type.GVK -}} +| `apiVersion` _string_ | `{{ $type.GVK.Group }}/{{ $type.GVK.Version }}` | +| `kind` _string_ | `{{ $type.GVK.Kind }}` | +{{ end -}} + +{{ range $type.Members -}} +| `{{ .Name }}` _{{ markdownRenderType .Type }}_ | {{ template "type_members" . }} | +{{ end -}} + +{{ end -}} + +{{- end -}} +{{- end -}} diff --git a/crd-docs/templates/markdown/type_members.tpl b/crd-docs/templates/markdown/type_members.tpl new file mode 100644 index 0000000..182fa18 --- /dev/null +++ b/crd-docs/templates/markdown/type_members.tpl @@ -0,0 +1,8 @@ +{{- define "type_members" -}} +{{- $field := . -}} +{{- if eq $field.Name "metadata" -}} +Refer to Kubernetes API documentation for fields of `metadata`. +{{- else -}} +{{ $field.Doc }} +{{- end -}} +{{- end -}} diff --git a/dev-resources/0-namespace.yaml b/dev-resources/0-namespace.yaml new file mode 100644 index 0000000..dfe833a --- /dev/null +++ b/dev-resources/0-namespace.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + labels: + istio-injection: enabled + name: services diff --git a/dev-resources/1-bookinfo.yaml b/dev-resources/1-bookinfo.yaml new file mode 100644 index 0000000..b1edc3e --- /dev/null +++ b/dev-resources/1-bookinfo.yaml @@ -0,0 +1,286 @@ +# Copyright Istio Authors +# +# 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. + +################################################################################################## +# This file defines the services, service accounts, and deployments for the Bookinfo sample. +# +# To apply all 4 Bookinfo services, their corresponding service accounts, and deployments: +# +# kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml +# +# Alternatively, you can deploy any resource separately: +# +# kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml -l service=reviews # reviews Service +# kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml -l account=reviews # reviews ServiceAccount +# kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml -l app=reviews,version=v3 # reviews-v3 Deployment +################################################################################################## + +################################################################################################## +# Details service +################################################################################################## +apiVersion: v1 +kind: Service +metadata: + name: details + labels: + app: details + service: details +spec: + ports: + - port: 9080 + name: http + selector: + app: details +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-details + labels: + account: details +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details-worker + labels: + app: details + version: shared +spec: + replicas: 1 + selector: + matchLabels: + app: details-worker + version: shared + template: + metadata: + labels: + app: details-worker + version: shared + spec: + serviceAccountName: bookinfo-details + containers: + - name: details + image: docker.io/istio/examples-bookinfo-details-v1:1.16.2 + imagePullPolicy: IfNotPresent + securityContext: + runAsUser: 1000 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details + labels: + app: details + version: shared +spec: + replicas: 1 + selector: + matchLabels: + app: details + version: shared + template: + metadata: + labels: + app: details + version: shared + spec: + serviceAccountName: bookinfo-details + containers: + - name: details + image: docker.io/istio/examples-bookinfo-details-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + securityContext: + runAsUser: 1000 +--- +################################################################################################## +# Ratings service +################################################################################################## +apiVersion: v1 +kind: Service +metadata: + name: ratings + labels: + app: ratings + service: ratings +spec: + ports: + - port: 9080 + name: http + selector: + app: ratings +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-ratings + labels: + account: ratings +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ratings + labels: + app: ratings + version: shared +spec: + replicas: 1 + selector: + matchLabels: + app: ratings + version: shared + template: + metadata: + labels: + app: ratings + version: shared + spec: + serviceAccountName: bookinfo-ratings + containers: + - name: ratings + image: docker.io/istio/examples-bookinfo-ratings-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + securityContext: + runAsUser: 1000 +--- +################################################################################################## +# Reviews service +################################################################################################## +apiVersion: v1 +kind: Service +metadata: + name: reviews + labels: + app: reviews + service: reviews +spec: + ports: + - port: 9080 + name: http + selector: + app: reviews +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-reviews + labels: + account: reviews +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: reviews + labels: + app: reviews + version: shared +spec: + replicas: 1 + selector: + matchLabels: + app: reviews + version: shared + template: + metadata: + labels: + app: reviews + version: shared + spec: + serviceAccountName: bookinfo-reviews + containers: + - name: reviews + image: docker.io/istio/examples-bookinfo-reviews-v2:1.16.2 + imagePullPolicy: IfNotPresent + env: + - name: LOG_DIR + value: "/tmp/logs" + ports: + - containerPort: 9080 + volumeMounts: + - name: tmp + mountPath: /tmp + - name: wlp-output + mountPath: /opt/ibm/wlp/output + securityContext: + runAsUser: 1000 + volumes: + - name: wlp-output + emptyDir: {} + - name: tmp + emptyDir: {} +--- +################################################################################################## +# Productpage services +################################################################################################## +apiVersion: v1 +kind: Service +metadata: + name: productpage + labels: + app: productpage + service: productpage +spec: + ports: + - port: 9080 + name: http + selector: + app: productpage +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-productpage + labels: + account: productpage +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: productpage + labels: + app: productpage + version: shared +spec: + replicas: 1 + selector: + matchLabels: + app: productpage + version: shared + template: + metadata: + labels: + app: productpage + version: shared + spec: + serviceAccountName: bookinfo-productpage + containers: + - name: productpage + image: docker.io/istio/examples-bookinfo-productpage-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + volumeMounts: + - name: tmp + mountPath: /tmp + securityContext: + runAsUser: 1000 + volumes: + - name: tmp + emptyDir: {} +--- diff --git a/dev-resources/2-destination-rule-all.yaml b/dev-resources/2-destination-rule-all.yaml new file mode 100644 index 0000000..d8ad5a0 --- /dev/null +++ b/dev-resources/2-destination-rule-all.yaml @@ -0,0 +1,44 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: productpage +spec: + host: productpage + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: reviews +spec: + host: reviews + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: ratings +spec: + host: ratings + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details +spec: + host: details + subsets: + - name: shared + labels: + version: shared +--- \ No newline at end of file diff --git a/dev-resources/3-virtual-service-all-v1.yaml b/dev-resources/3-virtual-service-all-v1.yaml new file mode 100644 index 0000000..d753e2d --- /dev/null +++ b/dev-resources/3-virtual-service-all-v1.yaml @@ -0,0 +1,52 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: productpage +spec: + hosts: + - productpage + http: + - route: + - destination: + host: productpage + subset: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: reviews +spec: + hosts: + - reviews + http: + - route: + - destination: + host: reviews + subset: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: ratings +spec: + hosts: + - ratings + http: + - route: + - destination: + host: ratings + subset: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: details +spec: + hosts: + - details + http: + - route: + - destination: + host: details + subset: shared +--- \ No newline at end of file diff --git a/e2e-testing/dependencies/cert-manager_1.8.yaml b/e2e-testing/dependencies/cert-manager_1.8.yaml new file mode 100644 index 0000000..d1cdaf9 --- /dev/null +++ b/e2e-testing/dependencies/cert-manager_1.8.yaml @@ -0,0 +1,5388 @@ +# Copyright 2021 The cert-manager Authors. +# +# 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. + +apiVersion: v1 +kind: Namespace +metadata: + name: cert-manager +--- +# Source: cert-manager/templates/crd-templates.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: certificaterequests.cert-manager.io + labels: + app: 'cert-manager' + app.kubernetes.io/name: 'cert-manager' + app.kubernetes.io/instance: 'cert-manager' + # Generated labels + app.kubernetes.io/version: "v1.8.0" +spec: + group: cert-manager.io + names: + kind: CertificateRequest + listKind: CertificateRequestList + plural: certificaterequests + shortNames: + - cr + - crs + singular: certificaterequest + categories: + - cert-manager + scope: Namespaced + versions: + - name: v1 + subresources: + status: {} + additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Approved")].status + name: Approved + type: string + - jsonPath: .status.conditions[?(@.type=="Denied")].status + name: Denied + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .spec.issuerRef.name + name: Issuer + type: string + - jsonPath: .spec.username + name: Requestor + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status + priority: 1 + type: string + - jsonPath: .metadata.creationTimestamp + description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. + name: Age + type: date + schema: + openAPIV3Schema: + description: "A CertificateRequest is used to request a signed certificate from one of the configured issuers. \n All fields within the CertificateRequest's `spec` are immutable after creation. A CertificateRequest will either succeed or fail, as denoted by its `status.state` field. \n A CertificateRequest is a one-shot resource, meaning it represents a single point in time request for a certificate and cannot be re-used." + type: object + required: + - spec + 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: Desired state of the CertificateRequest resource. + type: object + required: + - issuerRef + - request + properties: + duration: + description: The requested 'duration' (i.e. lifetime) of the Certificate. This option may be ignored/overridden by some issuer types. + type: string + extra: + description: Extra contains extra attributes of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable. + type: object + additionalProperties: + type: array + items: + type: string + groups: + description: Groups contains group membership of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable. + type: array + items: + type: string + x-kubernetes-list-type: atomic + isCA: + description: IsCA will request to mark the certificate as valid for certificate signing when submitting to the issuer. This will automatically add the `cert sign` usage to the list of `usages`. + type: boolean + issuerRef: + description: IssuerRef is a reference to the issuer for this CertificateRequest. If the `kind` field is not set, or set to `Issuer`, an Issuer resource with the given name in the same namespace as the CertificateRequest will be used. If the `kind` field is set to `ClusterIssuer`, a ClusterIssuer with the provided name will be used. The `name` field in this stanza is required at all times. The group field refers to the API group of the issuer which defaults to `cert-manager.io` if empty. + type: object + required: + - name + properties: + group: + description: Group of the resource being referred to. + type: string + kind: + description: Kind of the resource being referred to. + type: string + name: + description: Name of the resource being referred to. + type: string + request: + description: The PEM-encoded x509 certificate signing request to be submitted to the CA for signing. + type: string + format: byte + uid: + description: UID contains the uid of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable. + type: string + usages: + description: Usages is the set of x509 usages that are requested for the certificate. If usages are set they SHOULD be encoded inside the CSR spec Defaults to `digital signature` and `key encipherment` if not specified. + type: array + items: + description: 'KeyUsage specifies valid usage contexts for keys. See: https://tools.ietf.org/html/rfc5280#section-4.2.1.3 https://tools.ietf.org/html/rfc5280#section-4.2.1.12 Valid KeyUsage values are as follows: "signing", "digital signature", "content commitment", "key encipherment", "key agreement", "data encipherment", "cert sign", "crl sign", "encipher only", "decipher only", "any", "server auth", "client auth", "code signing", "email protection", "s/mime", "ipsec end system", "ipsec tunnel", "ipsec user", "timestamping", "ocsp signing", "microsoft sgc", "netscape sgc"' + type: string + enum: + - signing + - digital signature + - content commitment + - key encipherment + - key agreement + - data encipherment + - cert sign + - crl sign + - encipher only + - decipher only + - any + - server auth + - client auth + - code signing + - email protection + - s/mime + - ipsec end system + - ipsec tunnel + - ipsec user + - timestamping + - ocsp signing + - microsoft sgc + - netscape sgc + username: + description: Username contains the name of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable. + type: string + status: + description: Status of the CertificateRequest. This is set and managed automatically. + type: object + properties: + ca: + description: The PEM encoded x509 certificate of the signer, also known as the CA (Certificate Authority). This is set on a best-effort basis by different issuers. If not set, the CA is assumed to be unknown/not available. + type: string + format: byte + certificate: + description: The PEM encoded x509 certificate resulting from the certificate signing request. If not set, the CertificateRequest has either not been completed or has failed. More information on failure can be found by checking the `conditions` field. + type: string + format: byte + conditions: + description: List of status conditions to indicate the status of a CertificateRequest. Known condition types are `Ready` and `InvalidRequest`. + type: array + items: + description: CertificateRequestCondition contains condition information for a CertificateRequest. + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: LastTransitionTime is the timestamp corresponding to the last status change of this condition. + type: string + format: date-time + message: + description: Message is a human readable description of the details of the last transition, complementing reason. + type: string + reason: + description: Reason is a brief machine readable explanation for the condition's last transition. + type: string + status: + description: Status of the condition, one of (`True`, `False`, `Unknown`). + type: string + enum: + - "True" + - "False" + - Unknown + type: + description: Type of the condition, known values are (`Ready`, `InvalidRequest`, `Approved`, `Denied`). + type: string + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + failureTime: + description: FailureTime stores the time that this CertificateRequest failed. This is used to influence garbage collection and back-off. + type: string + format: date-time + served: true + storage: true +--- +# Source: cert-manager/templates/crd-templates.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: certificates.cert-manager.io + labels: + app: 'cert-manager' + app.kubernetes.io/name: 'cert-manager' + app.kubernetes.io/instance: 'cert-manager' + # Generated labels + app.kubernetes.io/version: "v1.8.0" +spec: + group: cert-manager.io + names: + kind: Certificate + listKind: CertificateList + plural: certificates + shortNames: + - cert + - certs + singular: certificate + categories: + - cert-manager + scope: Namespaced + versions: + - name: v1 + subresources: + status: {} + additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .spec.secretName + name: Secret + type: string + - jsonPath: .spec.issuerRef.name + name: Issuer + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status + priority: 1 + type: string + - jsonPath: .metadata.creationTimestamp + description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. + name: Age + type: date + schema: + openAPIV3Schema: + description: "A Certificate resource should be created to ensure an up to date and signed x509 certificate is stored in the Kubernetes Secret resource named in `spec.secretName`. \n The stored certificate will be renewed before it expires (as configured by `spec.renewBefore`)." + type: object + required: + - spec + 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: Desired state of the Certificate resource. + type: object + required: + - issuerRef + - secretName + properties: + additionalOutputFormats: + description: AdditionalOutputFormats defines extra output formats of the private key and signed certificate chain to be written to this Certificate's target Secret. This is an Alpha Feature and is only enabled with the `--feature-gates=AdditionalCertificateOutputFormats=true` option on both the controller and webhook components. + type: array + items: + description: CertificateAdditionalOutputFormat defines an additional output format of a Certificate resource. These contain supplementary data formats of the signed certificate chain and paired private key. + type: object + required: + - type + properties: + type: + description: Type is the name of the format type that should be written to the Certificate's target Secret. + type: string + enum: + - DER + - CombinedPEM + commonName: + description: 'CommonName is a common name to be used on the Certificate. The CommonName should have a length of 64 characters or fewer to avoid generating invalid CSRs. This value is ignored by TLS clients when any subject alt name is set. This is x509 behaviour: https://tools.ietf.org/html/rfc6125#section-6.4.4' + type: string + dnsNames: + description: DNSNames is a list of DNS subjectAltNames to be set on the Certificate. + type: array + items: + type: string + duration: + description: The requested 'duration' (i.e. lifetime) of the Certificate. This option may be ignored/overridden by some issuer types. If unset this defaults to 90 days. Certificate will be renewed either 2/3 through its duration or `renewBefore` period before its expiry, whichever is later. Minimum accepted duration is 1 hour. Value must be in units accepted by Go time.ParseDuration https://golang.org/pkg/time/#ParseDuration + type: string + emailAddresses: + description: EmailAddresses is a list of email subjectAltNames to be set on the Certificate. + type: array + items: + type: string + encodeUsagesInRequest: + description: EncodeUsagesInRequest controls whether key usages should be present in the CertificateRequest + type: boolean + ipAddresses: + description: IPAddresses is a list of IP address subjectAltNames to be set on the Certificate. + type: array + items: + type: string + isCA: + description: IsCA will mark this Certificate as valid for certificate signing. This will automatically add the `cert sign` usage to the list of `usages`. + type: boolean + issuerRef: + description: IssuerRef is a reference to the issuer for this certificate. If the `kind` field is not set, or set to `Issuer`, an Issuer resource with the given name in the same namespace as the Certificate will be used. If the `kind` field is set to `ClusterIssuer`, a ClusterIssuer with the provided name will be used. The `name` field in this stanza is required at all times. + type: object + required: + - name + properties: + group: + description: Group of the resource being referred to. + type: string + kind: + description: Kind of the resource being referred to. + type: string + name: + description: Name of the resource being referred to. + type: string + keystores: + description: Keystores configures additional keystore output formats stored in the `secretName` Secret resource. + type: object + properties: + jks: + description: JKS configures options for storing a JKS keystore in the `spec.secretName` Secret resource. + type: object + required: + - create + - passwordSecretRef + properties: + create: + description: Create enables JKS keystore creation for the Certificate. If true, a file named `keystore.jks` will be created in the target Secret resource, encrypted using the password stored in `passwordSecretRef`. The keystore file will only be updated upon re-issuance. A file named `truststore.jks` will also be created in the target Secret resource, encrypted using the password stored in `passwordSecretRef` containing the issuing Certificate Authority + type: boolean + passwordSecretRef: + description: PasswordSecretRef is a reference to a key in a Secret resource containing the password used to encrypt the JKS keystore. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + pkcs12: + description: PKCS12 configures options for storing a PKCS12 keystore in the `spec.secretName` Secret resource. + type: object + required: + - create + - passwordSecretRef + properties: + create: + description: Create enables PKCS12 keystore creation for the Certificate. If true, a file named `keystore.p12` will be created in the target Secret resource, encrypted using the password stored in `passwordSecretRef`. The keystore file will only be updated upon re-issuance. A file named `truststore.p12` will also be created in the target Secret resource, encrypted using the password stored in `passwordSecretRef` containing the issuing Certificate Authority + type: boolean + passwordSecretRef: + description: PasswordSecretRef is a reference to a key in a Secret resource containing the password used to encrypt the PKCS12 keystore. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + privateKey: + description: Options to control private keys used for the Certificate. + type: object + properties: + algorithm: + description: Algorithm is the private key algorithm of the corresponding private key for this certificate. If provided, allowed values are either `RSA`,`Ed25519` or `ECDSA` If `algorithm` is specified and `size` is not provided, key size of 256 will be used for `ECDSA` key algorithm and key size of 2048 will be used for `RSA` key algorithm. key size is ignored when using the `Ed25519` key algorithm. + type: string + enum: + - RSA + - ECDSA + - Ed25519 + encoding: + description: The private key cryptography standards (PKCS) encoding for this certificate's private key to be encoded in. If provided, allowed values are `PKCS1` and `PKCS8` standing for PKCS#1 and PKCS#8, respectively. Defaults to `PKCS1` if not specified. + type: string + enum: + - PKCS1 + - PKCS8 + rotationPolicy: + description: RotationPolicy controls how private keys should be regenerated when a re-issuance is being processed. If set to Never, a private key will only be generated if one does not already exist in the target `spec.secretName`. If one does exists but it does not have the correct algorithm or size, a warning will be raised to await user intervention. If set to Always, a private key matching the specified requirements will be generated whenever a re-issuance occurs. Default is 'Never' for backward compatibility. + type: string + enum: + - Never + - Always + size: + description: Size is the key bit size of the corresponding private key for this certificate. If `algorithm` is set to `RSA`, valid values are `2048`, `4096` or `8192`, and will default to `2048` if not specified. If `algorithm` is set to `ECDSA`, valid values are `256`, `384` or `521`, and will default to `256` if not specified. If `algorithm` is set to `Ed25519`, Size is ignored. No other values are allowed. + type: integer + renewBefore: + description: How long before the currently issued certificate's expiry cert-manager should renew the certificate. The default is 2/3 of the issued certificate's duration. Minimum accepted value is 5 minutes. Value must be in units accepted by Go time.ParseDuration https://golang.org/pkg/time/#ParseDuration + type: string + revisionHistoryLimit: + description: revisionHistoryLimit is the maximum number of CertificateRequest revisions that are maintained in the Certificate's history. Each revision represents a single `CertificateRequest` created by this Certificate, either when it was created, renewed, or Spec was changed. Revisions will be removed by oldest first if the number of revisions exceeds this number. If set, revisionHistoryLimit must be a value of `1` or greater. If unset (`nil`), revisions will not be garbage collected. Default value is `nil`. + type: integer + format: int32 + secretName: + description: SecretName is the name of the secret resource that will be automatically created and managed by this Certificate resource. It will be populated with a private key and certificate, signed by the denoted issuer. + type: string + secretTemplate: + description: SecretTemplate defines annotations and labels to be copied to the Certificate's Secret. Labels and annotations on the Secret will be changed as they appear on the SecretTemplate when added or removed. SecretTemplate annotations are added in conjunction with, and cannot overwrite, the base set of annotations cert-manager sets on the Certificate's Secret. + type: object + properties: + annotations: + description: Annotations is a key value map to be copied to the target Kubernetes Secret. + type: object + additionalProperties: + type: string + labels: + description: Labels is a key value map to be copied to the target Kubernetes Secret. + type: object + additionalProperties: + type: string + subject: + description: Full X509 name specification (https://golang.org/pkg/crypto/x509/pkix/#Name). + type: object + properties: + countries: + description: Countries to be used on the Certificate. + type: array + items: + type: string + localities: + description: Cities to be used on the Certificate. + type: array + items: + type: string + organizationalUnits: + description: Organizational Units to be used on the Certificate. + type: array + items: + type: string + organizations: + description: Organizations to be used on the Certificate. + type: array + items: + type: string + postalCodes: + description: Postal codes to be used on the Certificate. + type: array + items: + type: string + provinces: + description: State/Provinces to be used on the Certificate. + type: array + items: + type: string + serialNumber: + description: Serial number to be used on the Certificate. + type: string + streetAddresses: + description: Street addresses to be used on the Certificate. + type: array + items: + type: string + uris: + description: URIs is a list of URI subjectAltNames to be set on the Certificate. + type: array + items: + type: string + usages: + description: Usages is the set of x509 usages that are requested for the certificate. Defaults to `digital signature` and `key encipherment` if not specified. + type: array + items: + description: 'KeyUsage specifies valid usage contexts for keys. See: https://tools.ietf.org/html/rfc5280#section-4.2.1.3 https://tools.ietf.org/html/rfc5280#section-4.2.1.12 Valid KeyUsage values are as follows: "signing", "digital signature", "content commitment", "key encipherment", "key agreement", "data encipherment", "cert sign", "crl sign", "encipher only", "decipher only", "any", "server auth", "client auth", "code signing", "email protection", "s/mime", "ipsec end system", "ipsec tunnel", "ipsec user", "timestamping", "ocsp signing", "microsoft sgc", "netscape sgc"' + type: string + enum: + - signing + - digital signature + - content commitment + - key encipherment + - key agreement + - data encipherment + - cert sign + - crl sign + - encipher only + - decipher only + - any + - server auth + - client auth + - code signing + - email protection + - s/mime + - ipsec end system + - ipsec tunnel + - ipsec user + - timestamping + - ocsp signing + - microsoft sgc + - netscape sgc + status: + description: Status of the Certificate. This is set and managed automatically. + type: object + properties: + conditions: + description: List of status conditions to indicate the status of certificates. Known condition types are `Ready` and `Issuing`. + type: array + items: + description: CertificateCondition contains condition information for an Certificate. + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: LastTransitionTime is the timestamp corresponding to the last status change of this condition. + type: string + format: date-time + message: + description: Message is a human readable description of the details of the last transition, complementing reason. + type: string + observedGeneration: + description: If set, this represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the Certificate. + type: integer + format: int64 + reason: + description: Reason is a brief machine readable explanation for the condition's last transition. + type: string + status: + description: Status of the condition, one of (`True`, `False`, `Unknown`). + type: string + enum: + - "True" + - "False" + - Unknown + type: + description: Type of the condition, known values are (`Ready`, `Issuing`). + type: string + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + failedIssuanceAttempts: + description: The number of continuous failed issuance attempts up till now. This field gets removed (if set) on a successful issuance and gets set to 1 if unset and an issuance has failed. If an issuance has failed, the delay till the next issuance will be calculated using formula time.Hour * 2 ^ (failedIssuanceAttempts - 1). + type: integer + lastFailureTime: + description: LastFailureTime is the time as recorded by the Certificate controller of the most recent failure to complete a CertificateRequest for this Certificate resource. If set, cert-manager will not re-request another Certificate until 1 hour has elapsed from this time. + type: string + format: date-time + nextPrivateKeySecretName: + description: The name of the Secret resource containing the private key to be used for the next certificate iteration. The keymanager controller will automatically set this field if the `Issuing` condition is set to `True`. It will automatically unset this field when the Issuing condition is not set or False. + type: string + notAfter: + description: The expiration time of the certificate stored in the secret named by this resource in `spec.secretName`. + type: string + format: date-time + notBefore: + description: The time after which the certificate stored in the secret named by this resource in spec.secretName is valid. + type: string + format: date-time + renewalTime: + description: RenewalTime is the time at which the certificate will be next renewed. If not set, no upcoming renewal is scheduled. + type: string + format: date-time + revision: + description: "The current 'revision' of the certificate as issued. \n When a CertificateRequest resource is created, it will have the `cert-manager.io/certificate-revision` set to one greater than the current value of this field. \n Upon issuance, this field will be set to the value of the annotation on the CertificateRequest resource used to issue the certificate. \n Persisting the value on the CertificateRequest resource allows the certificates controller to know whether a request is part of an old issuance or if it is part of the ongoing revision's issuance by checking if the revision value in the annotation is greater than this field." + type: integer + served: true + storage: true +--- +# Source: cert-manager/templates/crd-templates.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: challenges.acme.cert-manager.io + labels: + app: 'cert-manager' + app.kubernetes.io/name: 'cert-manager' + app.kubernetes.io/instance: 'cert-manager' + # Generated labels + app.kubernetes.io/version: "v1.8.0" +spec: + group: acme.cert-manager.io + names: + kind: Challenge + listKind: ChallengeList + plural: challenges + singular: challenge + categories: + - cert-manager + - cert-manager-acme + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.state + name: State + type: string + - jsonPath: .spec.dnsName + name: Domain + type: string + - jsonPath: .status.reason + name: Reason + priority: 1 + type: string + - description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: Challenge is a type to represent a Challenge request with an ACME server + type: object + required: + - metadata + - spec + 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: + type: object + required: + - authorizationURL + - dnsName + - issuerRef + - key + - solver + - token + - type + - url + properties: + authorizationURL: + description: The URL to the ACME Authorization resource that this challenge is a part of. + type: string + dnsName: + description: dnsName is the identifier that this challenge is for, e.g. example.com. If the requested DNSName is a 'wildcard', this field MUST be set to the non-wildcard domain, e.g. for `*.example.com`, it must be `example.com`. + type: string + issuerRef: + description: References a properly configured ACME-type Issuer which should be used to create this Challenge. If the Issuer does not exist, processing will be retried. If the Issuer is not an 'ACME' Issuer, an error will be returned and the Challenge will be marked as failed. + type: object + required: + - name + properties: + group: + description: Group of the resource being referred to. + type: string + kind: + description: Kind of the resource being referred to. + type: string + name: + description: Name of the resource being referred to. + type: string + key: + description: 'The ACME challenge key for this challenge For HTTP01 challenges, this is the value that must be responded with to complete the HTTP01 challenge in the format: `.`. For DNS01 challenges, this is the base64 encoded SHA256 sum of the `.` text that must be set as the TXT record content.' + type: string + solver: + description: Contains the domain solving configuration that should be used to solve this challenge resource. + type: object + properties: + dns01: + description: Configures cert-manager to attempt to complete authorizations by performing the DNS01 challenge flow. + type: object + properties: + acmeDNS: + description: Use the 'ACME DNS' (https://github.com/joohoi/acme-dns) API to manage DNS01 challenge records. + type: object + required: + - accountSecretRef + - host + properties: + accountSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + host: + type: string + akamai: + description: Use the Akamai DNS zone management API to manage DNS01 challenge records. + type: object + required: + - accessTokenSecretRef + - clientSecretSecretRef + - clientTokenSecretRef + - serviceConsumerDomain + properties: + accessTokenSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + clientSecretSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + clientTokenSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + serviceConsumerDomain: + type: string + azureDNS: + description: Use the Microsoft Azure DNS API to manage DNS01 challenge records. + type: object + required: + - resourceGroupName + - subscriptionID + properties: + clientID: + description: if both this and ClientSecret are left unset MSI will be used + type: string + clientSecretSecretRef: + description: if both this and ClientID are left unset MSI will be used + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + environment: + description: name of the Azure environment (default AzurePublicCloud) + type: string + enum: + - AzurePublicCloud + - AzureChinaCloud + - AzureGermanCloud + - AzureUSGovernmentCloud + hostedZoneName: + description: name of the DNS zone that should be used + type: string + managedIdentity: + description: managed identity configuration, can not be used at the same time as clientID, clientSecretSecretRef or tenantID + type: object + properties: + clientID: + description: client ID of the managed identity, can not be used at the same time as resourceID + type: string + resourceID: + description: resource ID of the managed identity, can not be used at the same time as clientID + type: string + resourceGroupName: + description: resource group the DNS zone is located in + type: string + subscriptionID: + description: ID of the Azure subscription + type: string + tenantID: + description: when specifying ClientID and ClientSecret then this field is also needed + type: string + cloudDNS: + description: Use the Google Cloud DNS API to manage DNS01 challenge records. + type: object + required: + - project + properties: + hostedZoneName: + description: HostedZoneName is an optional field that tells cert-manager in which Cloud DNS zone the challenge record has to be created. If left empty cert-manager will automatically choose a zone. + type: string + project: + type: string + serviceAccountSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + cloudflare: + description: Use the Cloudflare API to manage DNS01 challenge records. + type: object + properties: + apiKeySecretRef: + description: 'API key to use to authenticate with Cloudflare. Note: using an API token to authenticate is now the recommended method as it allows greater control of permissions.' + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + apiTokenSecretRef: + description: API token used to authenticate with Cloudflare. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + email: + description: Email of the account, only required when using API key based authentication. + type: string + cnameStrategy: + description: CNAMEStrategy configures how the DNS01 provider should handle CNAME records when found in DNS zones. + type: string + enum: + - None + - Follow + digitalocean: + description: Use the DigitalOcean DNS API to manage DNS01 challenge records. + type: object + required: + - tokenSecretRef + properties: + tokenSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + rfc2136: + description: Use RFC2136 ("Dynamic Updates in the Domain Name System") (https://datatracker.ietf.org/doc/rfc2136/) to manage DNS01 challenge records. + type: object + required: + - nameserver + properties: + nameserver: + description: The IP address or hostname of an authoritative DNS server supporting RFC2136 in the form host:port. If the host is an IPv6 address it must be enclosed in square brackets (e.g [2001:db8::1]) ; port is optional. This field is required. + type: string + tsigAlgorithm: + description: 'The TSIG Algorithm configured in the DNS supporting RFC2136. Used only when ``tsigSecretSecretRef`` and ``tsigKeyName`` are defined. Supported values are (case-insensitive): ``HMACMD5`` (default), ``HMACSHA1``, ``HMACSHA256`` or ``HMACSHA512``.' + type: string + tsigKeyName: + description: The TSIG Key name configured in the DNS. If ``tsigSecretSecretRef`` is defined, this field is required. + type: string + tsigSecretSecretRef: + description: The name of the secret containing the TSIG value. If ``tsigKeyName`` is defined, this field is required. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + route53: + description: Use the AWS Route53 API to manage DNS01 challenge records. + type: object + required: + - region + properties: + accessKeyID: + description: 'The AccessKeyID is used for authentication. If not set we fall-back to using env vars, shared credentials file or AWS Instance metadata see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials' + type: string + hostedZoneID: + description: If set, the provider will manage only this zone in Route53 and will not do an lookup using the route53:ListHostedZonesByName api call. + type: string + region: + description: Always set the region when using AccessKeyID and SecretAccessKey + type: string + role: + description: Role is a Role ARN which the Route53 provider will assume using either the explicit credentials AccessKeyID/SecretAccessKey or the inferred credentials from environment variables, shared credentials file or AWS Instance metadata + type: string + secretAccessKeySecretRef: + description: The SecretAccessKey is used for authentication. If not set we fall-back to using env vars, shared credentials file or AWS Instance metadata https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + webhook: + description: Configure an external webhook based DNS01 challenge solver to manage DNS01 challenge records. + type: object + required: + - groupName + - solverName + properties: + config: + description: Additional configuration that should be passed to the webhook apiserver when challenges are processed. This can contain arbitrary JSON data. Secret values should not be specified in this stanza. If secret values are needed (e.g. credentials for a DNS service), you should use a SecretKeySelector to reference a Secret resource. For details on the schema of this field, consult the webhook provider implementation's documentation. + x-kubernetes-preserve-unknown-fields: true + groupName: + description: The API group name that should be used when POSTing ChallengePayload resources to the webhook apiserver. This should be the same as the GroupName specified in the webhook provider implementation. + type: string + solverName: + description: The name of the solver to use, as defined in the webhook provider implementation. This will typically be the name of the provider, e.g. 'cloudflare'. + type: string + http01: + description: Configures cert-manager to attempt to complete authorizations by performing the HTTP01 challenge flow. It is not possible to obtain certificates for wildcard domain names (e.g. `*.example.com`) using the HTTP01 challenge mechanism. + type: object + properties: + gatewayHTTPRoute: + description: The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create HTTPRoutes with the specified labels in the same namespace as the challenge. This solver is experimental, and fields / behaviour may change in the future. + type: object + properties: + labels: + description: Custom labels that will be applied to HTTPRoutes created by cert-manager while solving HTTP-01 challenges. + type: object + additionalProperties: + type: string + parentRefs: + description: 'When solving an HTTP-01 challenge, cert-manager creates an HTTPRoute. cert-manager needs to know which parentRefs should be used when creating the HTTPRoute. Usually, the parentRef references a Gateway. See: https://gateway-api.sigs.k8s.io/v1alpha2/api-types/httproute/#attaching-to-gateways' + type: array + items: + description: "ParentRef identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid. \n References to objects with invalid Group and Kind are not valid, and must be rejected by the implementation, with appropriate Conditions set on the containing object." + type: object + required: + - name + properties: + group: + description: "Group is the group of the referent. \n Support: Core" + type: string + default: gateway.networking.k8s.io + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + kind: + description: "Kind is kind of the referent. \n Support: Core (Gateway) Support: Custom (Other Resources)" + type: string + default: Gateway + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + name: + description: "Name is the name of the referent. \n Support: Core" + type: string + maxLength: 253 + minLength: 1 + namespace: + description: "Namespace is the namespace of the referent. When unspecified (or empty string), this refers to the local namespace of the Route. \n Support: Core" + type: string + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + type: string + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + serviceType: + description: Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + ingress: + description: The ingress based HTTP01 challenge solver will solve challenges by creating or modifying Ingress resources in order to route requests for '/.well-known/acme-challenge/XYZ' to 'challenge solver' pods that are provisioned by cert-manager for each Challenge to be completed. + type: object + properties: + class: + description: The ingress class to use when creating Ingress resources to solve ACME challenges that use this challenge solver. Only one of 'class' or 'name' may be specified. + type: string + ingressTemplate: + description: Optional ingress template used to configure the ACME challenge solver ingress used for HTTP01 challenges. + type: object + properties: + metadata: + description: ObjectMeta overrides for the ingress used to solve HTTP01 challenges. Only the 'labels' and 'annotations' fields may be set. If labels or annotations overlap with in-built values, the values here will override the in-built values. + type: object + properties: + annotations: + description: Annotations that should be added to the created ACME HTTP01 solver ingress. + type: object + additionalProperties: + type: string + labels: + description: Labels that should be added to the created ACME HTTP01 solver ingress. + type: object + additionalProperties: + type: string + name: + description: The name of the ingress resource that should have ACME challenge solving routes inserted into it in order to solve HTTP01 challenges. This is typically used in conjunction with ingress controllers like ingress-gce, which maintains a 1:1 mapping between external IPs and ingress resources. + type: string + podTemplate: + description: Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges. + type: object + properties: + metadata: + description: ObjectMeta overrides for the pod used to solve HTTP01 challenges. Only the 'labels' and 'annotations' fields may be set. If labels or annotations overlap with in-built values, the values here will override the in-built values. + type: object + properties: + annotations: + description: Annotations that should be added to the create ACME HTTP01 solver pods. + type: object + additionalProperties: + type: string + labels: + description: Labels that should be added to the created ACME HTTP01 solver pods. + type: object + additionalProperties: + type: string + spec: + description: PodSpec defines overrides for the HTTP01 challenge solver pod. Only the 'priorityClassName', 'nodeSelector', 'affinity', 'serviceAccountName' and 'tolerations' fields are supported currently. All other fields will be ignored. + type: object + properties: + affinity: + description: If specified, the pod's scheduling constraints + type: object + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the pod. + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred. + type: array + items: + description: An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + type: object + required: + - preference + - weight + properties: + preference: + description: A node selector term, associated with the corresponding weight. + type: object + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchFields: + description: A list of node selector requirements by node's fields. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + weight: + description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node. + type: object + required: + - nodeSelectorTerms + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. The terms are ORed. + type: array + items: + description: A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + type: object + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchFields: + description: A list of node selector requirements by node's fields. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. + type: array + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + type: object + required: + - podAffinityTerm + - weight + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. This field is beta-level and is only honored when PodAffinityNamespaceSelector feature is enabled. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace" + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + weight: + description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. + type: array + items: + description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. This field is beta-level and is only honored when PodAffinityNamespaceSelector feature is enabled. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace" + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. + type: array + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + type: object + required: + - podAffinityTerm + - weight + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. This field is beta-level and is only honored when PodAffinityNamespaceSelector feature is enabled. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace" + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + weight: + description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. + type: array + items: + description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. This field is beta-level and is only honored when PodAffinityNamespaceSelector feature is enabled. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace" + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + nodeSelector: + description: 'NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node''s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + additionalProperties: + type: string + priorityClassName: + description: If specified, the pod's priorityClassName. + type: string + serviceAccountName: + description: If specified, the pod's service account + type: string + tolerations: + description: If specified, the pod's tolerations. + type: array + items: + description: The pod this Toleration is attached to tolerates any taint that matches the triple using the matching operator . + type: object + properties: + effect: + description: Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system. + type: integer + format: int64 + value: + description: Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + serviceType: + description: Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + selector: + description: Selector selects a set of DNSNames on the Certificate resource that should be solved using this challenge solver. If not specified, the solver will be treated as the 'default' solver with the lowest priority, i.e. if any other solver has a more specific match, it will be used instead. + type: object + properties: + dnsNames: + description: List of DNSNames that this solver will be used to solve. If specified and a match is found, a dnsNames selector will take precedence over a dnsZones selector. If multiple solvers match with the same dnsNames value, the solver with the most matching labels in matchLabels will be selected. If neither has more matches, the solver defined earlier in the list will be selected. + type: array + items: + type: string + dnsZones: + description: List of DNSZones that this solver will be used to solve. The most specific DNS zone match specified here will take precedence over other DNS zone matches, so a solver specifying sys.example.com will be selected over one specifying example.com for the domain www.sys.example.com. If multiple solvers match with the same dnsZones value, the solver with the most matching labels in matchLabels will be selected. If neither has more matches, the solver defined earlier in the list will be selected. + type: array + items: + type: string + matchLabels: + description: A label selector that is used to refine the set of certificate's that this challenge solver will apply to. + type: object + additionalProperties: + type: string + token: + description: The ACME challenge token for this challenge. This is the raw value returned from the ACME server. + type: string + type: + description: The type of ACME challenge this resource represents. One of "HTTP-01" or "DNS-01". + type: string + enum: + - HTTP-01 + - DNS-01 + url: + description: The URL of the ACME Challenge resource for this challenge. This can be used to lookup details about the status of this challenge. + type: string + wildcard: + description: wildcard will be true if this challenge is for a wildcard identifier, for example '*.example.com'. + type: boolean + status: + type: object + properties: + presented: + description: presented will be set to true if the challenge values for this challenge are currently 'presented'. This *does not* imply the self check is passing. Only that the values have been 'submitted' for the appropriate challenge mechanism (i.e. the DNS01 TXT record has been presented, or the HTTP01 configuration has been configured). + type: boolean + processing: + description: Used to denote whether this challenge should be processed or not. This field will only be set to true by the 'scheduling' component. It will only be set to false by the 'challenges' controller, after the challenge has reached a final state or timed out. If this field is set to false, the challenge controller will not take any more action. + type: boolean + reason: + description: Contains human readable information on why the Challenge is in the current state. + type: string + state: + description: Contains the current 'state' of the challenge. If not set, the state of the challenge is unknown. + type: string + enum: + - valid + - ready + - pending + - processing + - invalid + - expired + - errored + served: true + storage: true + subresources: + status: {} +--- +# Source: cert-manager/templates/crd-templates.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: clusterissuers.cert-manager.io + labels: + app: 'cert-manager' + app.kubernetes.io/name: 'cert-manager' + app.kubernetes.io/instance: 'cert-manager' + # Generated labels + app.kubernetes.io/version: "v1.8.0" +spec: + group: cert-manager.io + names: + kind: ClusterIssuer + listKind: ClusterIssuerList + plural: clusterissuers + singular: clusterissuer + categories: + - cert-manager + scope: Cluster + versions: + - name: v1 + subresources: + status: {} + additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status + priority: 1 + type: string + - jsonPath: .metadata.creationTimestamp + description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. + name: Age + type: date + schema: + openAPIV3Schema: + description: A ClusterIssuer represents a certificate issuing authority which can be referenced as part of `issuerRef` fields. It is similar to an Issuer, however it is cluster-scoped and therefore can be referenced by resources that exist in *any* namespace, not just the same namespace as the referent. + type: object + required: + - spec + 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: Desired state of the ClusterIssuer resource. + type: object + properties: + acme: + description: ACME configures this issuer to communicate with a RFC8555 (ACME) server to obtain signed x509 certificates. + type: object + required: + - privateKeySecretRef + - server + properties: + disableAccountKeyGeneration: + description: Enables or disables generating a new ACME account key. If true, the Issuer resource will *not* request a new account but will expect the account key to be supplied via an existing secret. If false, the cert-manager system will generate a new ACME account key for the Issuer. Defaults to false. + type: boolean + email: + description: Email is the email address to be associated with the ACME account. This field is optional, but it is strongly recommended to be set. It will be used to contact you in case of issues with your account or certificates, including expiry notification emails. This field may be updated after the account is initially registered. + type: string + enableDurationFeature: + description: Enables requesting a Not After date on certificates that matches the duration of the certificate. This is not supported by all ACME servers like Let's Encrypt. If set to true when the ACME server does not support it it will create an error on the Order. Defaults to false. + type: boolean + externalAccountBinding: + description: ExternalAccountBinding is a reference to a CA external account of the ACME server. If set, upon registration cert-manager will attempt to associate the given external account credentials with the registered ACME account. + type: object + required: + - keyID + - keySecretRef + properties: + keyAlgorithm: + description: 'Deprecated: keyAlgorithm field exists for historical compatibility reasons and should not be used. The algorithm is now hardcoded to HS256 in golang/x/crypto/acme.' + type: string + enum: + - HS256 + - HS384 + - HS512 + keyID: + description: keyID is the ID of the CA key that the External Account is bound to. + type: string + keySecretRef: + description: keySecretRef is a Secret Key Selector referencing a data item in a Kubernetes Secret which holds the symmetric MAC key of the External Account Binding. The `key` is the index string that is paired with the key data in the Secret and should not be confused with the key data itself, or indeed with the External Account Binding keyID above. The secret key stored in the Secret **must** be un-padded, base64 URL encoded data. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + preferredChain: + description: 'PreferredChain is the chain to use if the ACME server outputs multiple. PreferredChain is no guarantee that this one gets delivered by the ACME endpoint. For example, for Let''s Encrypt''s DST crosssign you would use: "DST Root CA X3" or "ISRG Root X1" for the newer Let''s Encrypt root CA. This value picks the first certificate bundle in the ACME alternative chains that has a certificate with this value as its issuer''s CN' + type: string + maxLength: 64 + privateKeySecretRef: + description: PrivateKey is the name of a Kubernetes Secret resource that will be used to store the automatically generated ACME account private key. Optionally, a `key` may be specified to select a specific entry within the named Secret resource. If `key` is not specified, a default of `tls.key` will be used. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + server: + description: 'Server is the URL used to access the ACME server''s ''directory'' endpoint. For example, for Let''s Encrypt''s staging endpoint, you would use: "https://acme-staging-v02.api.letsencrypt.org/directory". Only ACME v2 endpoints (i.e. RFC 8555) are supported.' + type: string + skipTLSVerify: + description: Enables or disables validation of the ACME server TLS certificate. If true, requests to the ACME server will not have their TLS certificate validated (i.e. insecure connections will be allowed). Only enable this option in development environments. The cert-manager system installed roots will be used to verify connections to the ACME server if this is false. Defaults to false. + type: boolean + solvers: + description: 'Solvers is a list of challenge solvers that will be used to solve ACME challenges for the matching domains. Solver configurations must be provided in order to obtain certificates from an ACME server. For more information, see: https://cert-manager.io/docs/configuration/acme/' + type: array + items: + description: An ACMEChallengeSolver describes how to solve ACME challenges for the issuer it is part of. A selector may be provided to use different solving strategies for different DNS names. Only one of HTTP01 or DNS01 must be provided. + type: object + properties: + dns01: + description: Configures cert-manager to attempt to complete authorizations by performing the DNS01 challenge flow. + type: object + properties: + acmeDNS: + description: Use the 'ACME DNS' (https://github.com/joohoi/acme-dns) API to manage DNS01 challenge records. + type: object + required: + - accountSecretRef + - host + properties: + accountSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + host: + type: string + akamai: + description: Use the Akamai DNS zone management API to manage DNS01 challenge records. + type: object + required: + - accessTokenSecretRef + - clientSecretSecretRef + - clientTokenSecretRef + - serviceConsumerDomain + properties: + accessTokenSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + clientSecretSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + clientTokenSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + serviceConsumerDomain: + type: string + azureDNS: + description: Use the Microsoft Azure DNS API to manage DNS01 challenge records. + type: object + required: + - resourceGroupName + - subscriptionID + properties: + clientID: + description: if both this and ClientSecret are left unset MSI will be used + type: string + clientSecretSecretRef: + description: if both this and ClientID are left unset MSI will be used + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + environment: + description: name of the Azure environment (default AzurePublicCloud) + type: string + enum: + - AzurePublicCloud + - AzureChinaCloud + - AzureGermanCloud + - AzureUSGovernmentCloud + hostedZoneName: + description: name of the DNS zone that should be used + type: string + managedIdentity: + description: managed identity configuration, can not be used at the same time as clientID, clientSecretSecretRef or tenantID + type: object + properties: + clientID: + description: client ID of the managed identity, can not be used at the same time as resourceID + type: string + resourceID: + description: resource ID of the managed identity, can not be used at the same time as clientID + type: string + resourceGroupName: + description: resource group the DNS zone is located in + type: string + subscriptionID: + description: ID of the Azure subscription + type: string + tenantID: + description: when specifying ClientID and ClientSecret then this field is also needed + type: string + cloudDNS: + description: Use the Google Cloud DNS API to manage DNS01 challenge records. + type: object + required: + - project + properties: + hostedZoneName: + description: HostedZoneName is an optional field that tells cert-manager in which Cloud DNS zone the challenge record has to be created. If left empty cert-manager will automatically choose a zone. + type: string + project: + type: string + serviceAccountSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + cloudflare: + description: Use the Cloudflare API to manage DNS01 challenge records. + type: object + properties: + apiKeySecretRef: + description: 'API key to use to authenticate with Cloudflare. Note: using an API token to authenticate is now the recommended method as it allows greater control of permissions.' + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + apiTokenSecretRef: + description: API token used to authenticate with Cloudflare. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + email: + description: Email of the account, only required when using API key based authentication. + type: string + cnameStrategy: + description: CNAMEStrategy configures how the DNS01 provider should handle CNAME records when found in DNS zones. + type: string + enum: + - None + - Follow + digitalocean: + description: Use the DigitalOcean DNS API to manage DNS01 challenge records. + type: object + required: + - tokenSecretRef + properties: + tokenSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + rfc2136: + description: Use RFC2136 ("Dynamic Updates in the Domain Name System") (https://datatracker.ietf.org/doc/rfc2136/) to manage DNS01 challenge records. + type: object + required: + - nameserver + properties: + nameserver: + description: The IP address or hostname of an authoritative DNS server supporting RFC2136 in the form host:port. If the host is an IPv6 address it must be enclosed in square brackets (e.g [2001:db8::1]) ; port is optional. This field is required. + type: string + tsigAlgorithm: + description: 'The TSIG Algorithm configured in the DNS supporting RFC2136. Used only when ``tsigSecretSecretRef`` and ``tsigKeyName`` are defined. Supported values are (case-insensitive): ``HMACMD5`` (default), ``HMACSHA1``, ``HMACSHA256`` or ``HMACSHA512``.' + type: string + tsigKeyName: + description: The TSIG Key name configured in the DNS. If ``tsigSecretSecretRef`` is defined, this field is required. + type: string + tsigSecretSecretRef: + description: The name of the secret containing the TSIG value. If ``tsigKeyName`` is defined, this field is required. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + route53: + description: Use the AWS Route53 API to manage DNS01 challenge records. + type: object + required: + - region + properties: + accessKeyID: + description: 'The AccessKeyID is used for authentication. If not set we fall-back to using env vars, shared credentials file or AWS Instance metadata see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials' + type: string + hostedZoneID: + description: If set, the provider will manage only this zone in Route53 and will not do an lookup using the route53:ListHostedZonesByName api call. + type: string + region: + description: Always set the region when using AccessKeyID and SecretAccessKey + type: string + role: + description: Role is a Role ARN which the Route53 provider will assume using either the explicit credentials AccessKeyID/SecretAccessKey or the inferred credentials from environment variables, shared credentials file or AWS Instance metadata + type: string + secretAccessKeySecretRef: + description: The SecretAccessKey is used for authentication. If not set we fall-back to using env vars, shared credentials file or AWS Instance metadata https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + webhook: + description: Configure an external webhook based DNS01 challenge solver to manage DNS01 challenge records. + type: object + required: + - groupName + - solverName + properties: + config: + description: Additional configuration that should be passed to the webhook apiserver when challenges are processed. This can contain arbitrary JSON data. Secret values should not be specified in this stanza. If secret values are needed (e.g. credentials for a DNS service), you should use a SecretKeySelector to reference a Secret resource. For details on the schema of this field, consult the webhook provider implementation's documentation. + x-kubernetes-preserve-unknown-fields: true + groupName: + description: The API group name that should be used when POSTing ChallengePayload resources to the webhook apiserver. This should be the same as the GroupName specified in the webhook provider implementation. + type: string + solverName: + description: The name of the solver to use, as defined in the webhook provider implementation. This will typically be the name of the provider, e.g. 'cloudflare'. + type: string + http01: + description: Configures cert-manager to attempt to complete authorizations by performing the HTTP01 challenge flow. It is not possible to obtain certificates for wildcard domain names (e.g. `*.example.com`) using the HTTP01 challenge mechanism. + type: object + properties: + gatewayHTTPRoute: + description: The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create HTTPRoutes with the specified labels in the same namespace as the challenge. This solver is experimental, and fields / behaviour may change in the future. + type: object + properties: + labels: + description: Custom labels that will be applied to HTTPRoutes created by cert-manager while solving HTTP-01 challenges. + type: object + additionalProperties: + type: string + parentRefs: + description: 'When solving an HTTP-01 challenge, cert-manager creates an HTTPRoute. cert-manager needs to know which parentRefs should be used when creating the HTTPRoute. Usually, the parentRef references a Gateway. See: https://gateway-api.sigs.k8s.io/v1alpha2/api-types/httproute/#attaching-to-gateways' + type: array + items: + description: "ParentRef identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid. \n References to objects with invalid Group and Kind are not valid, and must be rejected by the implementation, with appropriate Conditions set on the containing object." + type: object + required: + - name + properties: + group: + description: "Group is the group of the referent. \n Support: Core" + type: string + default: gateway.networking.k8s.io + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + kind: + description: "Kind is kind of the referent. \n Support: Core (Gateway) Support: Custom (Other Resources)" + type: string + default: Gateway + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + name: + description: "Name is the name of the referent. \n Support: Core" + type: string + maxLength: 253 + minLength: 1 + namespace: + description: "Namespace is the namespace of the referent. When unspecified (or empty string), this refers to the local namespace of the Route. \n Support: Core" + type: string + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + type: string + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + serviceType: + description: Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + ingress: + description: The ingress based HTTP01 challenge solver will solve challenges by creating or modifying Ingress resources in order to route requests for '/.well-known/acme-challenge/XYZ' to 'challenge solver' pods that are provisioned by cert-manager for each Challenge to be completed. + type: object + properties: + class: + description: The ingress class to use when creating Ingress resources to solve ACME challenges that use this challenge solver. Only one of 'class' or 'name' may be specified. + type: string + ingressTemplate: + description: Optional ingress template used to configure the ACME challenge solver ingress used for HTTP01 challenges. + type: object + properties: + metadata: + description: ObjectMeta overrides for the ingress used to solve HTTP01 challenges. Only the 'labels' and 'annotations' fields may be set. If labels or annotations overlap with in-built values, the values here will override the in-built values. + type: object + properties: + annotations: + description: Annotations that should be added to the created ACME HTTP01 solver ingress. + type: object + additionalProperties: + type: string + labels: + description: Labels that should be added to the created ACME HTTP01 solver ingress. + type: object + additionalProperties: + type: string + name: + description: The name of the ingress resource that should have ACME challenge solving routes inserted into it in order to solve HTTP01 challenges. This is typically used in conjunction with ingress controllers like ingress-gce, which maintains a 1:1 mapping between external IPs and ingress resources. + type: string + podTemplate: + description: Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges. + type: object + properties: + metadata: + description: ObjectMeta overrides for the pod used to solve HTTP01 challenges. Only the 'labels' and 'annotations' fields may be set. If labels or annotations overlap with in-built values, the values here will override the in-built values. + type: object + properties: + annotations: + description: Annotations that should be added to the create ACME HTTP01 solver pods. + type: object + additionalProperties: + type: string + labels: + description: Labels that should be added to the created ACME HTTP01 solver pods. + type: object + additionalProperties: + type: string + spec: + description: PodSpec defines overrides for the HTTP01 challenge solver pod. Only the 'priorityClassName', 'nodeSelector', 'affinity', 'serviceAccountName' and 'tolerations' fields are supported currently. All other fields will be ignored. + type: object + properties: + affinity: + description: If specified, the pod's scheduling constraints + type: object + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the pod. + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred. + type: array + items: + description: An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + type: object + required: + - preference + - weight + properties: + preference: + description: A node selector term, associated with the corresponding weight. + type: object + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchFields: + description: A list of node selector requirements by node's fields. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + weight: + description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node. + type: object + required: + - nodeSelectorTerms + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. The terms are ORed. + type: array + items: + description: A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + type: object + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchFields: + description: A list of node selector requirements by node's fields. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. + type: array + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + type: object + required: + - podAffinityTerm + - weight + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. This field is beta-level and is only honored when PodAffinityNamespaceSelector feature is enabled. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace" + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + weight: + description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. + type: array + items: + description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. This field is beta-level and is only honored when PodAffinityNamespaceSelector feature is enabled. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace" + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. + type: array + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + type: object + required: + - podAffinityTerm + - weight + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. This field is beta-level and is only honored when PodAffinityNamespaceSelector feature is enabled. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace" + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + weight: + description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. + type: array + items: + description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. This field is beta-level and is only honored when PodAffinityNamespaceSelector feature is enabled. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace" + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + nodeSelector: + description: 'NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node''s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + additionalProperties: + type: string + priorityClassName: + description: If specified, the pod's priorityClassName. + type: string + serviceAccountName: + description: If specified, the pod's service account + type: string + tolerations: + description: If specified, the pod's tolerations. + type: array + items: + description: The pod this Toleration is attached to tolerates any taint that matches the triple using the matching operator . + type: object + properties: + effect: + description: Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system. + type: integer + format: int64 + value: + description: Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + serviceType: + description: Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + selector: + description: Selector selects a set of DNSNames on the Certificate resource that should be solved using this challenge solver. If not specified, the solver will be treated as the 'default' solver with the lowest priority, i.e. if any other solver has a more specific match, it will be used instead. + type: object + properties: + dnsNames: + description: List of DNSNames that this solver will be used to solve. If specified and a match is found, a dnsNames selector will take precedence over a dnsZones selector. If multiple solvers match with the same dnsNames value, the solver with the most matching labels in matchLabels will be selected. If neither has more matches, the solver defined earlier in the list will be selected. + type: array + items: + type: string + dnsZones: + description: List of DNSZones that this solver will be used to solve. The most specific DNS zone match specified here will take precedence over other DNS zone matches, so a solver specifying sys.example.com will be selected over one specifying example.com for the domain www.sys.example.com. If multiple solvers match with the same dnsZones value, the solver with the most matching labels in matchLabels will be selected. If neither has more matches, the solver defined earlier in the list will be selected. + type: array + items: + type: string + matchLabels: + description: A label selector that is used to refine the set of certificate's that this challenge solver will apply to. + type: object + additionalProperties: + type: string + ca: + description: CA configures this issuer to sign certificates using a signing CA keypair stored in a Secret resource. This is used to build internal PKIs that are managed by cert-manager. + type: object + required: + - secretName + properties: + crlDistributionPoints: + description: The CRL distribution points is an X.509 v3 certificate extension which identifies the location of the CRL from which the revocation of this certificate can be checked. If not set, certificates will be issued without distribution points set. + type: array + items: + type: string + ocspServers: + description: The OCSP server list is an X.509 v3 extension that defines a list of URLs of OCSP responders. The OCSP responders can be queried for the revocation status of an issued certificate. If not set, the certificate will be issued with no OCSP servers set. For example, an OCSP server URL could be "http://ocsp.int-x3.letsencrypt.org". + type: array + items: + type: string + secretName: + description: SecretName is the name of the secret used to sign Certificates issued by this Issuer. + type: string + selfSigned: + description: SelfSigned configures this issuer to 'self sign' certificates using the private key used to create the CertificateRequest object. + type: object + properties: + crlDistributionPoints: + description: The CRL distribution points is an X.509 v3 certificate extension which identifies the location of the CRL from which the revocation of this certificate can be checked. If not set certificate will be issued without CDP. Values are strings. + type: array + items: + type: string + vault: + description: Vault configures this issuer to sign certificates using a HashiCorp Vault PKI backend. + type: object + required: + - auth + - path + - server + properties: + auth: + description: Auth configures how cert-manager authenticates with the Vault server. + type: object + properties: + appRole: + description: AppRole authenticates with Vault using the App Role auth mechanism, with the role and secret stored in a Kubernetes Secret resource. + type: object + required: + - path + - roleId + - secretRef + properties: + path: + description: 'Path where the App Role authentication backend is mounted in Vault, e.g: "approle"' + type: string + roleId: + description: RoleID configured in the App Role authentication backend when setting up the authentication backend in Vault. + type: string + secretRef: + description: Reference to a key in a Secret that contains the App Role secret used to authenticate with Vault. The `key` field must be specified and denotes which entry within the Secret resource is used as the app role secret. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + kubernetes: + description: Kubernetes authenticates with Vault by passing the ServiceAccount token stored in the named Secret resource to the Vault server. + type: object + required: + - role + - secretRef + properties: + mountPath: + description: The Vault mountPath here is the mount path to use when authenticating with Vault. For example, setting a value to `/v1/auth/foo`, will use the path `/v1/auth/foo/login` to authenticate with Vault. If unspecified, the default value "/v1/auth/kubernetes" will be used. + type: string + role: + description: A required field containing the Vault Role to assume. A Role binds a Kubernetes ServiceAccount with a set of Vault policies. + type: string + secretRef: + description: The required Secret field containing a Kubernetes ServiceAccount JWT used for authenticating with Vault. Use of 'ambient credentials' is not supported. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + tokenSecretRef: + description: TokenSecretRef authenticates with Vault by presenting a token. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + caBundle: + description: PEM-encoded CA bundle (base64-encoded) used to validate Vault server certificate. Only used if the Server URL is using HTTPS protocol. This parameter is ignored for plain HTTP protocol connection. If not set the system root certificates are used to validate the TLS connection. + type: string + format: byte + namespace: + description: 'Name of the vault namespace. Namespaces is a set of features within Vault Enterprise that allows Vault environments to support Secure Multi-tenancy. e.g: "ns1" More about namespaces can be found here https://www.vaultproject.io/docs/enterprise/namespaces' + type: string + path: + description: 'Path is the mount path of the Vault PKI backend''s `sign` endpoint, e.g: "my_pki_mount/sign/my-role-name".' + type: string + server: + description: 'Server is the connection address for the Vault server, e.g: "https://vault.example.com:8200".' + type: string + venafi: + description: Venafi configures this issuer to sign certificates using a Venafi TPP or Venafi Cloud policy zone. + type: object + required: + - zone + properties: + cloud: + description: Cloud specifies the Venafi cloud configuration settings. Only one of TPP or Cloud may be specified. + type: object + required: + - apiTokenSecretRef + properties: + apiTokenSecretRef: + description: APITokenSecretRef is a secret key selector for the Venafi Cloud API token. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + url: + description: URL is the base URL for Venafi Cloud. Defaults to "https://api.venafi.cloud/v1". + type: string + tpp: + description: TPP specifies Trust Protection Platform configuration settings. Only one of TPP or Cloud may be specified. + type: object + required: + - credentialsRef + - url + properties: + caBundle: + description: CABundle is a PEM encoded TLS certificate to use to verify connections to the TPP instance. If specified, system roots will not be used and the issuing CA for the TPP instance must be verifiable using the provided root. If not specified, the connection will be verified using the cert-manager system root certificates. + type: string + format: byte + credentialsRef: + description: CredentialsRef is a reference to a Secret containing the username and password for the TPP server. The secret must contain two keys, 'username' and 'password'. + type: object + required: + - name + properties: + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + url: + description: 'URL is the base URL for the vedsdk endpoint of the Venafi TPP instance, for example: "https://tpp.example.com/vedsdk".' + type: string + zone: + description: Zone is the Venafi Policy Zone to use for this issuer. All requests made to the Venafi platform will be restricted by the named zone policy. This field is required. + type: string + status: + description: Status of the ClusterIssuer. This is set and managed automatically. + type: object + properties: + acme: + description: ACME specific status options. This field should only be set if the Issuer is configured to use an ACME server to issue certificates. + type: object + properties: + lastRegisteredEmail: + description: LastRegisteredEmail is the email associated with the latest registered ACME account, in order to track changes made to registered account associated with the Issuer + type: string + uri: + description: URI is the unique account identifier, which can also be used to retrieve account details from the CA + type: string + conditions: + description: List of status conditions to indicate the status of a CertificateRequest. Known condition types are `Ready`. + type: array + items: + description: IssuerCondition contains condition information for an Issuer. + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: LastTransitionTime is the timestamp corresponding to the last status change of this condition. + type: string + format: date-time + message: + description: Message is a human readable description of the details of the last transition, complementing reason. + type: string + observedGeneration: + description: If set, this represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the Issuer. + type: integer + format: int64 + reason: + description: Reason is a brief machine readable explanation for the condition's last transition. + type: string + status: + description: Status of the condition, one of (`True`, `False`, `Unknown`). + type: string + enum: + - "True" + - "False" + - Unknown + type: + description: Type of the condition, known values are (`Ready`). + type: string + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + served: true + storage: true +--- +# Source: cert-manager/templates/crd-templates.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: issuers.cert-manager.io + annotations: + cert-manager.io/inject-ca-from-secret: 'cert-manager/cert-manager-webhook-ca' + labels: + app: 'cert-manager' + app.kubernetes.io/name: 'cert-manager' + app.kubernetes.io/instance: 'cert-manager' + # Generated labels + app.kubernetes.io/version: "v1.8.0" +spec: + group: cert-manager.io + names: + kind: Issuer + listKind: IssuerList + plural: issuers + singular: issuer + categories: + - cert-manager + scope: Namespaced + versions: + - name: v1 + subresources: + status: {} + additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status + priority: 1 + type: string + - jsonPath: .metadata.creationTimestamp + description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. + name: Age + type: date + schema: + openAPIV3Schema: + description: An Issuer represents a certificate issuing authority which can be referenced as part of `issuerRef` fields. It is scoped to a single namespace and can therefore only be referenced by resources within the same namespace. + type: object + required: + - spec + 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: Desired state of the Issuer resource. + type: object + properties: + acme: + description: ACME configures this issuer to communicate with a RFC8555 (ACME) server to obtain signed x509 certificates. + type: object + required: + - privateKeySecretRef + - server + properties: + disableAccountKeyGeneration: + description: Enables or disables generating a new ACME account key. If true, the Issuer resource will *not* request a new account but will expect the account key to be supplied via an existing secret. If false, the cert-manager system will generate a new ACME account key for the Issuer. Defaults to false. + type: boolean + email: + description: Email is the email address to be associated with the ACME account. This field is optional, but it is strongly recommended to be set. It will be used to contact you in case of issues with your account or certificates, including expiry notification emails. This field may be updated after the account is initially registered. + type: string + enableDurationFeature: + description: Enables requesting a Not After date on certificates that matches the duration of the certificate. This is not supported by all ACME servers like Let's Encrypt. If set to true when the ACME server does not support it it will create an error on the Order. Defaults to false. + type: boolean + externalAccountBinding: + description: ExternalAccountBinding is a reference to a CA external account of the ACME server. If set, upon registration cert-manager will attempt to associate the given external account credentials with the registered ACME account. + type: object + required: + - keyID + - keySecretRef + properties: + keyAlgorithm: + description: 'Deprecated: keyAlgorithm field exists for historical compatibility reasons and should not be used. The algorithm is now hardcoded to HS256 in golang/x/crypto/acme.' + type: string + enum: + - HS256 + - HS384 + - HS512 + keyID: + description: keyID is the ID of the CA key that the External Account is bound to. + type: string + keySecretRef: + description: keySecretRef is a Secret Key Selector referencing a data item in a Kubernetes Secret which holds the symmetric MAC key of the External Account Binding. The `key` is the index string that is paired with the key data in the Secret and should not be confused with the key data itself, or indeed with the External Account Binding keyID above. The secret key stored in the Secret **must** be un-padded, base64 URL encoded data. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + preferredChain: + description: 'PreferredChain is the chain to use if the ACME server outputs multiple. PreferredChain is no guarantee that this one gets delivered by the ACME endpoint. For example, for Let''s Encrypt''s DST crosssign you would use: "DST Root CA X3" or "ISRG Root X1" for the newer Let''s Encrypt root CA. This value picks the first certificate bundle in the ACME alternative chains that has a certificate with this value as its issuer''s CN' + type: string + maxLength: 64 + privateKeySecretRef: + description: PrivateKey is the name of a Kubernetes Secret resource that will be used to store the automatically generated ACME account private key. Optionally, a `key` may be specified to select a specific entry within the named Secret resource. If `key` is not specified, a default of `tls.key` will be used. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + server: + description: 'Server is the URL used to access the ACME server''s ''directory'' endpoint. For example, for Let''s Encrypt''s staging endpoint, you would use: "https://acme-staging-v02.api.letsencrypt.org/directory". Only ACME v2 endpoints (i.e. RFC 8555) are supported.' + type: string + skipTLSVerify: + description: Enables or disables validation of the ACME server TLS certificate. If true, requests to the ACME server will not have their TLS certificate validated (i.e. insecure connections will be allowed). Only enable this option in development environments. The cert-manager system installed roots will be used to verify connections to the ACME server if this is false. Defaults to false. + type: boolean + solvers: + description: 'Solvers is a list of challenge solvers that will be used to solve ACME challenges for the matching domains. Solver configurations must be provided in order to obtain certificates from an ACME server. For more information, see: https://cert-manager.io/docs/configuration/acme/' + type: array + items: + description: An ACMEChallengeSolver describes how to solve ACME challenges for the issuer it is part of. A selector may be provided to use different solving strategies for different DNS names. Only one of HTTP01 or DNS01 must be provided. + type: object + properties: + dns01: + description: Configures cert-manager to attempt to complete authorizations by performing the DNS01 challenge flow. + type: object + properties: + acmeDNS: + description: Use the 'ACME DNS' (https://github.com/joohoi/acme-dns) API to manage DNS01 challenge records. + type: object + required: + - accountSecretRef + - host + properties: + accountSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + host: + type: string + akamai: + description: Use the Akamai DNS zone management API to manage DNS01 challenge records. + type: object + required: + - accessTokenSecretRef + - clientSecretSecretRef + - clientTokenSecretRef + - serviceConsumerDomain + properties: + accessTokenSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + clientSecretSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + clientTokenSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + serviceConsumerDomain: + type: string + azureDNS: + description: Use the Microsoft Azure DNS API to manage DNS01 challenge records. + type: object + required: + - resourceGroupName + - subscriptionID + properties: + clientID: + description: if both this and ClientSecret are left unset MSI will be used + type: string + clientSecretSecretRef: + description: if both this and ClientID are left unset MSI will be used + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + environment: + description: name of the Azure environment (default AzurePublicCloud) + type: string + enum: + - AzurePublicCloud + - AzureChinaCloud + - AzureGermanCloud + - AzureUSGovernmentCloud + hostedZoneName: + description: name of the DNS zone that should be used + type: string + managedIdentity: + description: managed identity configuration, can not be used at the same time as clientID, clientSecretSecretRef or tenantID + type: object + properties: + clientID: + description: client ID of the managed identity, can not be used at the same time as resourceID + type: string + resourceID: + description: resource ID of the managed identity, can not be used at the same time as clientID + type: string + resourceGroupName: + description: resource group the DNS zone is located in + type: string + subscriptionID: + description: ID of the Azure subscription + type: string + tenantID: + description: when specifying ClientID and ClientSecret then this field is also needed + type: string + cloudDNS: + description: Use the Google Cloud DNS API to manage DNS01 challenge records. + type: object + required: + - project + properties: + hostedZoneName: + description: HostedZoneName is an optional field that tells cert-manager in which Cloud DNS zone the challenge record has to be created. If left empty cert-manager will automatically choose a zone. + type: string + project: + type: string + serviceAccountSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + cloudflare: + description: Use the Cloudflare API to manage DNS01 challenge records. + type: object + properties: + apiKeySecretRef: + description: 'API key to use to authenticate with Cloudflare. Note: using an API token to authenticate is now the recommended method as it allows greater control of permissions.' + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + apiTokenSecretRef: + description: API token used to authenticate with Cloudflare. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + email: + description: Email of the account, only required when using API key based authentication. + type: string + cnameStrategy: + description: CNAMEStrategy configures how the DNS01 provider should handle CNAME records when found in DNS zones. + type: string + enum: + - None + - Follow + digitalocean: + description: Use the DigitalOcean DNS API to manage DNS01 challenge records. + type: object + required: + - tokenSecretRef + properties: + tokenSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + rfc2136: + description: Use RFC2136 ("Dynamic Updates in the Domain Name System") (https://datatracker.ietf.org/doc/rfc2136/) to manage DNS01 challenge records. + type: object + required: + - nameserver + properties: + nameserver: + description: The IP address or hostname of an authoritative DNS server supporting RFC2136 in the form host:port. If the host is an IPv6 address it must be enclosed in square brackets (e.g [2001:db8::1]) ; port is optional. This field is required. + type: string + tsigAlgorithm: + description: 'The TSIG Algorithm configured in the DNS supporting RFC2136. Used only when ``tsigSecretSecretRef`` and ``tsigKeyName`` are defined. Supported values are (case-insensitive): ``HMACMD5`` (default), ``HMACSHA1``, ``HMACSHA256`` or ``HMACSHA512``.' + type: string + tsigKeyName: + description: The TSIG Key name configured in the DNS. If ``tsigSecretSecretRef`` is defined, this field is required. + type: string + tsigSecretSecretRef: + description: The name of the secret containing the TSIG value. If ``tsigKeyName`` is defined, this field is required. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + route53: + description: Use the AWS Route53 API to manage DNS01 challenge records. + type: object + required: + - region + properties: + accessKeyID: + description: 'The AccessKeyID is used for authentication. If not set we fall-back to using env vars, shared credentials file or AWS Instance metadata see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials' + type: string + hostedZoneID: + description: If set, the provider will manage only this zone in Route53 and will not do an lookup using the route53:ListHostedZonesByName api call. + type: string + region: + description: Always set the region when using AccessKeyID and SecretAccessKey + type: string + role: + description: Role is a Role ARN which the Route53 provider will assume using either the explicit credentials AccessKeyID/SecretAccessKey or the inferred credentials from environment variables, shared credentials file or AWS Instance metadata + type: string + secretAccessKeySecretRef: + description: The SecretAccessKey is used for authentication. If not set we fall-back to using env vars, shared credentials file or AWS Instance metadata https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + webhook: + description: Configure an external webhook based DNS01 challenge solver to manage DNS01 challenge records. + type: object + required: + - groupName + - solverName + properties: + config: + description: Additional configuration that should be passed to the webhook apiserver when challenges are processed. This can contain arbitrary JSON data. Secret values should not be specified in this stanza. If secret values are needed (e.g. credentials for a DNS service), you should use a SecretKeySelector to reference a Secret resource. For details on the schema of this field, consult the webhook provider implementation's documentation. + x-kubernetes-preserve-unknown-fields: true + groupName: + description: The API group name that should be used when POSTing ChallengePayload resources to the webhook apiserver. This should be the same as the GroupName specified in the webhook provider implementation. + type: string + solverName: + description: The name of the solver to use, as defined in the webhook provider implementation. This will typically be the name of the provider, e.g. 'cloudflare'. + type: string + http01: + description: Configures cert-manager to attempt to complete authorizations by performing the HTTP01 challenge flow. It is not possible to obtain certificates for wildcard domain names (e.g. `*.example.com`) using the HTTP01 challenge mechanism. + type: object + properties: + gatewayHTTPRoute: + description: The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create HTTPRoutes with the specified labels in the same namespace as the challenge. This solver is experimental, and fields / behaviour may change in the future. + type: object + properties: + labels: + description: Custom labels that will be applied to HTTPRoutes created by cert-manager while solving HTTP-01 challenges. + type: object + additionalProperties: + type: string + parentRefs: + description: 'When solving an HTTP-01 challenge, cert-manager creates an HTTPRoute. cert-manager needs to know which parentRefs should be used when creating the HTTPRoute. Usually, the parentRef references a Gateway. See: https://gateway-api.sigs.k8s.io/v1alpha2/api-types/httproute/#attaching-to-gateways' + type: array + items: + description: "ParentRef identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid. \n References to objects with invalid Group and Kind are not valid, and must be rejected by the implementation, with appropriate Conditions set on the containing object." + type: object + required: + - name + properties: + group: + description: "Group is the group of the referent. \n Support: Core" + type: string + default: gateway.networking.k8s.io + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + kind: + description: "Kind is kind of the referent. \n Support: Core (Gateway) Support: Custom (Other Resources)" + type: string + default: Gateway + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + name: + description: "Name is the name of the referent. \n Support: Core" + type: string + maxLength: 253 + minLength: 1 + namespace: + description: "Namespace is the namespace of the referent. When unspecified (or empty string), this refers to the local namespace of the Route. \n Support: Core" + type: string + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + type: string + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + serviceType: + description: Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + ingress: + description: The ingress based HTTP01 challenge solver will solve challenges by creating or modifying Ingress resources in order to route requests for '/.well-known/acme-challenge/XYZ' to 'challenge solver' pods that are provisioned by cert-manager for each Challenge to be completed. + type: object + properties: + class: + description: The ingress class to use when creating Ingress resources to solve ACME challenges that use this challenge solver. Only one of 'class' or 'name' may be specified. + type: string + ingressTemplate: + description: Optional ingress template used to configure the ACME challenge solver ingress used for HTTP01 challenges. + type: object + properties: + metadata: + description: ObjectMeta overrides for the ingress used to solve HTTP01 challenges. Only the 'labels' and 'annotations' fields may be set. If labels or annotations overlap with in-built values, the values here will override the in-built values. + type: object + properties: + annotations: + description: Annotations that should be added to the created ACME HTTP01 solver ingress. + type: object + additionalProperties: + type: string + labels: + description: Labels that should be added to the created ACME HTTP01 solver ingress. + type: object + additionalProperties: + type: string + name: + description: The name of the ingress resource that should have ACME challenge solving routes inserted into it in order to solve HTTP01 challenges. This is typically used in conjunction with ingress controllers like ingress-gce, which maintains a 1:1 mapping between external IPs and ingress resources. + type: string + podTemplate: + description: Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges. + type: object + properties: + metadata: + description: ObjectMeta overrides for the pod used to solve HTTP01 challenges. Only the 'labels' and 'annotations' fields may be set. If labels or annotations overlap with in-built values, the values here will override the in-built values. + type: object + properties: + annotations: + description: Annotations that should be added to the create ACME HTTP01 solver pods. + type: object + additionalProperties: + type: string + labels: + description: Labels that should be added to the created ACME HTTP01 solver pods. + type: object + additionalProperties: + type: string + spec: + description: PodSpec defines overrides for the HTTP01 challenge solver pod. Only the 'priorityClassName', 'nodeSelector', 'affinity', 'serviceAccountName' and 'tolerations' fields are supported currently. All other fields will be ignored. + type: object + properties: + affinity: + description: If specified, the pod's scheduling constraints + type: object + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the pod. + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred. + type: array + items: + description: An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + type: object + required: + - preference + - weight + properties: + preference: + description: A node selector term, associated with the corresponding weight. + type: object + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchFields: + description: A list of node selector requirements by node's fields. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + weight: + description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node. + type: object + required: + - nodeSelectorTerms + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. The terms are ORed. + type: array + items: + description: A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + type: object + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchFields: + description: A list of node selector requirements by node's fields. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. + type: array + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + type: object + required: + - podAffinityTerm + - weight + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. This field is beta-level and is only honored when PodAffinityNamespaceSelector feature is enabled. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace" + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + weight: + description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. + type: array + items: + description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. This field is beta-level and is only honored when PodAffinityNamespaceSelector feature is enabled. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace" + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. + type: array + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + type: object + required: + - podAffinityTerm + - weight + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. This field is beta-level and is only honored when PodAffinityNamespaceSelector feature is enabled. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace" + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + weight: + description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. + type: array + items: + description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. This field is beta-level and is only honored when PodAffinityNamespaceSelector feature is enabled. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace" + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + nodeSelector: + description: 'NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node''s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + additionalProperties: + type: string + priorityClassName: + description: If specified, the pod's priorityClassName. + type: string + serviceAccountName: + description: If specified, the pod's service account + type: string + tolerations: + description: If specified, the pod's tolerations. + type: array + items: + description: The pod this Toleration is attached to tolerates any taint that matches the triple using the matching operator . + type: object + properties: + effect: + description: Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system. + type: integer + format: int64 + value: + description: Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + serviceType: + description: Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + selector: + description: Selector selects a set of DNSNames on the Certificate resource that should be solved using this challenge solver. If not specified, the solver will be treated as the 'default' solver with the lowest priority, i.e. if any other solver has a more specific match, it will be used instead. + type: object + properties: + dnsNames: + description: List of DNSNames that this solver will be used to solve. If specified and a match is found, a dnsNames selector will take precedence over a dnsZones selector. If multiple solvers match with the same dnsNames value, the solver with the most matching labels in matchLabels will be selected. If neither has more matches, the solver defined earlier in the list will be selected. + type: array + items: + type: string + dnsZones: + description: List of DNSZones that this solver will be used to solve. The most specific DNS zone match specified here will take precedence over other DNS zone matches, so a solver specifying sys.example.com will be selected over one specifying example.com for the domain www.sys.example.com. If multiple solvers match with the same dnsZones value, the solver with the most matching labels in matchLabels will be selected. If neither has more matches, the solver defined earlier in the list will be selected. + type: array + items: + type: string + matchLabels: + description: A label selector that is used to refine the set of certificate's that this challenge solver will apply to. + type: object + additionalProperties: + type: string + ca: + description: CA configures this issuer to sign certificates using a signing CA keypair stored in a Secret resource. This is used to build internal PKIs that are managed by cert-manager. + type: object + required: + - secretName + properties: + crlDistributionPoints: + description: The CRL distribution points is an X.509 v3 certificate extension which identifies the location of the CRL from which the revocation of this certificate can be checked. If not set, certificates will be issued without distribution points set. + type: array + items: + type: string + ocspServers: + description: The OCSP server list is an X.509 v3 extension that defines a list of URLs of OCSP responders. The OCSP responders can be queried for the revocation status of an issued certificate. If not set, the certificate will be issued with no OCSP servers set. For example, an OCSP server URL could be "http://ocsp.int-x3.letsencrypt.org". + type: array + items: + type: string + secretName: + description: SecretName is the name of the secret used to sign Certificates issued by this Issuer. + type: string + selfSigned: + description: SelfSigned configures this issuer to 'self sign' certificates using the private key used to create the CertificateRequest object. + type: object + properties: + crlDistributionPoints: + description: The CRL distribution points is an X.509 v3 certificate extension which identifies the location of the CRL from which the revocation of this certificate can be checked. If not set certificate will be issued without CDP. Values are strings. + type: array + items: + type: string + vault: + description: Vault configures this issuer to sign certificates using a HashiCorp Vault PKI backend. + type: object + required: + - auth + - path + - server + properties: + auth: + description: Auth configures how cert-manager authenticates with the Vault server. + type: object + properties: + appRole: + description: AppRole authenticates with Vault using the App Role auth mechanism, with the role and secret stored in a Kubernetes Secret resource. + type: object + required: + - path + - roleId + - secretRef + properties: + path: + description: 'Path where the App Role authentication backend is mounted in Vault, e.g: "approle"' + type: string + roleId: + description: RoleID configured in the App Role authentication backend when setting up the authentication backend in Vault. + type: string + secretRef: + description: Reference to a key in a Secret that contains the App Role secret used to authenticate with Vault. The `key` field must be specified and denotes which entry within the Secret resource is used as the app role secret. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + kubernetes: + description: Kubernetes authenticates with Vault by passing the ServiceAccount token stored in the named Secret resource to the Vault server. + type: object + required: + - role + - secretRef + properties: + mountPath: + description: The Vault mountPath here is the mount path to use when authenticating with Vault. For example, setting a value to `/v1/auth/foo`, will use the path `/v1/auth/foo/login` to authenticate with Vault. If unspecified, the default value "/v1/auth/kubernetes" will be used. + type: string + role: + description: A required field containing the Vault Role to assume. A Role binds a Kubernetes ServiceAccount with a set of Vault policies. + type: string + secretRef: + description: The required Secret field containing a Kubernetes ServiceAccount JWT used for authenticating with Vault. Use of 'ambient credentials' is not supported. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + tokenSecretRef: + description: TokenSecretRef authenticates with Vault by presenting a token. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + caBundle: + description: PEM-encoded CA bundle (base64-encoded) used to validate Vault server certificate. Only used if the Server URL is using HTTPS protocol. This parameter is ignored for plain HTTP protocol connection. If not set the system root certificates are used to validate the TLS connection. + type: string + format: byte + namespace: + description: 'Name of the vault namespace. Namespaces is a set of features within Vault Enterprise that allows Vault environments to support Secure Multi-tenancy. e.g: "ns1" More about namespaces can be found here https://www.vaultproject.io/docs/enterprise/namespaces' + type: string + path: + description: 'Path is the mount path of the Vault PKI backend''s `sign` endpoint, e.g: "my_pki_mount/sign/my-role-name".' + type: string + server: + description: 'Server is the connection address for the Vault server, e.g: "https://vault.example.com:8200".' + type: string + venafi: + description: Venafi configures this issuer to sign certificates using a Venafi TPP or Venafi Cloud policy zone. + type: object + required: + - zone + properties: + cloud: + description: Cloud specifies the Venafi cloud configuration settings. Only one of TPP or Cloud may be specified. + type: object + required: + - apiTokenSecretRef + properties: + apiTokenSecretRef: + description: APITokenSecretRef is a secret key selector for the Venafi Cloud API token. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + url: + description: URL is the base URL for Venafi Cloud. Defaults to "https://api.venafi.cloud/v1". + type: string + tpp: + description: TPP specifies Trust Protection Platform configuration settings. Only one of TPP or Cloud may be specified. + type: object + required: + - credentialsRef + - url + properties: + caBundle: + description: CABundle is a PEM encoded TLS certificate to use to verify connections to the TPP instance. If specified, system roots will not be used and the issuing CA for the TPP instance must be verifiable using the provided root. If not specified, the connection will be verified using the cert-manager system root certificates. + type: string + format: byte + credentialsRef: + description: CredentialsRef is a reference to a Secret containing the username and password for the TPP server. The secret must contain two keys, 'username' and 'password'. + type: object + required: + - name + properties: + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + url: + description: 'URL is the base URL for the vedsdk endpoint of the Venafi TPP instance, for example: "https://tpp.example.com/vedsdk".' + type: string + zone: + description: Zone is the Venafi Policy Zone to use for this issuer. All requests made to the Venafi platform will be restricted by the named zone policy. This field is required. + type: string + status: + description: Status of the Issuer. This is set and managed automatically. + type: object + properties: + acme: + description: ACME specific status options. This field should only be set if the Issuer is configured to use an ACME server to issue certificates. + type: object + properties: + lastRegisteredEmail: + description: LastRegisteredEmail is the email associated with the latest registered ACME account, in order to track changes made to registered account associated with the Issuer + type: string + uri: + description: URI is the unique account identifier, which can also be used to retrieve account details from the CA + type: string + conditions: + description: List of status conditions to indicate the status of a CertificateRequest. Known condition types are `Ready`. + type: array + items: + description: IssuerCondition contains condition information for an Issuer. + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: LastTransitionTime is the timestamp corresponding to the last status change of this condition. + type: string + format: date-time + message: + description: Message is a human readable description of the details of the last transition, complementing reason. + type: string + observedGeneration: + description: If set, this represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the Issuer. + type: integer + format: int64 + reason: + description: Reason is a brief machine readable explanation for the condition's last transition. + type: string + status: + description: Status of the condition, one of (`True`, `False`, `Unknown`). + type: string + enum: + - "True" + - "False" + - Unknown + type: + description: Type of the condition, known values are (`Ready`). + type: string + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + served: true + storage: true +--- +# Source: cert-manager/templates/crd-templates.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: orders.acme.cert-manager.io + annotations: + cert-manager.io/inject-ca-from-secret: 'cert-manager/cert-manager-webhook-ca' + labels: + app: 'cert-manager' + app.kubernetes.io/name: 'cert-manager' + app.kubernetes.io/instance: 'cert-manager' + # Generated labels + app.kubernetes.io/version: "v1.8.0" +spec: + group: acme.cert-manager.io + names: + kind: Order + listKind: OrderList + plural: orders + singular: order + categories: + - cert-manager + - cert-manager-acme + scope: Namespaced + versions: + - name: v1 + subresources: + status: {} + additionalPrinterColumns: + - jsonPath: .status.state + name: State + type: string + - jsonPath: .spec.issuerRef.name + name: Issuer + priority: 1 + type: string + - jsonPath: .status.reason + name: Reason + priority: 1 + type: string + - jsonPath: .metadata.creationTimestamp + description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. + name: Age + type: date + schema: + openAPIV3Schema: + description: Order is a type to represent an Order with an ACME server + type: object + required: + - metadata + - spec + 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: + type: object + required: + - issuerRef + - request + properties: + commonName: + description: CommonName is the common name as specified on the DER encoded CSR. If specified, this value must also be present in `dnsNames` or `ipAddresses`. This field must match the corresponding field on the DER encoded CSR. + type: string + dnsNames: + description: DNSNames is a list of DNS names that should be included as part of the Order validation process. This field must match the corresponding field on the DER encoded CSR. + type: array + items: + type: string + duration: + description: Duration is the duration for the not after date for the requested certificate. this is set on order creation as pe the ACME spec. + type: string + ipAddresses: + description: IPAddresses is a list of IP addresses that should be included as part of the Order validation process. This field must match the corresponding field on the DER encoded CSR. + type: array + items: + type: string + issuerRef: + description: IssuerRef references a properly configured ACME-type Issuer which should be used to create this Order. If the Issuer does not exist, processing will be retried. If the Issuer is not an 'ACME' Issuer, an error will be returned and the Order will be marked as failed. + type: object + required: + - name + properties: + group: + description: Group of the resource being referred to. + type: string + kind: + description: Kind of the resource being referred to. + type: string + name: + description: Name of the resource being referred to. + type: string + request: + description: Certificate signing request bytes in DER encoding. This will be used when finalizing the order. This field must be set on the order. + type: string + format: byte + status: + type: object + properties: + authorizations: + description: Authorizations contains data returned from the ACME server on what authorizations must be completed in order to validate the DNS names specified on the Order. + type: array + items: + description: ACMEAuthorization contains data returned from the ACME server on an authorization that must be completed in order validate a DNS name on an ACME Order resource. + type: object + required: + - url + properties: + challenges: + description: Challenges specifies the challenge types offered by the ACME server. One of these challenge types will be selected when validating the DNS name and an appropriate Challenge resource will be created to perform the ACME challenge process. + type: array + items: + description: Challenge specifies a challenge offered by the ACME server for an Order. An appropriate Challenge resource can be created to perform the ACME challenge process. + type: object + required: + - token + - type + - url + properties: + token: + description: Token is the token that must be presented for this challenge. This is used to compute the 'key' that must also be presented. + type: string + type: + description: Type is the type of challenge being offered, e.g. 'http-01', 'dns-01', 'tls-sni-01', etc. This is the raw value retrieved from the ACME server. Only 'http-01' and 'dns-01' are supported by cert-manager, other values will be ignored. + type: string + url: + description: URL is the URL of this challenge. It can be used to retrieve additional metadata about the Challenge from the ACME server. + type: string + identifier: + description: Identifier is the DNS name to be validated as part of this authorization + type: string + initialState: + description: InitialState is the initial state of the ACME authorization when first fetched from the ACME server. If an Authorization is already 'valid', the Order controller will not create a Challenge resource for the authorization. This will occur when working with an ACME server that enables 'authz reuse' (such as Let's Encrypt's production endpoint). If not set and 'identifier' is set, the state is assumed to be pending and a Challenge will be created. + type: string + enum: + - valid + - ready + - pending + - processing + - invalid + - expired + - errored + url: + description: URL is the URL of the Authorization that must be completed + type: string + wildcard: + description: Wildcard will be true if this authorization is for a wildcard DNS name. If this is true, the identifier will be the *non-wildcard* version of the DNS name. For example, if '*.example.com' is the DNS name being validated, this field will be 'true' and the 'identifier' field will be 'example.com'. + type: boolean + certificate: + description: Certificate is a copy of the PEM encoded certificate for this Order. This field will be populated after the order has been successfully finalized with the ACME server, and the order has transitioned to the 'valid' state. + type: string + format: byte + failureTime: + description: FailureTime stores the time that this order failed. This is used to influence garbage collection and back-off. + type: string + format: date-time + finalizeURL: + description: FinalizeURL of the Order. This is used to obtain certificates for this order once it has been completed. + type: string + reason: + description: Reason optionally provides more information about a why the order is in the current state. + type: string + state: + description: State contains the current state of this Order resource. States 'success' and 'expired' are 'final' + type: string + enum: + - valid + - ready + - pending + - processing + - invalid + - expired + - errored + url: + description: URL of the Order. This will initially be empty when the resource is first created. The Order controller will populate this field when the Order is first processed. This field will be immutable after it is initially set. + type: string + served: true + storage: true +--- +# Source: cert-manager/templates/cainjector-serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +automountServiceAccountToken: true +metadata: + name: cert-manager-cainjector + namespace: "cert-manager" + labels: + app: cainjector + app.kubernetes.io/name: cainjector + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "cainjector" + app.kubernetes.io/version: "v1.8.0" +--- +# Source: cert-manager/templates/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +automountServiceAccountToken: true +metadata: + name: cert-manager + namespace: "cert-manager" + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.8.0" +--- +# Source: cert-manager/templates/webhook-serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +automountServiceAccountToken: true +metadata: + name: cert-manager-webhook + namespace: "cert-manager" + labels: + app: webhook + app.kubernetes.io/name: webhook + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "webhook" + app.kubernetes.io/version: "v1.8.0" +--- +# Source: cert-manager/templates/webhook-config.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: cert-manager-webhook + namespace: "cert-manager" + labels: + app: webhook + app.kubernetes.io/name: webhook + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "webhook" +data: +--- +# Source: cert-manager/templates/cainjector-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-cainjector + labels: + app: cainjector + app.kubernetes.io/name: cainjector + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "cainjector" + app.kubernetes.io/version: "v1.8.0" +rules: + - apiGroups: ["cert-manager.io"] + resources: ["certificates"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["events"] + verbs: ["get", "create", "update", "patch"] + - apiGroups: ["admissionregistration.k8s.io"] + resources: ["validatingwebhookconfigurations", "mutatingwebhookconfigurations"] + verbs: ["get", "list", "watch", "update"] + - apiGroups: ["apiregistration.k8s.io"] + resources: ["apiservices"] + verbs: ["get", "list", "watch", "update"] + - apiGroups: ["apiextensions.k8s.io"] + resources: ["customresourcedefinitions"] + verbs: ["get", "list", "watch", "update"] +--- +# Source: cert-manager/templates/rbac.yaml +# Issuer controller role +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-controller-issuers + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.8.0" +rules: + - apiGroups: ["cert-manager.io"] + resources: ["issuers", "issuers/status"] + verbs: ["update", "patch"] + - apiGroups: ["cert-manager.io"] + resources: ["issuers"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "watch", "create", "update", "delete"] + - apiGroups: [""] + resources: ["events"] + verbs: ["create", "patch"] +--- +# Source: cert-manager/templates/rbac.yaml +# ClusterIssuer controller role +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-controller-clusterissuers + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.8.0" +rules: + - apiGroups: ["cert-manager.io"] + resources: ["clusterissuers", "clusterissuers/status"] + verbs: ["update", "patch"] + - apiGroups: ["cert-manager.io"] + resources: ["clusterissuers"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "watch", "create", "update", "delete"] + - apiGroups: [""] + resources: ["events"] + verbs: ["create", "patch"] +--- +# Source: cert-manager/templates/rbac.yaml +# Certificates controller role +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-controller-certificates + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.8.0" +rules: + - apiGroups: ["cert-manager.io"] + resources: ["certificates", "certificates/status", "certificaterequests", "certificaterequests/status"] + verbs: ["update", "patch"] + - apiGroups: ["cert-manager.io"] + resources: ["certificates", "certificaterequests", "clusterissuers", "issuers"] + verbs: ["get", "list", "watch"] + # We require these rules to support users with the OwnerReferencesPermissionEnforcement + # admission controller enabled: + # https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#ownerreferencespermissionenforcement + - apiGroups: ["cert-manager.io"] + resources: ["certificates/finalizers", "certificaterequests/finalizers"] + verbs: ["update"] + - apiGroups: ["acme.cert-manager.io"] + resources: ["orders"] + verbs: ["create", "delete", "get", "list", "watch"] + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "watch", "create", "update", "delete", "patch"] + - apiGroups: [""] + resources: ["events"] + verbs: ["create", "patch"] +--- +# Source: cert-manager/templates/rbac.yaml +# Orders controller role +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-controller-orders + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.8.0" +rules: + - apiGroups: ["acme.cert-manager.io"] + resources: ["orders", "orders/status"] + verbs: ["update", "patch"] + - apiGroups: ["acme.cert-manager.io"] + resources: ["orders", "challenges"] + verbs: ["get", "list", "watch"] + - apiGroups: ["cert-manager.io"] + resources: ["clusterissuers", "issuers"] + verbs: ["get", "list", "watch"] + - apiGroups: ["acme.cert-manager.io"] + resources: ["challenges"] + verbs: ["create", "delete"] + # We require these rules to support users with the OwnerReferencesPermissionEnforcement + # admission controller enabled: + # https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#ownerreferencespermissionenforcement + - apiGroups: ["acme.cert-manager.io"] + resources: ["orders/finalizers"] + verbs: ["update"] + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["events"] + verbs: ["create", "patch"] +--- +# Source: cert-manager/templates/rbac.yaml +# Challenges controller role +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-controller-challenges + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.8.0" +rules: + # Use to update challenge resource status + - apiGroups: ["acme.cert-manager.io"] + resources: ["challenges", "challenges/status"] + verbs: ["update", "patch"] + # Used to watch challenge resources + - apiGroups: ["acme.cert-manager.io"] + resources: ["challenges"] + verbs: ["get", "list", "watch"] + # Used to watch challenges, issuer and clusterissuer resources + - apiGroups: ["cert-manager.io"] + resources: ["issuers", "clusterissuers"] + verbs: ["get", "list", "watch"] + # Need to be able to retrieve ACME account private key to complete challenges + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "watch"] + # Used to create events + - apiGroups: [""] + resources: ["events"] + verbs: ["create", "patch"] + # HTTP01 rules + - apiGroups: [""] + resources: ["pods", "services"] + verbs: ["get", "list", "watch", "create", "delete"] + - apiGroups: ["networking.k8s.io"] + resources: ["ingresses"] + verbs: ["get", "list", "watch", "create", "delete", "update"] + - apiGroups: [ "gateway.networking.k8s.io" ] + resources: [ "httproutes" ] + verbs: ["get", "list", "watch", "create", "delete", "update"] + # We require the ability to specify a custom hostname when we are creating + # new ingress resources. + # See: https://github.com/openshift/origin/blob/21f191775636f9acadb44fa42beeb4f75b255532/pkg/route/apiserver/admission/ingress_admission.go#L84-L148 + - apiGroups: ["route.openshift.io"] + resources: ["routes/custom-host"] + verbs: ["create"] + # We require these rules to support users with the OwnerReferencesPermissionEnforcement + # admission controller enabled: + # https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#ownerreferencespermissionenforcement + - apiGroups: ["acme.cert-manager.io"] + resources: ["challenges/finalizers"] + verbs: ["update"] + # DNS01 rules (duplicated above) + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "watch"] +--- +# Source: cert-manager/templates/rbac.yaml +# ingress-shim controller role +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-controller-ingress-shim + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.8.0" +rules: + - apiGroups: ["cert-manager.io"] + resources: ["certificates", "certificaterequests"] + verbs: ["create", "update", "delete"] + - apiGroups: ["cert-manager.io"] + resources: ["certificates", "certificaterequests", "issuers", "clusterissuers"] + verbs: ["get", "list", "watch"] + - apiGroups: ["networking.k8s.io"] + resources: ["ingresses"] + verbs: ["get", "list", "watch"] + # We require these rules to support users with the OwnerReferencesPermissionEnforcement + # admission controller enabled: + # https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#ownerreferencespermissionenforcement + - apiGroups: ["networking.k8s.io"] + resources: ["ingresses/finalizers"] + verbs: ["update"] + - apiGroups: ["gateway.networking.k8s.io"] + resources: ["gateways", "httproutes"] + verbs: ["get", "list", "watch"] + - apiGroups: ["gateway.networking.k8s.io"] + resources: ["gateways/finalizers", "httproutes/finalizers"] + verbs: ["update"] + - apiGroups: [""] + resources: ["events"] + verbs: ["create", "patch"] +--- +# Source: cert-manager/templates/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-view + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.8.0" + rbac.authorization.k8s.io/aggregate-to-view: "true" + rbac.authorization.k8s.io/aggregate-to-edit: "true" + rbac.authorization.k8s.io/aggregate-to-admin: "true" +rules: + - apiGroups: ["cert-manager.io"] + resources: ["certificates", "certificaterequests", "issuers"] + verbs: ["get", "list", "watch"] + - apiGroups: ["acme.cert-manager.io"] + resources: ["challenges", "orders"] + verbs: ["get", "list", "watch"] +--- +# Source: cert-manager/templates/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-edit + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.8.0" + rbac.authorization.k8s.io/aggregate-to-edit: "true" + rbac.authorization.k8s.io/aggregate-to-admin: "true" +rules: + - apiGroups: ["cert-manager.io"] + resources: ["certificates", "certificaterequests", "issuers"] + verbs: ["create", "delete", "deletecollection", "patch", "update"] + - apiGroups: ["cert-manager.io"] + resources: ["certificates/status"] + verbs: ["update"] + - apiGroups: ["acme.cert-manager.io"] + resources: ["challenges", "orders"] + verbs: ["create", "delete", "deletecollection", "patch", "update"] +--- +# Source: cert-manager/templates/rbac.yaml +# Permission to approve CertificateRequests referencing cert-manager.io Issuers and ClusterIssuers +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-controller-approve:cert-manager-io + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "cert-manager" + app.kubernetes.io/version: "v1.8.0" +rules: + - apiGroups: ["cert-manager.io"] + resources: ["signers"] + verbs: ["approve"] + resourceNames: ["issuers.cert-manager.io/*", "clusterissuers.cert-manager.io/*"] +--- +# Source: cert-manager/templates/rbac.yaml +# Permission to: +# - Update and sign CertificatSigningeRequests referencing cert-manager.io Issuers and ClusterIssuers +# - Perform SubjectAccessReviews to test whether users are able to reference Namespaced Issuers +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-controller-certificatesigningrequests + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "cert-manager" + app.kubernetes.io/version: "v1.8.0" +rules: + - apiGroups: ["certificates.k8s.io"] + resources: ["certificatesigningrequests"] + verbs: ["get", "list", "watch", "update"] + - apiGroups: ["certificates.k8s.io"] + resources: ["certificatesigningrequests/status"] + verbs: ["update", "patch"] + - apiGroups: ["certificates.k8s.io"] + resources: ["signers"] + resourceNames: ["issuers.cert-manager.io/*", "clusterissuers.cert-manager.io/*"] + verbs: ["sign"] + - apiGroups: ["authorization.k8s.io"] + resources: ["subjectaccessreviews"] + verbs: ["create"] +--- +# Source: cert-manager/templates/webhook-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cert-manager-webhook:subjectaccessreviews + labels: + app: webhook + app.kubernetes.io/name: webhook + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "webhook" + app.kubernetes.io/version: "v1.8.0" +rules: +- apiGroups: ["authorization.k8s.io"] + resources: ["subjectaccessreviews"] + verbs: ["create"] +--- +# Source: cert-manager/templates/cainjector-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-cainjector + labels: + app: cainjector + app.kubernetes.io/name: cainjector + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "cainjector" + app.kubernetes.io/version: "v1.8.0" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-cainjector +subjects: + - name: cert-manager-cainjector + namespace: "cert-manager" + kind: ServiceAccount +--- +# Source: cert-manager/templates/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-controller-issuers + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.8.0" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-controller-issuers +subjects: + - name: cert-manager + namespace: "cert-manager" + kind: ServiceAccount +--- +# Source: cert-manager/templates/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-controller-clusterissuers + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.8.0" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-controller-clusterissuers +subjects: + - name: cert-manager + namespace: "cert-manager" + kind: ServiceAccount +--- +# Source: cert-manager/templates/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-controller-certificates + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.8.0" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-controller-certificates +subjects: + - name: cert-manager + namespace: "cert-manager" + kind: ServiceAccount +--- +# Source: cert-manager/templates/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-controller-orders + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.8.0" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-controller-orders +subjects: + - name: cert-manager + namespace: "cert-manager" + kind: ServiceAccount +--- +# Source: cert-manager/templates/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-controller-challenges + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.8.0" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-controller-challenges +subjects: + - name: cert-manager + namespace: "cert-manager" + kind: ServiceAccount +--- +# Source: cert-manager/templates/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-controller-ingress-shim + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.8.0" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-controller-ingress-shim +subjects: + - name: cert-manager + namespace: "cert-manager" + kind: ServiceAccount +--- +# Source: cert-manager/templates/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-controller-approve:cert-manager-io + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "cert-manager" + app.kubernetes.io/version: "v1.8.0" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-controller-approve:cert-manager-io +subjects: + - name: cert-manager + namespace: "cert-manager" + kind: ServiceAccount +--- +# Source: cert-manager/templates/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-controller-certificatesigningrequests + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "cert-manager" + app.kubernetes.io/version: "v1.8.0" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-controller-certificatesigningrequests +subjects: + - name: cert-manager + namespace: "cert-manager" + kind: ServiceAccount +--- +# Source: cert-manager/templates/webhook-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cert-manager-webhook:subjectaccessreviews + labels: + app: webhook + app.kubernetes.io/name: webhook + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "webhook" + app.kubernetes.io/version: "v1.8.0" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cert-manager-webhook:subjectaccessreviews +subjects: +- apiGroup: "" + kind: ServiceAccount + name: cert-manager-webhook + namespace: cert-manager +--- +# Source: cert-manager/templates/cainjector-rbac.yaml +# leader election rules +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: cert-manager-cainjector:leaderelection + namespace: kube-system + labels: + app: cainjector + app.kubernetes.io/name: cainjector + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "cainjector" + app.kubernetes.io/version: "v1.8.0" +rules: + # Used for leader election by the controller + # cert-manager-cainjector-leader-election is used by the CertificateBased injector controller + # see cmd/cainjector/start.go#L113 + # cert-manager-cainjector-leader-election-core is used by the SecretBased injector controller + # see cmd/cainjector/start.go#L137 + - apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + resourceNames: ["cert-manager-cainjector-leader-election", "cert-manager-cainjector-leader-election-core"] + verbs: ["get", "update", "patch"] + - apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: ["create"] +--- +# Source: cert-manager/templates/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: cert-manager:leaderelection + namespace: kube-system + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.8.0" +rules: + - apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + resourceNames: ["cert-manager-controller"] + verbs: ["get", "update", "patch"] + - apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: ["create"] +--- +# Source: cert-manager/templates/webhook-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: cert-manager-webhook:dynamic-serving + namespace: "cert-manager" + labels: + app: webhook + app.kubernetes.io/name: webhook + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "webhook" + app.kubernetes.io/version: "v1.8.0" +rules: +- apiGroups: [""] + resources: ["secrets"] + resourceNames: + - 'cert-manager-webhook-ca' + verbs: ["get", "list", "watch", "update"] +# It's not possible to grant CREATE permission on a single resourceName. +- apiGroups: [""] + resources: ["secrets"] + verbs: ["create"] +--- +# Source: cert-manager/templates/cainjector-rbac.yaml +# grant cert-manager permission to manage the leaderelection configmap in the +# leader election namespace +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: cert-manager-cainjector:leaderelection + namespace: kube-system + labels: + app: cainjector + app.kubernetes.io/name: cainjector + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "cainjector" + app.kubernetes.io/version: "v1.8.0" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: cert-manager-cainjector:leaderelection +subjects: + - kind: ServiceAccount + name: cert-manager-cainjector + namespace: cert-manager +--- +# Source: cert-manager/templates/rbac.yaml +# grant cert-manager permission to manage the leaderelection configmap in the +# leader election namespace +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: cert-manager:leaderelection + namespace: kube-system + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.8.0" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: cert-manager:leaderelection +subjects: + - apiGroup: "" + kind: ServiceAccount + name: cert-manager + namespace: cert-manager +--- +# Source: cert-manager/templates/webhook-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: cert-manager-webhook:dynamic-serving + namespace: "cert-manager" + labels: + app: webhook + app.kubernetes.io/name: webhook + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "webhook" + app.kubernetes.io/version: "v1.8.0" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: cert-manager-webhook:dynamic-serving +subjects: +- apiGroup: "" + kind: ServiceAccount + name: cert-manager-webhook + namespace: cert-manager +--- +# Source: cert-manager/templates/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: cert-manager + namespace: "cert-manager" + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.8.0" +spec: + type: ClusterIP + ports: + - protocol: TCP + port: 9402 + name: tcp-prometheus-servicemonitor + targetPort: 9402 + selector: + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" +--- +# Source: cert-manager/templates/webhook-service.yaml +apiVersion: v1 +kind: Service +metadata: + name: cert-manager-webhook + namespace: "cert-manager" + labels: + app: webhook + app.kubernetes.io/name: webhook + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "webhook" + app.kubernetes.io/version: "v1.8.0" +spec: + type: ClusterIP + ports: + - name: https + port: 443 + protocol: TCP + targetPort: "https" + selector: + app.kubernetes.io/name: webhook + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "webhook" +--- +# Source: cert-manager/templates/cainjector-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cert-manager-cainjector + namespace: "cert-manager" + labels: + app: cainjector + app.kubernetes.io/name: cainjector + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "cainjector" + app.kubernetes.io/version: "v1.8.0" +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: cainjector + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "cainjector" + template: + metadata: + labels: + app: cainjector + app.kubernetes.io/name: cainjector + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "cainjector" + app.kubernetes.io/version: "v1.8.0" + spec: + serviceAccountName: cert-manager-cainjector + securityContext: + runAsNonRoot: true + containers: + - name: cert-manager + image: "quay.io/jetstack/cert-manager-cainjector:v1.8.0" + imagePullPolicy: IfNotPresent + args: + - --v=2 + - --leader-election-namespace=kube-system + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + securityContext: + allowPrivilegeEscalation: false + nodeSelector: + kubernetes.io/os: linux +--- +# Source: cert-manager/templates/deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cert-manager + namespace: "cert-manager" + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.8.0" +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + template: + metadata: + labels: + app: cert-manager + app.kubernetes.io/name: cert-manager + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "controller" + app.kubernetes.io/version: "v1.8.0" + annotations: + prometheus.io/path: "/metrics" + prometheus.io/scrape: 'true' + prometheus.io/port: '9402' + spec: + serviceAccountName: cert-manager + securityContext: + + runAsNonRoot: true + containers: + - name: cert-manager + image: "quay.io/jetstack/cert-manager-controller:v1.8.0" + imagePullPolicy: IfNotPresent + args: + - --v=2 + - --cluster-resource-namespace=$(POD_NAMESPACE) + - --leader-election-namespace=kube-system + ports: + - containerPort: 9402 + name: http-metrics + protocol: TCP + securityContext: + allowPrivilegeEscalation: false + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + nodeSelector: + kubernetes.io/os: linux +--- +# Source: cert-manager/templates/webhook-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cert-manager-webhook + namespace: "cert-manager" + labels: + app: webhook + app.kubernetes.io/name: webhook + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "webhook" + app.kubernetes.io/version: "v1.8.0" +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: webhook + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "webhook" + template: + metadata: + labels: + app: webhook + app.kubernetes.io/name: webhook + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "webhook" + app.kubernetes.io/version: "v1.8.0" + spec: + serviceAccountName: cert-manager-webhook + securityContext: + runAsNonRoot: true + containers: + - name: cert-manager + image: "quay.io/jetstack/cert-manager-webhook:v1.8.0" + imagePullPolicy: IfNotPresent + args: + - --v=2 + - --secure-port=10250 + - --dynamic-serving-ca-secret-namespace=$(POD_NAMESPACE) + - --dynamic-serving-ca-secret-name=cert-manager-webhook-ca + - --dynamic-serving-dns-names=cert-manager-webhook,cert-manager-webhook.cert-manager,cert-manager-webhook.cert-manager.svc + ports: + - name: https + protocol: TCP + containerPort: 10250 + livenessProbe: + httpGet: + path: /livez + port: 6080 + scheme: HTTP + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /healthz + port: 6080 + scheme: HTTP + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 3 + securityContext: + allowPrivilegeEscalation: false + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + nodeSelector: + kubernetes.io/os: linux +--- +# Source: cert-manager/templates/webhook-mutating-webhook.yaml +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: cert-manager-webhook + labels: + app: webhook + app.kubernetes.io/name: webhook + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "webhook" + app.kubernetes.io/version: "v1.8.0" + annotations: + cert-manager.io/inject-ca-from-secret: "cert-manager/cert-manager-webhook-ca" +webhooks: + - name: webhook.cert-manager.io + rules: + - apiGroups: + - "cert-manager.io" + - "acme.cert-manager.io" + apiVersions: + - "v1" + operations: + - CREATE + - UPDATE + resources: + - "*/*" + admissionReviewVersions: ["v1"] + # This webhook only accepts v1 cert-manager resources. + # Equivalent matchPolicy ensures that non-v1 resource requests are sent to + # this webhook (after the resources have been converted to v1). + matchPolicy: Equivalent + timeoutSeconds: 10 + failurePolicy: Fail + # Only include 'sideEffects' field in Kubernetes 1.12+ + sideEffects: None + clientConfig: + service: + name: cert-manager-webhook + namespace: "cert-manager" + path: /mutate +--- +# Source: cert-manager/templates/webhook-validating-webhook.yaml +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: cert-manager-webhook + labels: + app: webhook + app.kubernetes.io/name: webhook + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: "webhook" + app.kubernetes.io/version: "v1.8.0" + annotations: + cert-manager.io/inject-ca-from-secret: "cert-manager/cert-manager-webhook-ca" +webhooks: + - name: webhook.cert-manager.io + namespaceSelector: + matchExpressions: + - key: "cert-manager.io/disable-validation" + operator: "NotIn" + values: + - "true" + - key: "name" + operator: "NotIn" + values: + - cert-manager + rules: + - apiGroups: + - "cert-manager.io" + - "acme.cert-manager.io" + apiVersions: + - "v1" + operations: + - CREATE + - UPDATE + resources: + - "*/*" + admissionReviewVersions: ["v1"] + # This webhook only accepts v1 cert-manager resources. + # Equivalent matchPolicy ensures that non-v1 resource requests are sent to + # this webhook (after the resources have been converted to v1). + matchPolicy: Equivalent + timeoutSeconds: 10 + failurePolicy: Fail + sideEffects: None + clientConfig: + service: + name: cert-manager-webhook + namespace: "cert-manager" + path: /validate diff --git a/e2e-testing/dependencies/istio/destinationrule.yaml b/e2e-testing/dependencies/istio/destinationrule.yaml new file mode 100644 index 0000000..b443605 --- /dev/null +++ b/e2e-testing/dependencies/istio/destinationrule.yaml @@ -0,0 +1,2541 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + helm.sh/resource-policy: keep + labels: + app: istio-pilot + chart: istio + heritage: Tiller + release: istio + name: destinationrules.networking.istio.io +spec: + group: networking.istio.io + names: + categories: + - istio-io + - networking-istio-io + kind: DestinationRule + listKind: DestinationRuleList + plural: destinationrules + shortNames: + - dr + singular: destinationrule + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The name of a service from the service registry + jsonPath: .spec.host + name: Host + type: string + - description: >- + CreationTimestamp is a timestamp representing the server time when + this object was created. It is not guaranteed to be set in + happens-before order across separate operations. Clients may not set + this value. It is represented in RFC3339 form and is in UTC. + Populated by the system. Read-only. Null for lists. More info: + https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha3 + schema: + openAPIV3Schema: + properties: + spec: + description: >- + Configuration affecting load balancing, outlier detection, etc. + See more details at: + https://istio.io/docs/reference/config/networking/destination-rule.html + properties: + exportTo: + description: >- + A list of namespaces to which this destination rule is + exported. + items: + type: string + type: array + host: + description: The name of a service from the service registry. + type: string + subsets: + items: + properties: + labels: + additionalProperties: + type: string + type: object + name: + description: Name of the subset. + type: string + trafficPolicy: + description: Traffic policies that apply to this subset. + properties: + connectionPool: + properties: + http: + description: HTTP connection pool settings. + properties: + h2UpgradePolicy: + description: >- + Specify if http1.1 connection should be + upgraded to http2 for the associated + destination. + enum: + - DEFAULT + - DO_NOT_UPGRADE + - UPGRADE + type: string + http1MaxPendingRequests: + description: >- + Maximum number of pending HTTP requests to + a destination. + format: int32 + type: integer + http2MaxRequests: + description: Maximum number of requests to a backend. + format: int32 + type: integer + idleTimeout: + description: >- + The idle timeout for upstream connection + pool connections. + type: string + maxRequestsPerConnection: + description: >- + Maximum number of requests per connection + to a backend. + format: int32 + type: integer + maxRetries: + format: int32 + type: integer + useClientProtocol: + description: >- + If set to true, client protocol will be + preserved while initiating connection to + backend. + type: boolean + type: object + tcp: + description: >- + Settings common to both HTTP and TCP upstream + connections. + properties: + connectTimeout: + description: TCP connection timeout. + type: string + maxConnections: + description: >- + Maximum number of HTTP1 /TCP connections + to a destination host. + format: int32 + type: integer + tcpKeepalive: + description: >- + If set then set SO_KEEPALIVE on the socket + to enable TCP Keepalives. + properties: + interval: + description: >- + The time duration between keep-alive + probes. + type: string + probes: + type: integer + time: + type: string + type: object + type: object + type: object + loadBalancer: + description: Settings controlling the load balancer algorithms. + oneOf: + - not: + anyOf: + - required: + - simple + - properties: + consistentHash: + oneOf: + - not: + anyOf: + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + required: + - consistentHash + - required: + - simple + - properties: + consistentHash: + oneOf: + - not: + anyOf: + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + required: + - consistentHash + properties: + consistentHash: + properties: + httpCookie: + description: Hash based on HTTP cookie. + properties: + name: + description: Name of the cookie. + type: string + path: + description: Path to set for the cookie. + type: string + ttl: + description: Lifetime of the cookie. + type: string + type: object + httpHeaderName: + description: Hash based on a specific HTTP header. + type: string + httpQueryParameterName: + description: >- + Hash based on a specific HTTP query + parameter. + type: string + minimumRingSize: + type: integer + useSourceIp: + description: Hash based on the source IP address. + type: boolean + type: object + localityLbSetting: + properties: + distribute: + description: >- + Optional: only one of distribute, failover + or failoverPriority can be set. + items: + properties: + from: + description: >- + Originating locality, '/' separated, + e.g. + type: string + to: + additionalProperties: + type: integer + description: >- + Map of upstream localities to traffic + distribution weights. + type: object + type: object + type: array + enabled: + description: >- + enable locality load balancing, this is + DestinationRule-level and will override + mesh wide settings in entirety. + nullable: true + type: boolean + failover: + description: >- + Optional: only one of distribute, failover + or failoverPriority can be set. + items: + properties: + from: + description: Originating region. + type: string + to: + type: string + type: object + type: array + failoverPriority: + description: >- + failoverPriority is an ordered list of + labels used to sort endpoints to do + priority based load balancing. + items: + type: string + type: array + type: object + simple: + enum: + - ROUND_ROBIN + - LEAST_CONN + - RANDOM + - PASSTHROUGH + type: string + type: object + outlierDetection: + properties: + baseEjectionTime: + description: Minimum ejection duration. + type: string + consecutive5xxErrors: + description: >- + Number of 5xx errors before a host is ejected + from the connection pool. + nullable: true + type: integer + consecutiveErrors: + format: int32 + type: integer + consecutiveGatewayErrors: + description: >- + Number of gateway errors before a host is + ejected from the connection pool. + nullable: true + type: integer + consecutiveLocalOriginFailures: + nullable: true + type: integer + interval: + description: Time interval between ejection sweep analysis. + type: string + maxEjectionPercent: + format: int32 + type: integer + minHealthPercent: + format: int32 + type: integer + splitExternalLocalOriginErrors: + description: >- + Determines whether to distinguish local origin + failures from external errors. + type: boolean + type: object + portLevelSettings: + description: Traffic policies specific to individual ports. + items: + properties: + connectionPool: + properties: + http: + description: HTTP connection pool settings. + properties: + h2UpgradePolicy: + description: >- + Specify if http1.1 connection should be + upgraded to http2 for the associated + destination. + enum: + - DEFAULT + - DO_NOT_UPGRADE + - UPGRADE + type: string + http1MaxPendingRequests: + description: >- + Maximum number of pending HTTP requests + to a destination. + format: int32 + type: integer + http2MaxRequests: + description: Maximum number of requests to a backend. + format: int32 + type: integer + idleTimeout: + description: >- + The idle timeout for upstream connection + pool connections. + type: string + maxRequestsPerConnection: + description: >- + Maximum number of requests per + connection to a backend. + format: int32 + type: integer + maxRetries: + format: int32 + type: integer + useClientProtocol: + description: >- + If set to true, client protocol will be + preserved while initiating connection to + backend. + type: boolean + type: object + tcp: + description: >- + Settings common to both HTTP and TCP + upstream connections. + properties: + connectTimeout: + description: TCP connection timeout. + type: string + maxConnections: + description: >- + Maximum number of HTTP1 /TCP connections + to a destination host. + format: int32 + type: integer + tcpKeepalive: + description: >- + If set then set SO_KEEPALIVE on the + socket to enable TCP Keepalives. + properties: + interval: + description: >- + The time duration between keep-alive + probes. + type: string + probes: + type: integer + time: + type: string + type: object + type: object + type: object + loadBalancer: + description: >- + Settings controlling the load balancer + algorithms. + oneOf: + - not: + anyOf: + - required: + - simple + - properties: + consistentHash: + oneOf: + - not: + anyOf: + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + required: + - consistentHash + - required: + - simple + - properties: + consistentHash: + oneOf: + - not: + anyOf: + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + required: + - consistentHash + properties: + consistentHash: + properties: + httpCookie: + description: Hash based on HTTP cookie. + properties: + name: + description: Name of the cookie. + type: string + path: + description: Path to set for the cookie. + type: string + ttl: + description: Lifetime of the cookie. + type: string + type: object + httpHeaderName: + description: Hash based on a specific HTTP header. + type: string + httpQueryParameterName: + description: >- + Hash based on a specific HTTP query + parameter. + type: string + minimumRingSize: + type: integer + useSourceIp: + description: Hash based on the source IP address. + type: boolean + type: object + localityLbSetting: + properties: + distribute: + description: >- + Optional: only one of distribute, + failover or failoverPriority can be set. + items: + properties: + from: + description: >- + Originating locality, '/' separated, + e.g. + type: string + to: + additionalProperties: + type: integer + description: >- + Map of upstream localities to traffic + distribution weights. + type: object + type: object + type: array + enabled: + description: >- + enable locality load balancing, this is + DestinationRule-level and will override + mesh wide settings in entirety. + nullable: true + type: boolean + failover: + description: >- + Optional: only one of distribute, + failover or failoverPriority can be set. + items: + properties: + from: + description: Originating region. + type: string + to: + type: string + type: object + type: array + failoverPriority: + description: >- + failoverPriority is an ordered list of + labels used to sort endpoints to do + priority based load balancing. + items: + type: string + type: array + type: object + simple: + enum: + - ROUND_ROBIN + - LEAST_CONN + - RANDOM + - PASSTHROUGH + type: string + type: object + outlierDetection: + properties: + baseEjectionTime: + description: Minimum ejection duration. + type: string + consecutive5xxErrors: + description: >- + Number of 5xx errors before a host is + ejected from the connection pool. + nullable: true + type: integer + consecutiveErrors: + format: int32 + type: integer + consecutiveGatewayErrors: + description: >- + Number of gateway errors before a host + is ejected from the connection pool. + nullable: true + type: integer + consecutiveLocalOriginFailures: + nullable: true + type: integer + interval: + description: >- + Time interval between ejection sweep + analysis. + type: string + maxEjectionPercent: + format: int32 + type: integer + minHealthPercent: + format: int32 + type: integer + splitExternalLocalOriginErrors: + description: >- + Determines whether to distinguish local + origin failures from external errors. + type: boolean + type: object + port: + properties: + number: + type: integer + type: object + tls: + description: >- + TLS related settings for connections to the + upstream service. + properties: + caCertificates: + type: string + clientCertificate: + description: REQUIRED if mode is `MUTUAL`. + type: string + credentialName: + type: string + insecureSkipVerify: + nullable: true + type: boolean + mode: + enum: + - DISABLE + - SIMPLE + - MUTUAL + - ISTIO_MUTUAL + type: string + privateKey: + description: REQUIRED if mode is `MUTUAL`. + type: string + sni: + description: >- + SNI string to present to the server + during TLS handshake. + type: string + subjectAltNames: + items: + type: string + type: array + type: object + type: object + type: array + tls: + description: >- + TLS related settings for connections to the + upstream service. + properties: + caCertificates: + type: string + clientCertificate: + description: REQUIRED if mode is `MUTUAL`. + type: string + credentialName: + type: string + insecureSkipVerify: + nullable: true + type: boolean + mode: + enum: + - DISABLE + - SIMPLE + - MUTUAL + - ISTIO_MUTUAL + type: string + privateKey: + description: REQUIRED if mode is `MUTUAL`. + type: string + sni: + description: >- + SNI string to present to the server during TLS + handshake. + type: string + subjectAltNames: + items: + type: string + type: array + type: object + type: object + type: object + type: array + trafficPolicy: + properties: + connectionPool: + properties: + http: + description: HTTP connection pool settings. + properties: + h2UpgradePolicy: + description: >- + Specify if http1.1 connection should be upgraded + to http2 for the associated destination. + enum: + - DEFAULT + - DO_NOT_UPGRADE + - UPGRADE + type: string + http1MaxPendingRequests: + description: >- + Maximum number of pending HTTP requests to a + destination. + format: int32 + type: integer + http2MaxRequests: + description: Maximum number of requests to a backend. + format: int32 + type: integer + idleTimeout: + description: >- + The idle timeout for upstream connection pool + connections. + type: string + maxRequestsPerConnection: + description: >- + Maximum number of requests per connection to a + backend. + format: int32 + type: integer + maxRetries: + format: int32 + type: integer + useClientProtocol: + description: >- + If set to true, client protocol will be + preserved while initiating connection to + backend. + type: boolean + type: object + tcp: + description: >- + Settings common to both HTTP and TCP upstream + connections. + properties: + connectTimeout: + description: TCP connection timeout. + type: string + maxConnections: + description: >- + Maximum number of HTTP1 /TCP connections to a + destination host. + format: int32 + type: integer + tcpKeepalive: + description: >- + If set then set SO_KEEPALIVE on the socket to + enable TCP Keepalives. + properties: + interval: + description: The time duration between keep-alive probes. + type: string + probes: + type: integer + time: + type: string + type: object + type: object + type: object + loadBalancer: + description: Settings controlling the load balancer algorithms. + oneOf: + - not: + anyOf: + - required: + - simple + - properties: + consistentHash: + oneOf: + - not: + anyOf: + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + required: + - consistentHash + - required: + - simple + - properties: + consistentHash: + oneOf: + - not: + anyOf: + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + required: + - consistentHash + properties: + consistentHash: + properties: + httpCookie: + description: Hash based on HTTP cookie. + properties: + name: + description: Name of the cookie. + type: string + path: + description: Path to set for the cookie. + type: string + ttl: + description: Lifetime of the cookie. + type: string + type: object + httpHeaderName: + description: Hash based on a specific HTTP header. + type: string + httpQueryParameterName: + description: Hash based on a specific HTTP query parameter. + type: string + minimumRingSize: + type: integer + useSourceIp: + description: Hash based on the source IP address. + type: boolean + type: object + localityLbSetting: + properties: + distribute: + description: >- + Optional: only one of distribute, failover or + failoverPriority can be set. + items: + properties: + from: + description: 'Originating locality, ''/'' separated, e.g.' + type: string + to: + additionalProperties: + type: integer + description: >- + Map of upstream localities to traffic + distribution weights. + type: object + type: object + type: array + enabled: + description: >- + enable locality load balancing, this is + DestinationRule-level and will override mesh + wide settings in entirety. + nullable: true + type: boolean + failover: + description: >- + Optional: only one of distribute, failover or + failoverPriority can be set. + items: + properties: + from: + description: Originating region. + type: string + to: + type: string + type: object + type: array + failoverPriority: + description: >- + failoverPriority is an ordered list of labels + used to sort endpoints to do priority based load + balancing. + items: + type: string + type: array + type: object + simple: + enum: + - ROUND_ROBIN + - LEAST_CONN + - RANDOM + - PASSTHROUGH + type: string + type: object + outlierDetection: + properties: + baseEjectionTime: + description: Minimum ejection duration. + type: string + consecutive5xxErrors: + description: >- + Number of 5xx errors before a host is ejected from + the connection pool. + nullable: true + type: integer + consecutiveErrors: + format: int32 + type: integer + consecutiveGatewayErrors: + description: >- + Number of gateway errors before a host is ejected + from the connection pool. + nullable: true + type: integer + consecutiveLocalOriginFailures: + nullable: true + type: integer + interval: + description: Time interval between ejection sweep analysis. + type: string + maxEjectionPercent: + format: int32 + type: integer + minHealthPercent: + format: int32 + type: integer + splitExternalLocalOriginErrors: + description: >- + Determines whether to distinguish local origin + failures from external errors. + type: boolean + type: object + portLevelSettings: + description: Traffic policies specific to individual ports. + items: + properties: + connectionPool: + properties: + http: + description: HTTP connection pool settings. + properties: + h2UpgradePolicy: + description: >- + Specify if http1.1 connection should be + upgraded to http2 for the associated + destination. + enum: + - DEFAULT + - DO_NOT_UPGRADE + - UPGRADE + type: string + http1MaxPendingRequests: + description: >- + Maximum number of pending HTTP requests to + a destination. + format: int32 + type: integer + http2MaxRequests: + description: Maximum number of requests to a backend. + format: int32 + type: integer + idleTimeout: + description: >- + The idle timeout for upstream connection + pool connections. + type: string + maxRequestsPerConnection: + description: >- + Maximum number of requests per connection + to a backend. + format: int32 + type: integer + maxRetries: + format: int32 + type: integer + useClientProtocol: + description: >- + If set to true, client protocol will be + preserved while initiating connection to + backend. + type: boolean + type: object + tcp: + description: >- + Settings common to both HTTP and TCP upstream + connections. + properties: + connectTimeout: + description: TCP connection timeout. + type: string + maxConnections: + description: >- + Maximum number of HTTP1 /TCP connections + to a destination host. + format: int32 + type: integer + tcpKeepalive: + description: >- + If set then set SO_KEEPALIVE on the socket + to enable TCP Keepalives. + properties: + interval: + description: >- + The time duration between keep-alive + probes. + type: string + probes: + type: integer + time: + type: string + type: object + type: object + type: object + loadBalancer: + description: Settings controlling the load balancer algorithms. + oneOf: + - not: + anyOf: + - required: + - simple + - properties: + consistentHash: + oneOf: + - not: + anyOf: + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + required: + - consistentHash + - required: + - simple + - properties: + consistentHash: + oneOf: + - not: + anyOf: + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + required: + - consistentHash + properties: + consistentHash: + properties: + httpCookie: + description: Hash based on HTTP cookie. + properties: + name: + description: Name of the cookie. + type: string + path: + description: Path to set for the cookie. + type: string + ttl: + description: Lifetime of the cookie. + type: string + type: object + httpHeaderName: + description: Hash based on a specific HTTP header. + type: string + httpQueryParameterName: + description: >- + Hash based on a specific HTTP query + parameter. + type: string + minimumRingSize: + type: integer + useSourceIp: + description: Hash based on the source IP address. + type: boolean + type: object + localityLbSetting: + properties: + distribute: + description: >- + Optional: only one of distribute, failover + or failoverPriority can be set. + items: + properties: + from: + description: >- + Originating locality, '/' separated, + e.g. + type: string + to: + additionalProperties: + type: integer + description: >- + Map of upstream localities to traffic + distribution weights. + type: object + type: object + type: array + enabled: + description: >- + enable locality load balancing, this is + DestinationRule-level and will override + mesh wide settings in entirety. + nullable: true + type: boolean + failover: + description: >- + Optional: only one of distribute, failover + or failoverPriority can be set. + items: + properties: + from: + description: Originating region. + type: string + to: + type: string + type: object + type: array + failoverPriority: + description: >- + failoverPriority is an ordered list of + labels used to sort endpoints to do + priority based load balancing. + items: + type: string + type: array + type: object + simple: + enum: + - ROUND_ROBIN + - LEAST_CONN + - RANDOM + - PASSTHROUGH + type: string + type: object + outlierDetection: + properties: + baseEjectionTime: + description: Minimum ejection duration. + type: string + consecutive5xxErrors: + description: >- + Number of 5xx errors before a host is ejected + from the connection pool. + nullable: true + type: integer + consecutiveErrors: + format: int32 + type: integer + consecutiveGatewayErrors: + description: >- + Number of gateway errors before a host is + ejected from the connection pool. + nullable: true + type: integer + consecutiveLocalOriginFailures: + nullable: true + type: integer + interval: + description: Time interval between ejection sweep analysis. + type: string + maxEjectionPercent: + format: int32 + type: integer + minHealthPercent: + format: int32 + type: integer + splitExternalLocalOriginErrors: + description: >- + Determines whether to distinguish local origin + failures from external errors. + type: boolean + type: object + port: + properties: + number: + type: integer + type: object + tls: + description: >- + TLS related settings for connections to the + upstream service. + properties: + caCertificates: + type: string + clientCertificate: + description: REQUIRED if mode is `MUTUAL`. + type: string + credentialName: + type: string + insecureSkipVerify: + nullable: true + type: boolean + mode: + enum: + - DISABLE + - SIMPLE + - MUTUAL + - ISTIO_MUTUAL + type: string + privateKey: + description: REQUIRED if mode is `MUTUAL`. + type: string + sni: + description: >- + SNI string to present to the server during TLS + handshake. + type: string + subjectAltNames: + items: + type: string + type: array + type: object + type: object + type: array + tls: + description: >- + TLS related settings for connections to the upstream + service. + properties: + caCertificates: + type: string + clientCertificate: + description: REQUIRED if mode is `MUTUAL`. + type: string + credentialName: + type: string + insecureSkipVerify: + nullable: true + type: boolean + mode: + enum: + - DISABLE + - SIMPLE + - MUTUAL + - ISTIO_MUTUAL + type: string + privateKey: + description: REQUIRED if mode is `MUTUAL`. + type: string + sni: + description: >- + SNI string to present to the server during TLS + handshake. + type: string + subjectAltNames: + items: + type: string + type: array + type: object + type: object + type: object + status: + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - description: The name of a service from the service registry + jsonPath: .spec.host + name: Host + type: string + - description: >- + CreationTimestamp is a timestamp representing the server time when + this object was created. It is not guaranteed to be set in + happens-before order across separate operations. Clients may not set + this value. It is represented in RFC3339 form and is in UTC. + Populated by the system. Read-only. Null for lists. More info: + https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + properties: + spec: + description: >- + Configuration affecting load balancing, outlier detection, etc. + See more details at: + https://istio.io/docs/reference/config/networking/destination-rule.html + properties: + exportTo: + description: >- + A list of namespaces to which this destination rule is + exported. + items: + type: string + type: array + host: + description: The name of a service from the service registry. + type: string + subsets: + items: + properties: + labels: + additionalProperties: + type: string + type: object + name: + description: Name of the subset. + type: string + trafficPolicy: + description: Traffic policies that apply to this subset. + properties: + connectionPool: + properties: + http: + description: HTTP connection pool settings. + properties: + h2UpgradePolicy: + description: >- + Specify if http1.1 connection should be + upgraded to http2 for the associated + destination. + enum: + - DEFAULT + - DO_NOT_UPGRADE + - UPGRADE + type: string + http1MaxPendingRequests: + description: >- + Maximum number of pending HTTP requests to + a destination. + format: int32 + type: integer + http2MaxRequests: + description: Maximum number of requests to a backend. + format: int32 + type: integer + idleTimeout: + description: >- + The idle timeout for upstream connection + pool connections. + type: string + maxRequestsPerConnection: + description: >- + Maximum number of requests per connection + to a backend. + format: int32 + type: integer + maxRetries: + format: int32 + type: integer + useClientProtocol: + description: >- + If set to true, client protocol will be + preserved while initiating connection to + backend. + type: boolean + type: object + tcp: + description: >- + Settings common to both HTTP and TCP upstream + connections. + properties: + connectTimeout: + description: TCP connection timeout. + type: string + maxConnections: + description: >- + Maximum number of HTTP1 /TCP connections + to a destination host. + format: int32 + type: integer + tcpKeepalive: + description: >- + If set then set SO_KEEPALIVE on the socket + to enable TCP Keepalives. + properties: + interval: + description: >- + The time duration between keep-alive + probes. + type: string + probes: + type: integer + time: + type: string + type: object + type: object + type: object + loadBalancer: + description: Settings controlling the load balancer algorithms. + oneOf: + - not: + anyOf: + - required: + - simple + - properties: + consistentHash: + oneOf: + - not: + anyOf: + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + required: + - consistentHash + - required: + - simple + - properties: + consistentHash: + oneOf: + - not: + anyOf: + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + required: + - consistentHash + properties: + consistentHash: + properties: + httpCookie: + description: Hash based on HTTP cookie. + properties: + name: + description: Name of the cookie. + type: string + path: + description: Path to set for the cookie. + type: string + ttl: + description: Lifetime of the cookie. + type: string + type: object + httpHeaderName: + description: Hash based on a specific HTTP header. + type: string + httpQueryParameterName: + description: >- + Hash based on a specific HTTP query + parameter. + type: string + minimumRingSize: + type: integer + useSourceIp: + description: Hash based on the source IP address. + type: boolean + type: object + localityLbSetting: + properties: + distribute: + description: >- + Optional: only one of distribute, failover + or failoverPriority can be set. + items: + properties: + from: + description: >- + Originating locality, '/' separated, + e.g. + type: string + to: + additionalProperties: + type: integer + description: >- + Map of upstream localities to traffic + distribution weights. + type: object + type: object + type: array + enabled: + description: >- + enable locality load balancing, this is + DestinationRule-level and will override + mesh wide settings in entirety. + nullable: true + type: boolean + failover: + description: >- + Optional: only one of distribute, failover + or failoverPriority can be set. + items: + properties: + from: + description: Originating region. + type: string + to: + type: string + type: object + type: array + failoverPriority: + description: >- + failoverPriority is an ordered list of + labels used to sort endpoints to do + priority based load balancing. + items: + type: string + type: array + type: object + simple: + enum: + - ROUND_ROBIN + - LEAST_CONN + - RANDOM + - PASSTHROUGH + type: string + type: object + outlierDetection: + properties: + baseEjectionTime: + description: Minimum ejection duration. + type: string + consecutive5xxErrors: + description: >- + Number of 5xx errors before a host is ejected + from the connection pool. + nullable: true + type: integer + consecutiveErrors: + format: int32 + type: integer + consecutiveGatewayErrors: + description: >- + Number of gateway errors before a host is + ejected from the connection pool. + nullable: true + type: integer + consecutiveLocalOriginFailures: + nullable: true + type: integer + interval: + description: Time interval between ejection sweep analysis. + type: string + maxEjectionPercent: + format: int32 + type: integer + minHealthPercent: + format: int32 + type: integer + splitExternalLocalOriginErrors: + description: >- + Determines whether to distinguish local origin + failures from external errors. + type: boolean + type: object + portLevelSettings: + description: Traffic policies specific to individual ports. + items: + properties: + connectionPool: + properties: + http: + description: HTTP connection pool settings. + properties: + h2UpgradePolicy: + description: >- + Specify if http1.1 connection should be + upgraded to http2 for the associated + destination. + enum: + - DEFAULT + - DO_NOT_UPGRADE + - UPGRADE + type: string + http1MaxPendingRequests: + description: >- + Maximum number of pending HTTP requests + to a destination. + format: int32 + type: integer + http2MaxRequests: + description: Maximum number of requests to a backend. + format: int32 + type: integer + idleTimeout: + description: >- + The idle timeout for upstream connection + pool connections. + type: string + maxRequestsPerConnection: + description: >- + Maximum number of requests per + connection to a backend. + format: int32 + type: integer + maxRetries: + format: int32 + type: integer + useClientProtocol: + description: >- + If set to true, client protocol will be + preserved while initiating connection to + backend. + type: boolean + type: object + tcp: + description: >- + Settings common to both HTTP and TCP + upstream connections. + properties: + connectTimeout: + description: TCP connection timeout. + type: string + maxConnections: + description: >- + Maximum number of HTTP1 /TCP connections + to a destination host. + format: int32 + type: integer + tcpKeepalive: + description: >- + If set then set SO_KEEPALIVE on the + socket to enable TCP Keepalives. + properties: + interval: + description: >- + The time duration between keep-alive + probes. + type: string + probes: + type: integer + time: + type: string + type: object + type: object + type: object + loadBalancer: + description: >- + Settings controlling the load balancer + algorithms. + oneOf: + - not: + anyOf: + - required: + - simple + - properties: + consistentHash: + oneOf: + - not: + anyOf: + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + required: + - consistentHash + - required: + - simple + - properties: + consistentHash: + oneOf: + - not: + anyOf: + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + required: + - consistentHash + properties: + consistentHash: + properties: + httpCookie: + description: Hash based on HTTP cookie. + properties: + name: + description: Name of the cookie. + type: string + path: + description: Path to set for the cookie. + type: string + ttl: + description: Lifetime of the cookie. + type: string + type: object + httpHeaderName: + description: Hash based on a specific HTTP header. + type: string + httpQueryParameterName: + description: >- + Hash based on a specific HTTP query + parameter. + type: string + minimumRingSize: + type: integer + useSourceIp: + description: Hash based on the source IP address. + type: boolean + type: object + localityLbSetting: + properties: + distribute: + description: >- + Optional: only one of distribute, + failover or failoverPriority can be set. + items: + properties: + from: + description: >- + Originating locality, '/' separated, + e.g. + type: string + to: + additionalProperties: + type: integer + description: >- + Map of upstream localities to traffic + distribution weights. + type: object + type: object + type: array + enabled: + description: >- + enable locality load balancing, this is + DestinationRule-level and will override + mesh wide settings in entirety. + nullable: true + type: boolean + failover: + description: >- + Optional: only one of distribute, + failover or failoverPriority can be set. + items: + properties: + from: + description: Originating region. + type: string + to: + type: string + type: object + type: array + failoverPriority: + description: >- + failoverPriority is an ordered list of + labels used to sort endpoints to do + priority based load balancing. + items: + type: string + type: array + type: object + simple: + enum: + - ROUND_ROBIN + - LEAST_CONN + - RANDOM + - PASSTHROUGH + type: string + type: object + outlierDetection: + properties: + baseEjectionTime: + description: Minimum ejection duration. + type: string + consecutive5xxErrors: + description: >- + Number of 5xx errors before a host is + ejected from the connection pool. + nullable: true + type: integer + consecutiveErrors: + format: int32 + type: integer + consecutiveGatewayErrors: + description: >- + Number of gateway errors before a host + is ejected from the connection pool. + nullable: true + type: integer + consecutiveLocalOriginFailures: + nullable: true + type: integer + interval: + description: >- + Time interval between ejection sweep + analysis. + type: string + maxEjectionPercent: + format: int32 + type: integer + minHealthPercent: + format: int32 + type: integer + splitExternalLocalOriginErrors: + description: >- + Determines whether to distinguish local + origin failures from external errors. + type: boolean + type: object + port: + properties: + number: + type: integer + type: object + tls: + description: >- + TLS related settings for connections to the + upstream service. + properties: + caCertificates: + type: string + clientCertificate: + description: REQUIRED if mode is `MUTUAL`. + type: string + credentialName: + type: string + insecureSkipVerify: + nullable: true + type: boolean + mode: + enum: + - DISABLE + - SIMPLE + - MUTUAL + - ISTIO_MUTUAL + type: string + privateKey: + description: REQUIRED if mode is `MUTUAL`. + type: string + sni: + description: >- + SNI string to present to the server + during TLS handshake. + type: string + subjectAltNames: + items: + type: string + type: array + type: object + type: object + type: array + tls: + description: >- + TLS related settings for connections to the + upstream service. + properties: + caCertificates: + type: string + clientCertificate: + description: REQUIRED if mode is `MUTUAL`. + type: string + credentialName: + type: string + insecureSkipVerify: + nullable: true + type: boolean + mode: + enum: + - DISABLE + - SIMPLE + - MUTUAL + - ISTIO_MUTUAL + type: string + privateKey: + description: REQUIRED if mode is `MUTUAL`. + type: string + sni: + description: >- + SNI string to present to the server during TLS + handshake. + type: string + subjectAltNames: + items: + type: string + type: array + type: object + type: object + type: object + type: array + trafficPolicy: + properties: + connectionPool: + properties: + http: + description: HTTP connection pool settings. + properties: + h2UpgradePolicy: + description: >- + Specify if http1.1 connection should be upgraded + to http2 for the associated destination. + enum: + - DEFAULT + - DO_NOT_UPGRADE + - UPGRADE + type: string + http1MaxPendingRequests: + description: >- + Maximum number of pending HTTP requests to a + destination. + format: int32 + type: integer + http2MaxRequests: + description: Maximum number of requests to a backend. + format: int32 + type: integer + idleTimeout: + description: >- + The idle timeout for upstream connection pool + connections. + type: string + maxRequestsPerConnection: + description: >- + Maximum number of requests per connection to a + backend. + format: int32 + type: integer + maxRetries: + format: int32 + type: integer + useClientProtocol: + description: >- + If set to true, client protocol will be + preserved while initiating connection to + backend. + type: boolean + type: object + tcp: + description: >- + Settings common to both HTTP and TCP upstream + connections. + properties: + connectTimeout: + description: TCP connection timeout. + type: string + maxConnections: + description: >- + Maximum number of HTTP1 /TCP connections to a + destination host. + format: int32 + type: integer + tcpKeepalive: + description: >- + If set then set SO_KEEPALIVE on the socket to + enable TCP Keepalives. + properties: + interval: + description: The time duration between keep-alive probes. + type: string + probes: + type: integer + time: + type: string + type: object + type: object + type: object + loadBalancer: + description: Settings controlling the load balancer algorithms. + oneOf: + - not: + anyOf: + - required: + - simple + - properties: + consistentHash: + oneOf: + - not: + anyOf: + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + required: + - consistentHash + - required: + - simple + - properties: + consistentHash: + oneOf: + - not: + anyOf: + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + required: + - consistentHash + properties: + consistentHash: + properties: + httpCookie: + description: Hash based on HTTP cookie. + properties: + name: + description: Name of the cookie. + type: string + path: + description: Path to set for the cookie. + type: string + ttl: + description: Lifetime of the cookie. + type: string + type: object + httpHeaderName: + description: Hash based on a specific HTTP header. + type: string + httpQueryParameterName: + description: Hash based on a specific HTTP query parameter. + type: string + minimumRingSize: + type: integer + useSourceIp: + description: Hash based on the source IP address. + type: boolean + type: object + localityLbSetting: + properties: + distribute: + description: >- + Optional: only one of distribute, failover or + failoverPriority can be set. + items: + properties: + from: + description: 'Originating locality, ''/'' separated, e.g.' + type: string + to: + additionalProperties: + type: integer + description: >- + Map of upstream localities to traffic + distribution weights. + type: object + type: object + type: array + enabled: + description: >- + enable locality load balancing, this is + DestinationRule-level and will override mesh + wide settings in entirety. + nullable: true + type: boolean + failover: + description: >- + Optional: only one of distribute, failover or + failoverPriority can be set. + items: + properties: + from: + description: Originating region. + type: string + to: + type: string + type: object + type: array + failoverPriority: + description: >- + failoverPriority is an ordered list of labels + used to sort endpoints to do priority based load + balancing. + items: + type: string + type: array + type: object + simple: + enum: + - ROUND_ROBIN + - LEAST_CONN + - RANDOM + - PASSTHROUGH + type: string + type: object + outlierDetection: + properties: + baseEjectionTime: + description: Minimum ejection duration. + type: string + consecutive5xxErrors: + description: >- + Number of 5xx errors before a host is ejected from + the connection pool. + nullable: true + type: integer + consecutiveErrors: + format: int32 + type: integer + consecutiveGatewayErrors: + description: >- + Number of gateway errors before a host is ejected + from the connection pool. + nullable: true + type: integer + consecutiveLocalOriginFailures: + nullable: true + type: integer + interval: + description: Time interval between ejection sweep analysis. + type: string + maxEjectionPercent: + format: int32 + type: integer + minHealthPercent: + format: int32 + type: integer + splitExternalLocalOriginErrors: + description: >- + Determines whether to distinguish local origin + failures from external errors. + type: boolean + type: object + portLevelSettings: + description: Traffic policies specific to individual ports. + items: + properties: + connectionPool: + properties: + http: + description: HTTP connection pool settings. + properties: + h2UpgradePolicy: + description: >- + Specify if http1.1 connection should be + upgraded to http2 for the associated + destination. + enum: + - DEFAULT + - DO_NOT_UPGRADE + - UPGRADE + type: string + http1MaxPendingRequests: + description: >- + Maximum number of pending HTTP requests to + a destination. + format: int32 + type: integer + http2MaxRequests: + description: Maximum number of requests to a backend. + format: int32 + type: integer + idleTimeout: + description: >- + The idle timeout for upstream connection + pool connections. + type: string + maxRequestsPerConnection: + description: >- + Maximum number of requests per connection + to a backend. + format: int32 + type: integer + maxRetries: + format: int32 + type: integer + useClientProtocol: + description: >- + If set to true, client protocol will be + preserved while initiating connection to + backend. + type: boolean + type: object + tcp: + description: >- + Settings common to both HTTP and TCP upstream + connections. + properties: + connectTimeout: + description: TCP connection timeout. + type: string + maxConnections: + description: >- + Maximum number of HTTP1 /TCP connections + to a destination host. + format: int32 + type: integer + tcpKeepalive: + description: >- + If set then set SO_KEEPALIVE on the socket + to enable TCP Keepalives. + properties: + interval: + description: >- + The time duration between keep-alive + probes. + type: string + probes: + type: integer + time: + type: string + type: object + type: object + type: object + loadBalancer: + description: Settings controlling the load balancer algorithms. + oneOf: + - not: + anyOf: + - required: + - simple + - properties: + consistentHash: + oneOf: + - not: + anyOf: + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + required: + - consistentHash + - required: + - simple + - properties: + consistentHash: + oneOf: + - not: + anyOf: + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + - required: + - httpHeaderName + - required: + - httpCookie + - required: + - useSourceIp + - required: + - httpQueryParameterName + required: + - consistentHash + properties: + consistentHash: + properties: + httpCookie: + description: Hash based on HTTP cookie. + properties: + name: + description: Name of the cookie. + type: string + path: + description: Path to set for the cookie. + type: string + ttl: + description: Lifetime of the cookie. + type: string + type: object + httpHeaderName: + description: Hash based on a specific HTTP header. + type: string + httpQueryParameterName: + description: >- + Hash based on a specific HTTP query + parameter. + type: string + minimumRingSize: + type: integer + useSourceIp: + description: Hash based on the source IP address. + type: boolean + type: object + localityLbSetting: + properties: + distribute: + description: >- + Optional: only one of distribute, failover + or failoverPriority can be set. + items: + properties: + from: + description: >- + Originating locality, '/' separated, + e.g. + type: string + to: + additionalProperties: + type: integer + description: >- + Map of upstream localities to traffic + distribution weights. + type: object + type: object + type: array + enabled: + description: >- + enable locality load balancing, this is + DestinationRule-level and will override + mesh wide settings in entirety. + nullable: true + type: boolean + failover: + description: >- + Optional: only one of distribute, failover + or failoverPriority can be set. + items: + properties: + from: + description: Originating region. + type: string + to: + type: string + type: object + type: array + failoverPriority: + description: >- + failoverPriority is an ordered list of + labels used to sort endpoints to do + priority based load balancing. + items: + type: string + type: array + type: object + simple: + enum: + - ROUND_ROBIN + - LEAST_CONN + - RANDOM + - PASSTHROUGH + type: string + type: object + outlierDetection: + properties: + baseEjectionTime: + description: Minimum ejection duration. + type: string + consecutive5xxErrors: + description: >- + Number of 5xx errors before a host is ejected + from the connection pool. + nullable: true + type: integer + consecutiveErrors: + format: int32 + type: integer + consecutiveGatewayErrors: + description: >- + Number of gateway errors before a host is + ejected from the connection pool. + nullable: true + type: integer + consecutiveLocalOriginFailures: + nullable: true + type: integer + interval: + description: Time interval between ejection sweep analysis. + type: string + maxEjectionPercent: + format: int32 + type: integer + minHealthPercent: + format: int32 + type: integer + splitExternalLocalOriginErrors: + description: >- + Determines whether to distinguish local origin + failures from external errors. + type: boolean + type: object + port: + properties: + number: + type: integer + type: object + tls: + description: >- + TLS related settings for connections to the + upstream service. + properties: + caCertificates: + type: string + clientCertificate: + description: REQUIRED if mode is `MUTUAL`. + type: string + credentialName: + type: string + insecureSkipVerify: + nullable: true + type: boolean + mode: + enum: + - DISABLE + - SIMPLE + - MUTUAL + - ISTIO_MUTUAL + type: string + privateKey: + description: REQUIRED if mode is `MUTUAL`. + type: string + sni: + description: >- + SNI string to present to the server during TLS + handshake. + type: string + subjectAltNames: + items: + type: string + type: array + type: object + type: object + type: array + tls: + description: >- + TLS related settings for connections to the upstream + service. + properties: + caCertificates: + type: string + clientCertificate: + description: REQUIRED if mode is `MUTUAL`. + type: string + credentialName: + type: string + insecureSkipVerify: + nullable: true + type: boolean + mode: + enum: + - DISABLE + - SIMPLE + - MUTUAL + - ISTIO_MUTUAL + type: string + privateKey: + description: REQUIRED if mode is `MUTUAL`. + type: string + sni: + description: >- + SNI string to present to the server during TLS + handshake. + type: string + subjectAltNames: + items: + type: string + type: array + type: object + type: object + type: object + status: + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} diff --git a/e2e-testing/dependencies/istio/virtualservice.yaml b/e2e-testing/dependencies/istio/virtualservice.yaml new file mode 100644 index 0000000..55bf13e --- /dev/null +++ b/e2e-testing/dependencies/istio/virtualservice.yaml @@ -0,0 +1,1643 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + helm.sh/resource-policy: keep + labels: + app: istio-pilot + chart: istio + heritage: Tiller + release: istio + name: virtualservices.networking.istio.io +spec: + group: networking.istio.io + names: + categories: + - istio-io + - networking-istio-io + kind: VirtualService + listKind: VirtualServiceList + plural: virtualservices + shortNames: + - vs + singular: virtualservice + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The names of gateways and sidecars that should apply these routes + jsonPath: .spec.gateways + name: Gateways + type: string + - description: The destination hosts to which traffic is being sent + jsonPath: .spec.hosts + name: Hosts + type: string + - description: >- + CreationTimestamp is a timestamp representing the server time when + this object was created. It is not guaranteed to be set in + happens-before order across separate operations. Clients may not set + this value. It is represented in RFC3339 form and is in UTC. + Populated by the system. Read-only. Null for lists. More info: + https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha3 + schema: + openAPIV3Schema: + properties: + spec: + description: >- + Configuration affecting label/content routing, sni routing, etc. + See more details at: + https://istio.io/docs/reference/config/networking/virtual-service.html + properties: + exportTo: + description: >- + A list of namespaces to which this virtual service is + exported. + items: + type: string + type: array + gateways: + description: >- + The names of gateways and sidecars that should apply these + routes. + items: + type: string + type: array + hosts: + description: The destination hosts to which traffic is being sent. + items: + type: string + type: array + http: + description: An ordered list of route rules for HTTP traffic. + items: + properties: + corsPolicy: + description: Cross-Origin Resource Sharing policy (CORS). + properties: + allowCredentials: + nullable: true + type: boolean + allowHeaders: + items: + type: string + type: array + allowMethods: + description: >- + List of HTTP methods allowed to access the + resource. + items: + type: string + type: array + allowOrigin: + description: >- + The list of origins that are allowed to perform + CORS requests. + items: + type: string + type: array + allowOrigins: + description: String patterns that match allowed origins. + items: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + type: string + prefix: + type: string + regex: + description: >- + RE2 style regex-based match + (https://github.com/google/re2/wiki/Syntax). + type: string + type: object + type: array + exposeHeaders: + items: + type: string + type: array + maxAge: + type: string + type: object + delegate: + properties: + name: + description: >- + Name specifies the name of the delegate + VirtualService. + type: string + namespace: + description: >- + Namespace specifies the namespace where the + delegate VirtualService resides. + type: string + type: object + fault: + description: >- + Fault injection policy to apply on HTTP traffic at the + client side. + properties: + abort: + oneOf: + - not: + anyOf: + - required: + - httpStatus + - required: + - grpcStatus + - required: + - http2Error + - required: + - httpStatus + - required: + - grpcStatus + - required: + - http2Error + properties: + grpcStatus: + type: string + http2Error: + type: string + httpStatus: + description: >- + HTTP status code to use to abort the Http + request. + format: int32 + type: integer + percentage: + description: >- + Percentage of requests to be aborted with the + error code provided. + properties: + value: + format: double + type: number + type: object + type: object + delay: + oneOf: + - not: + anyOf: + - required: + - fixedDelay + - required: + - exponentialDelay + - required: + - fixedDelay + - required: + - exponentialDelay + properties: + exponentialDelay: + type: string + fixedDelay: + description: >- + Add a fixed delay before forwarding the + request. + type: string + percent: + description: >- + Percentage of requests on which the delay will + be injected (0-100). + format: int32 + type: integer + percentage: + description: >- + Percentage of requests on which the delay will + be injected. + properties: + value: + format: double + type: number + type: object + type: object + type: object + headers: + properties: + request: + properties: + add: + additionalProperties: + type: string + type: object + remove: + items: + type: string + type: array + set: + additionalProperties: + type: string + type: object + type: object + response: + properties: + add: + additionalProperties: + type: string + type: object + remove: + items: + type: string + type: array + set: + additionalProperties: + type: string + type: object + type: object + type: object + match: + items: + properties: + authority: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + type: string + prefix: + type: string + regex: + description: >- + RE2 style regex-based match + (https://github.com/google/re2/wiki/Syntax). + type: string + type: object + gateways: + description: >- + Names of gateways where the rule should be + applied. + items: + type: string + type: array + headers: + additionalProperties: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + type: string + prefix: + type: string + regex: + description: >- + RE2 style regex-based match + (https://github.com/google/re2/wiki/Syntax). + type: string + type: object + type: object + ignoreUriCase: + description: >- + Flag to specify whether the URI matching should + be case-insensitive. + type: boolean + method: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + type: string + prefix: + type: string + regex: + description: >- + RE2 style regex-based match + (https://github.com/google/re2/wiki/Syntax). + type: string + type: object + name: + description: The name assigned to a match. + type: string + port: + description: >- + Specifies the ports on the host that is being + addressed. + type: integer + queryParams: + additionalProperties: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + type: string + prefix: + type: string + regex: + description: >- + RE2 style regex-based match + (https://github.com/google/re2/wiki/Syntax). + type: string + type: object + description: Query parameters for matching. + type: object + scheme: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + type: string + prefix: + type: string + regex: + description: >- + RE2 style regex-based match + (https://github.com/google/re2/wiki/Syntax). + type: string + type: object + sourceLabels: + additionalProperties: + type: string + type: object + sourceNamespace: + description: >- + Source namespace constraining the applicability + of a rule to workloads in that namespace. + type: string + uri: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + type: string + prefix: + type: string + regex: + description: >- + RE2 style regex-based match + (https://github.com/google/re2/wiki/Syntax). + type: string + type: object + withoutHeaders: + additionalProperties: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + type: string + prefix: + type: string + regex: + description: >- + RE2 style regex-based match + (https://github.com/google/re2/wiki/Syntax). + type: string + type: object + description: >- + withoutHeader has the same syntax with the + header, but has opposite meaning. + type: object + type: object + type: array + mirror: + properties: + host: + description: The name of a service from the service registry. + type: string + port: + description: >- + Specifies the port on the host that is being + addressed. + properties: + number: + type: integer + type: object + subset: + description: The name of a subset within the service. + type: string + type: object + mirrorPercent: + description: >- + Percentage of the traffic to be mirrored by the + `mirror` field. + nullable: true + type: integer + mirrorPercentage: + description: >- + Percentage of the traffic to be mirrored by the + `mirror` field. + properties: + value: + format: double + type: number + type: object + mirror_percent: + description: >- + Percentage of the traffic to be mirrored by the + `mirror` field. + nullable: true + type: integer + name: + description: The name assigned to the route for debugging purposes. + type: string + redirect: + description: >- + A HTTP rule can either redirect or forward (default) + traffic. + oneOf: + - not: + anyOf: + - required: + - port + - required: + - derivePort + - required: + - port + - required: + - derivePort + properties: + authority: + type: string + derivePort: + enum: + - FROM_PROTOCOL_DEFAULT + - FROM_REQUEST_PORT + type: string + port: + description: >- + On a redirect, overwrite the port portion of the + URL with this value. + type: integer + redirectCode: + type: integer + scheme: + description: >- + On a redirect, overwrite the scheme portion of the + URL with this value. + type: string + uri: + type: string + type: object + retries: + description: Retry policy for HTTP requests. + properties: + attempts: + description: >- + Number of retries to be allowed for a given + request. + format: int32 + type: integer + perTryTimeout: + description: >- + Timeout per attempt for a given request, including + the initial call and any retries. + type: string + retryOn: + description: >- + Specifies the conditions under which retry takes + place. + type: string + retryRemoteLocalities: + description: >- + Flag to specify whether the retries should retry + to other localities. + nullable: true + type: boolean + type: object + rewrite: + description: Rewrite HTTP URIs and Authority headers. + properties: + authority: + description: rewrite the Authority/Host header with this value. + type: string + uri: + type: string + type: object + route: + description: >- + A HTTP rule can either redirect or forward (default) + traffic. + items: + properties: + destination: + properties: + host: + description: >- + The name of a service from the service + registry. + type: string + port: + description: >- + Specifies the port on the host that is being + addressed. + properties: + number: + type: integer + type: object + subset: + description: The name of a subset within the service. + type: string + type: object + headers: + properties: + request: + properties: + add: + additionalProperties: + type: string + type: object + remove: + items: + type: string + type: array + set: + additionalProperties: + type: string + type: object + type: object + response: + properties: + add: + additionalProperties: + type: string + type: object + remove: + items: + type: string + type: array + set: + additionalProperties: + type: string + type: object + type: object + type: object + weight: + format: int32 + type: integer + type: object + type: array + timeout: + description: 'Timeout for HTTP requests, default is disabled.' + type: string + type: object + type: array + tcp: + description: An ordered list of route rules for opaque TCP traffic. + items: + properties: + match: + items: + properties: + destinationSubnets: + description: >- + IPv4 or IPv6 ip addresses of destination with + optional subnet. + items: + type: string + type: array + gateways: + description: >- + Names of gateways where the rule should be + applied. + items: + type: string + type: array + port: + description: >- + Specifies the port on the host that is being + addressed. + type: integer + sourceLabels: + additionalProperties: + type: string + type: object + sourceNamespace: + description: >- + Source namespace constraining the applicability + of a rule to workloads in that namespace. + type: string + sourceSubnet: + description: >- + IPv4 or IPv6 ip address of source with optional + subnet. + type: string + type: object + type: array + route: + description: >- + The destination to which the connection should be + forwarded to. + items: + properties: + destination: + properties: + host: + description: >- + The name of a service from the service + registry. + type: string + port: + description: >- + Specifies the port on the host that is being + addressed. + properties: + number: + type: integer + type: object + subset: + description: The name of a subset within the service. + type: string + type: object + weight: + format: int32 + type: integer + type: object + type: array + type: object + type: array + tls: + items: + properties: + match: + items: + properties: + destinationSubnets: + description: >- + IPv4 or IPv6 ip addresses of destination with + optional subnet. + items: + type: string + type: array + gateways: + description: >- + Names of gateways where the rule should be + applied. + items: + type: string + type: array + port: + description: >- + Specifies the port on the host that is being + addressed. + type: integer + sniHosts: + description: SNI (server name indicator) to match on. + items: + type: string + type: array + sourceLabels: + additionalProperties: + type: string + type: object + sourceNamespace: + description: >- + Source namespace constraining the applicability + of a rule to workloads in that namespace. + type: string + type: object + type: array + route: + description: >- + The destination to which the connection should be + forwarded to. + items: + properties: + destination: + properties: + host: + description: >- + The name of a service from the service + registry. + type: string + port: + description: >- + Specifies the port on the host that is being + addressed. + properties: + number: + type: integer + type: object + subset: + description: The name of a subset within the service. + type: string + type: object + weight: + format: int32 + type: integer + type: object + type: array + type: object + type: array + type: object + status: + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - description: The names of gateways and sidecars that should apply these routes + jsonPath: .spec.gateways + name: Gateways + type: string + - description: The destination hosts to which traffic is being sent + jsonPath: .spec.hosts + name: Hosts + type: string + - description: >- + CreationTimestamp is a timestamp representing the server time when + this object was created. It is not guaranteed to be set in + happens-before order across separate operations. Clients may not set + this value. It is represented in RFC3339 form and is in UTC. + Populated by the system. Read-only. Null for lists. More info: + https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + properties: + spec: + description: >- + Configuration affecting label/content routing, sni routing, etc. + See more details at: + https://istio.io/docs/reference/config/networking/virtual-service.html + properties: + exportTo: + description: >- + A list of namespaces to which this virtual service is + exported. + items: + type: string + type: array + gateways: + description: >- + The names of gateways and sidecars that should apply these + routes. + items: + type: string + type: array + hosts: + description: The destination hosts to which traffic is being sent. + items: + type: string + type: array + http: + description: An ordered list of route rules for HTTP traffic. + items: + properties: + corsPolicy: + description: Cross-Origin Resource Sharing policy (CORS). + properties: + allowCredentials: + nullable: true + type: boolean + allowHeaders: + items: + type: string + type: array + allowMethods: + description: >- + List of HTTP methods allowed to access the + resource. + items: + type: string + type: array + allowOrigin: + description: >- + The list of origins that are allowed to perform + CORS requests. + items: + type: string + type: array + allowOrigins: + description: String patterns that match allowed origins. + items: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + type: string + prefix: + type: string + regex: + description: >- + RE2 style regex-based match + (https://github.com/google/re2/wiki/Syntax). + type: string + type: object + type: array + exposeHeaders: + items: + type: string + type: array + maxAge: + type: string + type: object + delegate: + properties: + name: + description: >- + Name specifies the name of the delegate + VirtualService. + type: string + namespace: + description: >- + Namespace specifies the namespace where the + delegate VirtualService resides. + type: string + type: object + fault: + description: >- + Fault injection policy to apply on HTTP traffic at the + client side. + properties: + abort: + oneOf: + - not: + anyOf: + - required: + - httpStatus + - required: + - grpcStatus + - required: + - http2Error + - required: + - httpStatus + - required: + - grpcStatus + - required: + - http2Error + properties: + grpcStatus: + type: string + http2Error: + type: string + httpStatus: + description: >- + HTTP status code to use to abort the Http + request. + format: int32 + type: integer + percentage: + description: >- + Percentage of requests to be aborted with the + error code provided. + properties: + value: + format: double + type: number + type: object + type: object + delay: + oneOf: + - not: + anyOf: + - required: + - fixedDelay + - required: + - exponentialDelay + - required: + - fixedDelay + - required: + - exponentialDelay + properties: + exponentialDelay: + type: string + fixedDelay: + description: >- + Add a fixed delay before forwarding the + request. + type: string + percent: + description: >- + Percentage of requests on which the delay will + be injected (0-100). + format: int32 + type: integer + percentage: + description: >- + Percentage of requests on which the delay will + be injected. + properties: + value: + format: double + type: number + type: object + type: object + type: object + headers: + properties: + request: + properties: + add: + additionalProperties: + type: string + type: object + remove: + items: + type: string + type: array + set: + additionalProperties: + type: string + type: object + type: object + response: + properties: + add: + additionalProperties: + type: string + type: object + remove: + items: + type: string + type: array + set: + additionalProperties: + type: string + type: object + type: object + type: object + match: + items: + properties: + authority: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + type: string + prefix: + type: string + regex: + description: >- + RE2 style regex-based match + (https://github.com/google/re2/wiki/Syntax). + type: string + type: object + gateways: + description: >- + Names of gateways where the rule should be + applied. + items: + type: string + type: array + headers: + additionalProperties: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + type: string + prefix: + type: string + regex: + description: >- + RE2 style regex-based match + (https://github.com/google/re2/wiki/Syntax). + type: string + type: object + type: object + ignoreUriCase: + description: >- + Flag to specify whether the URI matching should + be case-insensitive. + type: boolean + method: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + type: string + prefix: + type: string + regex: + description: >- + RE2 style regex-based match + (https://github.com/google/re2/wiki/Syntax). + type: string + type: object + name: + description: The name assigned to a match. + type: string + port: + description: >- + Specifies the ports on the host that is being + addressed. + type: integer + queryParams: + additionalProperties: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + type: string + prefix: + type: string + regex: + description: >- + RE2 style regex-based match + (https://github.com/google/re2/wiki/Syntax). + type: string + type: object + description: Query parameters for matching. + type: object + scheme: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + type: string + prefix: + type: string + regex: + description: >- + RE2 style regex-based match + (https://github.com/google/re2/wiki/Syntax). + type: string + type: object + sourceLabels: + additionalProperties: + type: string + type: object + sourceNamespace: + description: >- + Source namespace constraining the applicability + of a rule to workloads in that namespace. + type: string + uri: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + type: string + prefix: + type: string + regex: + description: >- + RE2 style regex-based match + (https://github.com/google/re2/wiki/Syntax). + type: string + type: object + withoutHeaders: + additionalProperties: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + type: string + prefix: + type: string + regex: + description: >- + RE2 style regex-based match + (https://github.com/google/re2/wiki/Syntax). + type: string + type: object + description: >- + withoutHeader has the same syntax with the + header, but has opposite meaning. + type: object + type: object + type: array + mirror: + properties: + host: + description: The name of a service from the service registry. + type: string + port: + description: >- + Specifies the port on the host that is being + addressed. + properties: + number: + type: integer + type: object + subset: + description: The name of a subset within the service. + type: string + type: object + mirrorPercent: + description: >- + Percentage of the traffic to be mirrored by the + `mirror` field. + nullable: true + type: integer + mirrorPercentage: + description: >- + Percentage of the traffic to be mirrored by the + `mirror` field. + properties: + value: + format: double + type: number + type: object + mirror_percent: + description: >- + Percentage of the traffic to be mirrored by the + `mirror` field. + nullable: true + type: integer + name: + description: The name assigned to the route for debugging purposes. + type: string + redirect: + description: >- + A HTTP rule can either redirect or forward (default) + traffic. + oneOf: + - not: + anyOf: + - required: + - port + - required: + - derivePort + - required: + - port + - required: + - derivePort + properties: + authority: + type: string + derivePort: + enum: + - FROM_PROTOCOL_DEFAULT + - FROM_REQUEST_PORT + type: string + port: + description: >- + On a redirect, overwrite the port portion of the + URL with this value. + type: integer + redirectCode: + type: integer + scheme: + description: >- + On a redirect, overwrite the scheme portion of the + URL with this value. + type: string + uri: + type: string + type: object + retries: + description: Retry policy for HTTP requests. + properties: + attempts: + description: >- + Number of retries to be allowed for a given + request. + format: int32 + type: integer + perTryTimeout: + description: >- + Timeout per attempt for a given request, including + the initial call and any retries. + type: string + retryOn: + description: >- + Specifies the conditions under which retry takes + place. + type: string + retryRemoteLocalities: + description: >- + Flag to specify whether the retries should retry + to other localities. + nullable: true + type: boolean + type: object + rewrite: + description: Rewrite HTTP URIs and Authority headers. + properties: + authority: + description: rewrite the Authority/Host header with this value. + type: string + uri: + type: string + type: object + route: + description: >- + A HTTP rule can either redirect or forward (default) + traffic. + items: + properties: + destination: + properties: + host: + description: >- + The name of a service from the service + registry. + type: string + port: + description: >- + Specifies the port on the host that is being + addressed. + properties: + number: + type: integer + type: object + subset: + description: The name of a subset within the service. + type: string + type: object + headers: + properties: + request: + properties: + add: + additionalProperties: + type: string + type: object + remove: + items: + type: string + type: array + set: + additionalProperties: + type: string + type: object + type: object + response: + properties: + add: + additionalProperties: + type: string + type: object + remove: + items: + type: string + type: array + set: + additionalProperties: + type: string + type: object + type: object + type: object + weight: + format: int32 + type: integer + type: object + type: array + timeout: + description: 'Timeout for HTTP requests, default is disabled.' + type: string + type: object + type: array + tcp: + description: An ordered list of route rules for opaque TCP traffic. + items: + properties: + match: + items: + properties: + destinationSubnets: + description: >- + IPv4 or IPv6 ip addresses of destination with + optional subnet. + items: + type: string + type: array + gateways: + description: >- + Names of gateways where the rule should be + applied. + items: + type: string + type: array + port: + description: >- + Specifies the port on the host that is being + addressed. + type: integer + sourceLabels: + additionalProperties: + type: string + type: object + sourceNamespace: + description: >- + Source namespace constraining the applicability + of a rule to workloads in that namespace. + type: string + sourceSubnet: + description: >- + IPv4 or IPv6 ip address of source with optional + subnet. + type: string + type: object + type: array + route: + description: >- + The destination to which the connection should be + forwarded to. + items: + properties: + destination: + properties: + host: + description: >- + The name of a service from the service + registry. + type: string + port: + description: >- + Specifies the port on the host that is being + addressed. + properties: + number: + type: integer + type: object + subset: + description: The name of a subset within the service. + type: string + type: object + weight: + format: int32 + type: integer + type: object + type: array + type: object + type: array + tls: + items: + properties: + match: + items: + properties: + destinationSubnets: + description: >- + IPv4 or IPv6 ip addresses of destination with + optional subnet. + items: + type: string + type: array + gateways: + description: >- + Names of gateways where the rule should be + applied. + items: + type: string + type: array + port: + description: >- + Specifies the port on the host that is being + addressed. + type: integer + sniHosts: + description: SNI (server name indicator) to match on. + items: + type: string + type: array + sourceLabels: + additionalProperties: + type: string + type: object + sourceNamespace: + description: >- + Source namespace constraining the applicability + of a rule to workloads in that namespace. + type: string + type: object + type: array + route: + description: >- + The destination to which the connection should be + forwarded to. + items: + properties: + destination: + properties: + host: + description: >- + The name of a service from the service + registry. + type: string + port: + description: >- + Specifies the port on the host that is being + addressed. + properties: + number: + type: integer + type: object + subset: + description: The name of a subset within the service. + type: string + type: object + weight: + format: int32 + type: integer + type: object + type: array + type: object + type: array + type: object + status: + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} diff --git a/e2e-testing/env b/e2e-testing/env new file mode 100644 index 0000000..75f68a3 --- /dev/null +++ b/e2e-testing/env @@ -0,0 +1 @@ +KIND_CUSTOM_IMAGE=kindest/node:v1.26.3 diff --git a/e2e-testing/kuttl/accidental-resources-deletion/00-assert.yaml b/e2e-testing/kuttl/accidental-resources-deletion/00-assert.yaml new file mode 100644 index 0000000..03f9a9b --- /dev/null +++ b/e2e-testing/kuttl/accidental-resources-deletion/00-assert.yaml @@ -0,0 +1,45 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: details + version: default-dynamicenv-accidental-resources-deletion + name: details-default-dynamicenv-accidental-resources-deletion + namespace: accidental-resources-deletion +status: + replicas: 1 +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details-default-dynamicenv-accidental-resources-deletion-details + namespace: accidental-resources-deletion +spec: + host: details + subsets: + - name: default-dynamicenv-accidental-resources-deletion + labels: + version: default-dynamicenv-accidental-resources-deletion +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: reviews + version: default-dynamicenv-accidental-resources-deletion + name: reviews-default-dynamicenv-accidental-resources-deletion + namespace: accidental-resources-deletion +status: + replicas: 1 +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: reviews-default-dynamicenv-accidental-resources-deletion-reviews + namespace: accidental-resources-deletion +spec: + host: reviews + subsets: + - name: default-dynamicenv-accidental-resources-deletion + labels: + version: default-dynamicenv-accidental-resources-deletion \ No newline at end of file diff --git a/e2e-testing/kuttl/accidental-resources-deletion/00-full-bookinfo-details.yaml b/e2e-testing/kuttl/accidental-resources-deletion/00-full-bookinfo-details.yaml new file mode 100644 index 0000000..f9a3edd --- /dev/null +++ b/e2e-testing/kuttl/accidental-resources-deletion/00-full-bookinfo-details.yaml @@ -0,0 +1,376 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: accidental-resources-deletion +--- +apiVersion: v1 +kind: Service +metadata: + name: details + namespace: accidental-resources-deletion + labels: + app: details + service: details +spec: + ports: + - port: 9080 + name: http + selector: + app: details +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-details + namespace: accidental-resources-deletion + labels: + account: details +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details + namespace: accidental-resources-deletion + labels: + app: details + version: shared +spec: + replicas: 1 + selector: + matchLabels: + app: details + version: shared + template: + metadata: + labels: + app: details + version: shared + spec: + serviceAccountName: bookinfo-details + containers: + - name: details + image: docker.io/istio/examples-bookinfo-details-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + securityContext: + runAsUser: 1000 +--- +################################################################################################## +# Ratings service +################################################################################################## +apiVersion: v1 +kind: Service +metadata: + name: ratings + namespace: accidental-resources-deletion + labels: + app: ratings + service: ratings +spec: + ports: + - port: 9080 + name: http + selector: + app: ratings +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-ratings + namespace: accidental-resources-deletion + labels: + account: ratings +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ratings + namespace: accidental-resources-deletion + labels: + app: ratings + version: shared +spec: + replicas: 1 + selector: + matchLabels: + app: ratings + version: shared + template: + metadata: + labels: + app: ratings + version: shared + spec: + serviceAccountName: bookinfo-ratings + containers: + - name: ratings + image: docker.io/istio/examples-bookinfo-ratings-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + securityContext: + runAsUser: 1000 +--- +################################################################################################## +# Reviews service +################################################################################################## +apiVersion: v1 +kind: Service +metadata: + name: reviews + namespace: accidental-resources-deletion + labels: + app: reviews + service: reviews +spec: + ports: + - port: 9080 + name: http + selector: + app: reviews +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-reviews + namespace: accidental-resources-deletion + labels: + account: reviews +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: reviews + namespace: accidental-resources-deletion + labels: + app: reviews + version: shared +spec: + replicas: 1 + selector: + matchLabels: + app: reviews + version: shared + template: + metadata: + labels: + app: reviews + version: shared + spec: + serviceAccountName: bookinfo-reviews + containers: + - name: reviews + image: docker.io/istio/examples-bookinfo-reviews-v2:1.16.2 + imagePullPolicy: IfNotPresent + env: + - name: LOG_DIR + value: "/tmp/logs" + ports: + - containerPort: 9080 + volumeMounts: + - name: tmp + mountPath: /tmp + - name: wlp-output + mountPath: /opt/ibm/wlp/output + securityContext: + runAsUser: 1000 + volumes: + - name: wlp-output + emptyDir: {} + - name: tmp + emptyDir: {} +--- +################################################################################################## +# Productpage services +################################################################################################## +apiVersion: v1 +kind: Service +metadata: + name: productpage + namespace: accidental-resources-deletion + labels: + app: productpage + service: productpage +spec: + ports: + - port: 9080 + name: http + selector: + app: productpage +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-productpage + namespace: accidental-resources-deletion + labels: + account: productpage +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: productpage + namespace: accidental-resources-deletion + labels: + app: productpage + version: shared +spec: + replicas: 1 + selector: + matchLabels: + app: productpage + version: shared + template: + metadata: + labels: + app: productpage + version: shared + spec: + serviceAccountName: bookinfo-productpage + containers: + - name: productpage + image: docker.io/istio/examples-bookinfo-productpage-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + volumeMounts: + - name: tmp + mountPath: /tmp + securityContext: + runAsUser: 1000 + volumes: + - name: tmp + emptyDir: {} +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: productpage + namespace: accidental-resources-deletion +spec: + host: productpage + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: reviews + namespace: accidental-resources-deletion +spec: + host: reviews + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: ratings + namespace: accidental-resources-deletion +spec: + host: ratings + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details + namespace: accidental-resources-deletion +spec: + host: details + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: productpage + namespace: accidental-resources-deletion +spec: + hosts: + - productpage + http: + - route: + - destination: + host: productpage + subset: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: reviews + namespace: accidental-resources-deletion +spec: + hosts: + - reviews + http: + - route: + - destination: + host: reviews + subset: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: ratings + namespace: accidental-resources-deletion +spec: + hosts: + - ratings + http: + - route: + - destination: + host: ratings + subset: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: details + namespace: accidental-resources-deletion +spec: + hosts: + - details + http: + - route: + - destination: + host: details + subset: shared +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-accidental-resources-deletion +spec: + istioMatches: + - headers: + end-user: + prefix: jason + subsets: + - name: "details" + namespace: "accidental-resources-deletion" + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test + - name: "reviews" + namespace: "accidental-resources-deletion" + containers: + - containerName: reviews + command: ["/opt/ibm/wlp/bin/server", "run", "defaultServer"] + image: docker.io/istio/examples-bookinfo-reviews-v3:1.16.2 + env: + - name: TOPIC_NAME + value: test diff --git a/e2e-testing/kuttl/accidental-resources-deletion/01-delete-deployment-and-destination-rule.yaml b/e2e-testing/kuttl/accidental-resources-deletion/01-delete-deployment-and-destination-rule.yaml new file mode 100644 index 0000000..a49beab --- /dev/null +++ b/e2e-testing/kuttl/accidental-resources-deletion/01-delete-deployment-and-destination-rule.yaml @@ -0,0 +1,11 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: apps/v1 + kind: Deployment + name: details-default-accidental-resources-deletion + namespace: accidental-resources-deletion + - apiVersion: networking.istio.io/v1alpha3 + kind: DestinationRule + name: reviews + namespace: accidental-resources-deletion \ No newline at end of file diff --git a/e2e-testing/kuttl/accidental-resources-deletion/01-errors.yaml b/e2e-testing/kuttl/accidental-resources-deletion/01-errors.yaml new file mode 100644 index 0000000..7995e50 --- /dev/null +++ b/e2e-testing/kuttl/accidental-resources-deletion/01-errors.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details-default-accidental-resources-deletion +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: reviews + namespace: accidental-resources-deletion \ No newline at end of file diff --git a/e2e-testing/kuttl/accidental-resources-deletion/02-delete-dynamic-env.yaml b/e2e-testing/kuttl/accidental-resources-deletion/02-delete-dynamic-env.yaml new file mode 100644 index 0000000..06db2ce --- /dev/null +++ b/e2e-testing/kuttl/accidental-resources-deletion/02-delete-dynamic-env.yaml @@ -0,0 +1,6 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: riskified.com/v1alpha1 + kind: DynamicEnv + name: dynamicenv-accidental-resources-deletion diff --git a/e2e-testing/kuttl/accidental-resources-deletion/02-errors.yaml b/e2e-testing/kuttl/accidental-resources-deletion/02-errors.yaml new file mode 100644 index 0000000..c5fc2cb --- /dev/null +++ b/e2e-testing/kuttl/accidental-resources-deletion/02-errors.yaml @@ -0,0 +1,29 @@ +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-accidental-resources-deletion +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details-default-accidental-resources-deletion + namespace: accidental-resources-deletion +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details-default-accidental-resources-deletion + namespace: accidental-resources-deletion +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: reviews-default-accidental-resources-deletion + namespace: accidental-resources-deletion +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: reviews-default-accidental-resources-deletion + namespace: accidental-resources-deletion diff --git a/e2e-testing/kuttl/accidental-resources-deletion/Readme.md b/e2e-testing/kuttl/accidental-resources-deletion/Readme.md new file mode 100644 index 0000000..b608867 --- /dev/null +++ b/e2e-testing/kuttl/accidental-resources-deletion/Readme.md @@ -0,0 +1,4 @@ +## DEV-46153 Validate accidental resource deletion + +This test verifies that accidental deletion of *Deployment* and *DestinationRule* will not affect deletion of the *DynamicEnv*. + diff --git a/e2e-testing/kuttl/alternative-default-version/00-assert.yaml b/e2e-testing/kuttl/alternative-default-version/00-assert.yaml new file mode 100644 index 0000000..6f617d3 --- /dev/null +++ b/e2e-testing/kuttl/alternative-default-version/00-assert.yaml @@ -0,0 +1,60 @@ +--- +apiVersion: networking.istio.io/v1beta1 +kind: DestinationRule +metadata: + name: reviews-default-dynamicenv-alternative-default-version-reviews + namespace: alternative-default-version +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: details + namespace: alternative-default-version +spec: + hosts: + - details + http: + - match: + - headers: + end-user: + prefix: jason +# name: dynamic-environment-default-dynamicenv-alternative-default-version-details-763ad37a61 + route: + - destination: + host: details + subset: default-dynamicenv-alternative-default-version + headers: + response: + add: + x-dynamic-env: details-default-dynamicenv-alternative-default-version + - route: + - destination: + host: details + subset: shared +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: reviews + namespace: alternative-default-version +spec: + hosts: + - reviews + http: + - match: + - headers: + end-user: + prefix: jason +# name: dynamic-environment-default-dynamicenv-alternative-default-version-reviews-de08d07349 + route: + - destination: + host: reviews + subset: default-dynamicenv-alternative-default-version + headers: + response: + add: + x-dynamic-env: reviews-default-dynamicenv-alternative-default-version + - route: + - destination: + host: reviews + subset: v1 diff --git a/e2e-testing/kuttl/alternative-default-version/00-base.yaml b/e2e-testing/kuttl/alternative-default-version/00-base.yaml new file mode 100644 index 0000000..c8dee8b --- /dev/null +++ b/e2e-testing/kuttl/alternative-default-version/00-base.yaml @@ -0,0 +1,378 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: alternative-default-version +--- +apiVersion: v1 +kind: Service +metadata: + name: details + namespace: alternative-default-version + labels: + app: details + service: details +spec: + ports: + - port: 9080 + name: http + selector: + app: details +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-details + namespace: alternative-default-version + labels: + account: details +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details + namespace: alternative-default-version + labels: + app: details + version: shared +spec: + replicas: 1 + selector: + matchLabels: + app: details + version: shared + template: + metadata: + labels: + app: details + version: shared + spec: + serviceAccountName: bookinfo-details + containers: + - name: details + image: docker.io/istio/examples-bookinfo-details-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + securityContext: + runAsUser: 1000 +--- +################################################################################################## +# Ratings service +################################################################################################## +apiVersion: v1 +kind: Service +metadata: + name: ratings + namespace: alternative-default-version + labels: + app: ratings + service: ratings +spec: + ports: + - port: 9080 + name: http + selector: + app: ratings +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-ratings + namespace: alternative-default-version + labels: + account: ratings +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ratings + namespace: alternative-default-version + labels: + app: ratings + version: shared +spec: + replicas: 1 + selector: + matchLabels: + app: ratings + version: shared + template: + metadata: + labels: + app: ratings + version: shared + spec: + serviceAccountName: bookinfo-ratings + containers: + - name: ratings + image: docker.io/istio/examples-bookinfo-ratings-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + securityContext: + runAsUser: 1000 +--- +################################################################################################## +# Reviews service +################################################################################################## +apiVersion: v1 +kind: Service +metadata: + name: reviews + namespace: alternative-default-version + labels: + app: reviews + service: reviews +spec: + ports: + - port: 9080 + name: http + selector: + app: reviews +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-reviews + namespace: alternative-default-version + labels: + account: reviews +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: reviews + namespace: alternative-default-version + labels: + app: reviews + version: v1 +spec: + replicas: 1 + selector: + matchLabels: + app: reviews + version: v1 + template: + metadata: + labels: + app: reviews + version: v1 + spec: + serviceAccountName: bookinfo-reviews + containers: + - name: reviews + image: docker.io/istio/examples-bookinfo-reviews-v2:1.16.2 + imagePullPolicy: IfNotPresent + env: + - name: LOG_DIR + value: "/tmp/logs" + ports: + - containerPort: 9080 + volumeMounts: + - name: tmp + mountPath: /tmp + - name: wlp-output + mountPath: /opt/ibm/wlp/output + securityContext: + runAsUser: 1000 + volumes: + - name: wlp-output + emptyDir: {} + - name: tmp + emptyDir: {} +--- +################################################################################################## +# Productpage services +################################################################################################## +apiVersion: v1 +kind: Service +metadata: + name: productpage + namespace: alternative-default-version + labels: + app: productpage + service: productpage +spec: + ports: + - port: 9080 + name: http + selector: + app: productpage +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-productpage + namespace: alternative-default-version + labels: + account: productpage +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: productpage + namespace: alternative-default-version + labels: + app: productpage + version: shared +spec: + replicas: 1 + selector: + matchLabels: + app: productpage + version: shared + template: + metadata: + labels: + app: productpage + version: shared + spec: + serviceAccountName: bookinfo-productpage + containers: + - name: productpage + image: docker.io/istio/examples-bookinfo-productpage-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + volumeMounts: + - name: tmp + mountPath: /tmp + securityContext: + runAsUser: 1000 + volumes: + - name: tmp + emptyDir: {} +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: productpage + namespace: alternative-default-version +spec: + host: productpage + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: reviews + namespace: alternative-default-version +spec: + host: reviews + subsets: + - name: shared + labels: + version: v1 +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: ratings + namespace: alternative-default-version +spec: + host: ratings + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details + namespace: alternative-default-version +spec: + host: details + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: productpage + namespace: alternative-default-version +spec: + hosts: + - productpage + http: + - route: + - destination: + host: productpage + subset: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: reviews + namespace: alternative-default-version +spec: + hosts: + - reviews + http: + - route: + - destination: + host: reviews + subset: v1 +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: ratings + namespace: alternative-default-version +spec: + hosts: + - ratings + http: + - route: + - destination: + host: ratings + subset: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: details + namespace: alternative-default-version +spec: + hosts: + - details + http: + - route: + - destination: + host: details + subset: shared +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-alternative-default-version +spec: + istioMatches: + - headers: + end-user: + prefix: jason + subsets: + - name: details + namespace: alternative-default-version + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test + - name: reviews + namespace: alternative-default-version + defaultVersion: v1 + containers: + - containerName: reviews + command: ["/opt/ibm/wlp/bin/server", "run", "defaultServer"] + image: docker.io/istio/examples-bookinfo-reviews-v3:1.16.2 + env: + - name: TOPIC_NAME + value: test diff --git a/e2e-testing/kuttl/alternative-default-version/Readme.md b/e2e-testing/kuttl/alternative-default-version/Readme.md new file mode 100644 index 0000000..cf13ff6 --- /dev/null +++ b/e2e-testing/kuttl/alternative-default-version/Readme.md @@ -0,0 +1,12 @@ +## Support Custom Default Version per Subset + +This test is for validating part of the effort to support custom version labels +and default values. We do not test for global custom version label (this is only +global) or global value as it will affect the entire end-to-end testing. We only +test the option to add specific custom default value per subset. + +For validation we: + +* Validate that a _DestinationRule_ was created for the subset with custom + default version (_reviews_). +* Validate the rules of the _VirtualService_ for set service. diff --git a/e2e-testing/kuttl/consumers-with-and-without-errors/00-assert.yaml b/e2e-testing/kuttl/consumers-with-and-without-errors/00-assert.yaml new file mode 100644 index 0000000..4038fc4 --- /dev/null +++ b/e2e-testing/kuttl/consumers-with-and-without-errors/00-assert.yaml @@ -0,0 +1,20 @@ +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-consumers-with-and-without-errors + namespace: default +status: + state: degraded + totalCount: 3 + totalReady: 2 + consumersStatus: + details-worker-default-dynamicenv-consumers-with-and-without-er: + name: details-worker-default-dynamicenv-consumers-with-and-without-er + namespace: consumers-with-and-without-errors + status: running + hash: -7893432429265961114 + details-worker-invalid-default-dynamicenv-consumers-with-and-without-er: + errors: + - error: 'couldn''t find the deployment we need to override (name: details-worker-invalid, + ns: consumers-with-and-without-errors), Deployment.apps "details-worker-invalid" + not found' diff --git a/e2e-testing/kuttl/consumers-with-and-without-errors/00-bookinfo-details.yaml b/e2e-testing/kuttl/consumers-with-and-without-errors/00-bookinfo-details.yaml new file mode 100644 index 0000000..97b70f9 --- /dev/null +++ b/e2e-testing/kuttl/consumers-with-and-without-errors/00-bookinfo-details.yaml @@ -0,0 +1,148 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: consumers-with-and-without-errors + labels: + istio-injection: enabled +--- +apiVersion: v1 +kind: Service +metadata: + name: details + namespace: consumers-with-and-without-errors + labels: + app: details + service: details +spec: + ports: + - port: 9080 + name: http + selector: + app: details +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-details + namespace: consumers-with-and-without-errors + labels: + account: details +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details + namespace: consumers-with-and-without-errors + labels: + app: details + version: shared +spec: + selector: + matchLabels: + app: details + version: shared + template: + metadata: + labels: + app: details + version: shared + spec: + serviceAccountName: bookinfo-details + containers: + - name: details + image: docker.io/istio/examples-bookinfo-details-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + securityContext: + runAsUser: 1000 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details-worker + namespace: consumers-with-and-without-errors + labels: + app: details + version: shared +spec: + selector: + matchLabels: + app: details-worker + version: shared + template: + metadata: + labels: + app: details-worker + version: shared + spec: + serviceAccountName: bookinfo-details + containers: + - name: details + image: docker.io/istio/examples-bookinfo-details-v1:1.16.2 + imagePullPolicy: IfNotPresent + securityContext: + runAsUser: 1000 +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details + namespace: consumers-with-and-without-errors +spec: + host: details + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: details + namespace: consumers-with-and-without-errors +spec: + hosts: + - details + http: + - route: + - destination: + host: details + subset: shared +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-consumers-with-and-without-errors +spec: + istioMatches: + - headers: + end-user: + prefix: jason + subsets: + - name: details + namespace: consumers-with-and-without-errors + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test + consumers: + - name: details-worker + namespace: consumers-with-and-without-errors + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test-worker + - name: details-worker-invalid + namespace: consumers-with-and-without-errors + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test-worker diff --git a/e2e-testing/kuttl/consumers-with-and-without-errors/01-delete-dynamic-environment.yaml b/e2e-testing/kuttl/consumers-with-and-without-errors/01-delete-dynamic-environment.yaml new file mode 100644 index 0000000..acb0cdd --- /dev/null +++ b/e2e-testing/kuttl/consumers-with-and-without-errors/01-delete-dynamic-environment.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: riskified.com/v1alpha1 + kind: DynamicEnv + name: dynamicenv-consumers-with-and-without-errors + namespace: default diff --git a/e2e-testing/kuttl/consumers-with-and-without-errors/01-errors.yaml b/e2e-testing/kuttl/consumers-with-and-without-errors/01-errors.yaml new file mode 100644 index 0000000..bbb5202 --- /dev/null +++ b/e2e-testing/kuttl/consumers-with-and-without-errors/01-errors.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details-worker-default-dynamicenv-consumers-with-and-without-er + namespace: consumers-with-and-without-errors +... \ No newline at end of file diff --git a/e2e-testing/kuttl/consumers-with-and-without-errors/Readme.md b/e2e-testing/kuttl/consumers-with-and-without-errors/Readme.md new file mode 100644 index 0000000..7a06f0a --- /dev/null +++ b/e2e-testing/kuttl/consumers-with-and-without-errors/Readme.md @@ -0,0 +1,7 @@ +## Consumer Status (with and without errors) + +This test verifies that consumer has its own section under _Status_, and it's not a part of +_Subsets_. This should apply both to regular status and error messages. We also verify the existence +of hash in consumers. + +Also, it verifies that consumers are also deleted when the dynamic-environment is deleted. \ No newline at end of file diff --git a/e2e-testing/kuttl/delegate-virtual-service/00-assert.yaml b/e2e-testing/kuttl/delegate-virtual-service/00-assert.yaml new file mode 100644 index 0000000..4cf9370 --- /dev/null +++ b/e2e-testing/kuttl/delegate-virtual-service/00-assert.yaml @@ -0,0 +1,140 @@ +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: test-app-vs + namespace: delegate-virtual-service-2 +spec: + hosts: + - test-app-service.delegate-virtual-service.svc.cluster.local + http: + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - mesh + headers: + end-user: + prefix: jason +# name: dynamic-environment-default-dynamicenv-delegate-virtual-service-test-app-eab8f3914d + route: + - destination: + host: test-app-service.delegate-virtual-service.svc.cluster.local + port: + number: 80 + subset: default-dynamicenv-delegate-virtual-service + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - mesh + name: in-cluster-default + route: + - destination: + host: test-app-service.delegate-virtual-service.svc.cluster.local + port: + number: 80 + subset: shared + weight: 100 + - delegate: + name: test-app-vs-delegated + namespace: delegate-virtual-service + match: + - uri: + prefix: /nisan + - delegate: + name: does-not-exist-delegate + namespace: delegate-virtual-service + match: + - uri: + prefix: /adi +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: test-app-vs-delegated + namespace: delegate-virtual-service +spec: + http: + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - headers: + end-user: + prefix: jason + uri: + prefix: /haim +# name: dynamic-environment-default-dynamicenv-delegate-virtual-service-test-app-d559661c4d + route: + - destination: + host: test-app-service.delegate-virtual-service.svc.cluster.local + port: + number: 80 + subset: default-dynamicenv-delegate-virtual-service + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - uri: + prefix: /haim + name: haim + route: + - destination: + host: test-app-service.delegate-virtual-service.svc.cluster.local + port: + number: 80 + subset: shared + weight: 100 +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-delegate-virtual-service + namespace: default +status: + subsetsStatus: + test-app-default-dynamicenv-delegate-virtual-service: + subsetErrors: + virtualServices: + - error: 'Wierd, Couldn''t find a service with name: does-not-exist-delegate, + namespace: delegate-virtual-service, in the service list' + - error: Delegate (delegate-virtual-service/does-not-exist-delegate) not found + virtualServices: + - name: test-app-vs + namespace: delegate-virtual-service-2 + status: running + - name: test-app-vs-delegated + namespace: delegate-virtual-service + status: running +... \ No newline at end of file diff --git a/e2e-testing/kuttl/delegate-virtual-service/00-base.yaml b/e2e-testing/kuttl/delegate-virtual-service/00-base.yaml new file mode 100644 index 0000000..5838c6a --- /dev/null +++ b/e2e-testing/kuttl/delegate-virtual-service/00-base.yaml @@ -0,0 +1,181 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: delegate-virtual-service + labels: + istio-injection: enabled +--- +apiVersion: v1 +kind: Namespace +metadata: + name: delegate-virtual-service-2 + labels: + istio-injection: enabled +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + deployment.kubernetes.io/revision: "7" + labels: + app: test-app + version: shared + name: test-app + namespace: delegate-virtual-service +spec: + progressDeadlineSeconds: 1200 + replicas: 2 + selector: + matchLabels: + app: test-app + version: shared + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + labels: + app: test-app + app.kubernetes.io/instance: test-app + version: shared + spec: + containers: + - image: willejs/go-hello-world + imagePullPolicy: IfNotPresent + name: server + ports: + - containerPort: 5000 + name: http + protocol: TCP +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: test-app + purpose: main + name: test-app-service + namespace: delegate-virtual-service +spec: + ports: + - name: http + port: 80 + protocol: TCP + targetPort: 5000 + selector: + app: test-app + type: ClusterIP +--- +apiVersion: networking.istio.io/v1beta1 +kind: DestinationRule +metadata: + labels: + app: test-app + name: test-app + namespace: delegate-virtual-service +spec: + host: test-app-service.delegate-virtual-service.svc.cluster.local + subsets: + - labels: + version: shared + name: shared +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + labels: + app: test-app + name: test-app-vs + namespace: delegate-virtual-service-2 +spec: + gateways: + - gateways/private-default-1 + - mesh + hosts: + - test-app-service.delegate-virtual-service.svc.cluster.local + http: + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - mesh + name: in-cluster-default + route: + - destination: + host: test-app-service.delegate-virtual-service.svc.cluster.local + port: + number: 80 + subset: shared + weight: 100 + - match: + - uri: + prefix: /nisan + delegate: + name: test-app-vs-delegated + namespace: delegate-virtual-service + - match: + - uri: + prefix: /adi + delegate: + name: does-not-exist-delegate + namespace: delegate-virtual-service +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + labels: + app: test-app + name: test-app-vs-delegated + namespace: delegate-virtual-service +spec: + http: + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - uri: + prefix: /haim + name: haim + route: + - destination: + host: test-app-service.delegate-virtual-service.svc.cluster.local + port: + number: 80 + subset: shared + weight: 100 +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-delegate-virtual-service +spec: + istioMatches: + - headers: + end-user: + prefix: jason + subsets: + - name: test-app + namespace: delegate-virtual-service + containers: + - containerName: server + env: + - name: TOPIC_NAME + value: test diff --git a/e2e-testing/kuttl/delegate-virtual-service/01-assert.yaml b/e2e-testing/kuttl/delegate-virtual-service/01-assert.yaml new file mode 100644 index 0000000..2b333b8 --- /dev/null +++ b/e2e-testing/kuttl/delegate-virtual-service/01-assert.yaml @@ -0,0 +1,75 @@ +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: test-app-vs + namespace: delegate-virtual-service-2 +spec: + gateways: + - gateways/private-default-1 + - mesh + hosts: + - test-app-service.delegate-virtual-service.svc.cluster.local + http: + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - mesh + name: in-cluster-default + route: + - destination: + host: test-app-service.delegate-virtual-service.svc.cluster.local + port: + number: 80 + subset: shared + weight: 100 + - match: + - uri: + prefix: /nisan + delegate: + name: test-app-vs-delegated + namespace: delegate-virtual-service + - delegate: + name: does-not-exist-delegate + namespace: delegate-virtual-service + match: + - uri: + prefix: /adi +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: test-app-vs-delegated + namespace: delegate-virtual-service +spec: + http: + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - uri: + prefix: /haim + name: haim + route: + - destination: + host: test-app-service.delegate-virtual-service.svc.cluster.local + port: + number: 80 + subset: shared + weight: 100 diff --git a/e2e-testing/kuttl/delegate-virtual-service/01-delete-dynamic-env.yaml b/e2e-testing/kuttl/delegate-virtual-service/01-delete-dynamic-env.yaml new file mode 100644 index 0000000..66b7177 --- /dev/null +++ b/e2e-testing/kuttl/delegate-virtual-service/01-delete-dynamic-env.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: riskified.com/v1alpha1 + kind: DynamicEnv + name: dynamicenv-delegate-virtual-service + namespace: default diff --git a/e2e-testing/kuttl/delegate-virtual-service/Readme.md b/e2e-testing/kuttl/delegate-virtual-service/Readme.md new file mode 100644 index 0000000..d5a9f9b --- /dev/null +++ b/e2e-testing/kuttl/delegate-virtual-service/Readme.md @@ -0,0 +1,10 @@ +## DEV-52636 - Support Delegated Virtual Services + +This test verified integration with delegate virtual service: + +- Test virtual service with VS that contains both delegated and direct routes. +- Test VS across multiple namespaces. +- Test correct status +- Test cleanup of virtual *all* virtual services (delegated and direct) +- Test status errors (produced by missing delegate - we inject a delegated route to missing virtual + service) diff --git a/e2e-testing/kuttl/global-virtual-service-errors/00-assert.yaml b/e2e-testing/kuttl/global-virtual-service-errors/00-assert.yaml new file mode 100644 index 0000000..70faa61 --- /dev/null +++ b/e2e-testing/kuttl/global-virtual-service-errors/00-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-global-virtual-service-errors + namespace: default +status: + state: degraded + totalCount: 1 + totalReady: 0 + subsetsStatus: + details-default-dynamicenv-global-virtual-service-errors: + subsetErrors: + virtualServices: + - error: 'error updating virtual service for subset (details-default-dynamicenv-global-virtual-service-errors): + could not find even one virtual service that handles subset "details-default-dynamicenv-global-virtual-service-errors"' +... \ No newline at end of file diff --git a/e2e-testing/kuttl/global-virtual-service-errors/00-bookinfo-details.yaml b/e2e-testing/kuttl/global-virtual-service-errors/00-bookinfo-details.yaml new file mode 100644 index 0000000..ef5b7f4 --- /dev/null +++ b/e2e-testing/kuttl/global-virtual-service-errors/00-bookinfo-details.yaml @@ -0,0 +1,106 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: global-virtual-service-errors + labels: + istio-injection: enabled +--- +apiVersion: v1 +kind: Service +metadata: + name: details + namespace: global-virtual-service-errors + labels: + app: details + service: details +spec: + ports: + - port: 9080 + name: http + selector: + app: details +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-details + namespace: global-virtual-service-errors + labels: + account: details +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details + namespace: global-virtual-service-errors + labels: + app: details + version: shared +spec: + replicas: 1 + selector: + matchLabels: + app: details + version: shared + template: + metadata: + labels: + app: details + version: shared + spec: + serviceAccountName: bookinfo-details + containers: + - name: details + image: docker.io/istio/examples-bookinfo-details-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + securityContext: + runAsUser: 1000 +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details + namespace: global-virtual-service-errors +spec: + host: details + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: details + namespace: global-virtual-service-errors +spec: + hosts: + - details-2 + http: + - route: + - destination: + host: details + subset: shared +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-global-virtual-service-errors +spec: + istioMatches: + - headers: + end-user: + prefix: jason + subsets: + - name: details + namespace: global-virtual-service-errors + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test +... diff --git a/e2e-testing/kuttl/global-virtual-service-errors/Readme.md b/e2e-testing/kuttl/global-virtual-service-errors/Readme.md new file mode 100644 index 0000000..158a9e6 --- /dev/null +++ b/e2e-testing/kuttl/global-virtual-service-errors/Readme.md @@ -0,0 +1,5 @@ +## Global Virtual Service Errors + +This test makes sure we add global virtual service errors when needed. It only tests that the +provisioning for adding global errors works, It does not test the various use cases in which we add +these errors. It also validates that the *state* is *degraded* if such error exists. \ No newline at end of file diff --git a/e2e-testing/kuttl/kuttl-test.yaml b/e2e-testing/kuttl/kuttl-test.yaml new file mode 100644 index 0000000..e2b9fdc --- /dev/null +++ b/e2e-testing/kuttl/kuttl-test.yaml @@ -0,0 +1,5 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestSuite +startControlPlane: false +timeout: 240 +parallel: 4 diff --git a/e2e-testing/kuttl/modify-valid-httproutes/00-base.yaml b/e2e-testing/kuttl/modify-valid-httproutes/00-base.yaml new file mode 100644 index 0000000..6211c9e --- /dev/null +++ b/e2e-testing/kuttl/modify-valid-httproutes/00-base.yaml @@ -0,0 +1,73 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: modify-valid-httproutes + labels: + istio-injection: enabled +--- +apiVersion: v1 +kind: Service +metadata: + name: details + namespace: modify-valid-httproutes + labels: + app: details + service: details +spec: + ports: + - port: 9080 + name: http + selector: + app: details +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-details + namespace: modify-valid-httproutes + labels: + account: details +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details + namespace: modify-valid-httproutes + labels: + app: details + version: shared +spec: + replicas: 2 + selector: + matchLabels: + app: details + version: shared + template: + metadata: + labels: + app: details + version: shared + spec: + serviceAccountName: bookinfo-details + containers: + - name: details + image: docker.io/istio/examples-bookinfo-details-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + securityContext: + runAsUser: 1000 +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details + namespace: modify-valid-httproutes +spec: + host: details + subsets: + - name: shared + labels: + version: shared +... \ No newline at end of file diff --git a/e2e-testing/kuttl/modify-valid-httproutes/00-dynamic-env.yaml b/e2e-testing/kuttl/modify-valid-httproutes/00-dynamic-env.yaml new file mode 100644 index 0000000..77267cb --- /dev/null +++ b/e2e-testing/kuttl/modify-valid-httproutes/00-dynamic-env.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-modify-valid-httproutes +spec: + istioMatches: + - headers: + end-user: + prefix: jason + subsets: + - name: "details" + namespace: "modify-valid-httproutes" + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test diff --git a/e2e-testing/kuttl/modify-valid-httproutes/00-virtual-service.yaml b/e2e-testing/kuttl/modify-valid-httproutes/00-virtual-service.yaml new file mode 100644 index 0000000..a6d4f69 --- /dev/null +++ b/e2e-testing/kuttl/modify-valid-httproutes/00-virtual-service.yaml @@ -0,0 +1,27 @@ +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: details + namespace: modify-valid-httproutes +spec: + hosts: + - details + - another-details + http: + - match: + - gateways: + - mesh + route: + - destination: + host: details + subset: shared + weight: 80 + - destination: + host: another-details + subset: shared + weight: 20 + - route: + - destination: + host: details + subset: shared \ No newline at end of file diff --git a/e2e-testing/kuttl/modify-valid-httproutes/01-assert.yaml b/e2e-testing/kuttl/modify-valid-httproutes/01-assert.yaml new file mode 100644 index 0000000..f3ae6d6 --- /dev/null +++ b/e2e-testing/kuttl/modify-valid-httproutes/01-assert.yaml @@ -0,0 +1,42 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: details + namespace: modify-valid-httproutes +spec: + http: + # This match only routes to the target that matches our service host and not to both routes. + - match: + - gateways: + - mesh + headers: + end-user: + prefix: jason + # name: dynamic-environment-default-dynamicenv-modify-valid-httproutes-691103e5cc + route: + - destination: + host: details + subset: default-dynamicenv-modify-valid-httproutes + - match: + - gateways: + - mesh + route: + - destination: + host: details + subset: shared + - destination: + host: another-details + subset: shared + - match: + - headers: + end-user: + prefix: jason + # name: dynamic-environment-default-dynamicenv-modify-valid-httproutes-d0c21a3977 + route: + - destination: + host: details + subset: default-dynamicenv-modify-valid-httproutes + - route: + - destination: + host: details + subset: shared diff --git a/e2e-testing/kuttl/modify-valid-httproutes/02-assert.yaml b/e2e-testing/kuttl/modify-valid-httproutes/02-assert.yaml new file mode 100644 index 0000000..b2b03a3 --- /dev/null +++ b/e2e-testing/kuttl/modify-valid-httproutes/02-assert.yaml @@ -0,0 +1,21 @@ +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: details + namespace: modify-valid-httproutes +spec: + http: + - match: + - gateways: + - mesh + route: + - destination: + host: details + subset: shared + - destination: + host: another-details + subset: shared + - route: + - destination: + host: details + subset: shared diff --git a/e2e-testing/kuttl/modify-valid-httproutes/02-delete-dynamic-env.yaml b/e2e-testing/kuttl/modify-valid-httproutes/02-delete-dynamic-env.yaml new file mode 100644 index 0000000..bcd1595 --- /dev/null +++ b/e2e-testing/kuttl/modify-valid-httproutes/02-delete-dynamic-env.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: riskified.com/v1alpha1 + kind: DynamicEnv + name: dynamicenv-modify-valid-httproutes + namespace: default diff --git a/e2e-testing/kuttl/modify-valid-httproutes/Readme.md b/e2e-testing/kuttl/modify-valid-httproutes/Readme.md new file mode 100644 index 0000000..82889bf --- /dev/null +++ b/e2e-testing/kuttl/modify-valid-httproutes/Readme.md @@ -0,0 +1,14 @@ +## DEV-46154 Verify That Only Valid HttpRoutes are Modified + +This test verifies that when updating *VirtualService* we do not modify routes +that are pointing to different service then hours (you can see the comments in +the `01-assert.yaml` file). It then verifies that removing the *DynamicEnv* the +virtual service return to its original state. + +### Notes + +* The original `00-virtual-service.yaml` file would probably fail to install in + an environment that contains full *Istio* because it points to invalid target. +* The modified routes names in `01-assert.yaml` are commented out as *Kuttl* + only supports full match (no wildcard) and every time we'll update the test we + may change the hashed string and break the test. diff --git a/e2e-testing/kuttl/multiple-containers/00-assert.yaml b/e2e-testing/kuttl/multiple-containers/00-assert.yaml new file mode 100644 index 0000000..c1ddedd --- /dev/null +++ b/e2e-testing/kuttl/multiple-containers/00-assert.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details-default-dynamicenv-multiple-containers + namespace: multiple-containers +spec: + template: + spec: + containers: + - name: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + ports: + - containerPort: 9080 + env: + - name: MYENV + value: main + - name: TOPIC_NAME + value: test + - name: details-sidecar + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: MYENV + value: sidecar + - name: TOPIC_NAME + value: test diff --git a/e2e-testing/kuttl/multiple-containers/00-bookinfo-details.yaml b/e2e-testing/kuttl/multiple-containers/00-bookinfo-details.yaml new file mode 100644 index 0000000..ac14f54 --- /dev/null +++ b/e2e-testing/kuttl/multiple-containers/00-bookinfo-details.yaml @@ -0,0 +1,125 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: multiple-containers + labels: + istio-injection: enabled +--- +apiVersion: v1 +kind: Service +metadata: + name: details + namespace: multiple-containers + labels: + app: details + service: details +spec: + ports: + - port: 9080 + name: http + selector: + app: details +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-details + namespace: multiple-containers + labels: + account: details +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details + namespace: multiple-containers + labels: + app: details + version: shared +spec: + replicas: 1 + selector: + matchLabels: + app: details + version: shared + template: + metadata: + labels: + app: details + version: shared + spec: + serviceAccountName: bookinfo-details + containers: + - name: details + image: docker.io/istio/examples-bookinfo-details-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + securityContext: + runAsUser: 1000 + env: + - name: MYENV + value: main + - name: details-sidecar + image: docker.io/istio/examples-bookinfo-details-v1:1.16.2 + imagePullPolicy: IfNotPresent + securityContext: + runAsUser: 1000 + command: + - sleep + - "10000" + env: + - name: MYENV + value: sidecar +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details + namespace: multiple-containers +spec: + host: details + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: details + namespace: multiple-containers +spec: + hosts: + - details + http: + - route: + - destination: + host: details + subset: shared +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-multiple-containers +spec: + istioMatches: + - headers: + end-user: + prefix: jason + subsets: + - name: "details" + namespace: "multiple-containers" + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test + - containerName: details-sidecar + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test +... diff --git a/e2e-testing/kuttl/multiple-containers/Readme.md b/e2e-testing/kuttl/multiple-containers/Readme.md new file mode 100644 index 0000000..be3ed85 --- /dev/null +++ b/e2e-testing/kuttl/multiple-containers/Readme.md @@ -0,0 +1,3 @@ +|## Testing multiple containers in single deployment + +Just testing that the all containers get updated. \ No newline at end of file diff --git a/e2e-testing/kuttl/multiple-matches/00-assert.yaml b/e2e-testing/kuttl/multiple-matches/00-assert.yaml new file mode 100644 index 0000000..989b8c7 --- /dev/null +++ b/e2e-testing/kuttl/multiple-matches/00-assert.yaml @@ -0,0 +1,47 @@ +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: details + namespace: multiple-matches +spec: + http: + - match: + - headers: + end-user: + prefix: jason +# name: dynamic-environment-default-dynamicenv-multiple-matches-details-1cf517baac + route: + - destination: + host: details + subset: default-dynamicenv-multiple-matches + headers: + response: + add: + x-dynamic-env: details-default-dynamicenv-multiple-matches + - match: + - sourceLabels: + end-user: json +# name: dynamic-environment-default-dynamicenv-multiple-matches-details-5ad8be9618 + route: + - destination: + host: details + subset: default-dynamicenv-multiple-matches + headers: + response: + add: + x-dynamic-env: details-default-dynamicenv-multiple-matches + - route: + - destination: + host: details + subset: shared +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details-default-dynamicenv-multiple-matches + namespace: multiple-matches +spec: + template: + metadata: + labels: + x-key: x-value diff --git a/e2e-testing/kuttl/multiple-matches/00-bookinfo-details.yaml b/e2e-testing/kuttl/multiple-matches/00-bookinfo-details.yaml new file mode 100644 index 0000000..4c7bd7a --- /dev/null +++ b/e2e-testing/kuttl/multiple-matches/00-bookinfo-details.yaml @@ -0,0 +1,110 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: multiple-matches + labels: + istio-injection: enabled +--- +apiVersion: v1 +kind: Service +metadata: + name: details + namespace: multiple-matches + labels: + app: details + service: details +spec: + ports: + - port: 9080 + name: http + selector: + app: details +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-details + namespace: multiple-matches + labels: + account: details +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details + namespace: multiple-matches + labels: + app: details + version: shared +spec: + replicas: 1 + selector: + matchLabels: + app: details + version: shared + template: + metadata: + labels: + app: details + version: shared + spec: + serviceAccountName: bookinfo-details + containers: + - name: details + image: docker.io/istio/examples-bookinfo-details-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + securityContext: + runAsUser: 1000 +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details + namespace: multiple-matches +spec: + host: details + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: details + namespace: multiple-matches +spec: + hosts: + - details + http: + - route: + - destination: + host: details + subset: shared +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-multiple-matches +spec: + istioMatches: + - headers: + end-user: + prefix: jason + - sourceLabels: + end-user: json + subsets: + - name: "details" + namespace: "multiple-matches" + podLabels: + x-key: x-value + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test +... diff --git a/e2e-testing/kuttl/multiple-matches/Readme.md b/e2e-testing/kuttl/multiple-matches/Readme.md new file mode 100644 index 0000000..9148ddf --- /dev/null +++ b/e2e-testing/kuttl/multiple-matches/Readme.md @@ -0,0 +1,6 @@ +## Multiple Istio Matches and Source Labels + +This test validates the following features of multiple matches / source labels support: + +* Order of the routes is equal to the order of matches +* Source labels defined in Subset are passed to the deployments launched for the subset. diff --git a/e2e-testing/kuttl/multiple-services-scenarios-no-vs/00-assert.yaml b/e2e-testing/kuttl/multiple-services-scenarios-no-vs/00-assert.yaml new file mode 100644 index 0000000..00c4a66 --- /dev/null +++ b/e2e-testing/kuttl/multiple-services-scenarios-no-vs/00-assert.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-multiple-services-scenarios-no-vs + namespace: default +status: + state: degraded + subsetsStatus: + details-default-dynamicenv-multiple-services-scenarios-n: + deployment: + name: details-default-dynamicenv-multiple-services-scenarios-n + namespace: multiple-services-scenarios-no-vs + status: running + destinationRules: + - name: details-default-dynamicenv-multiple-services-scenarios-n-details + namespace: multiple-services-scenarios-no-vs + status: running + - name: details-default-dynamicenv-multiple-services-scenarios-n-details-alt + namespace: multiple-services-scenarios-no-vs + status: running + subsetErrors: + virtualServices: + - error: 'error updating virtual service for subset (details-default-dynamicenv-multiple-services-scenarios-n): + could not find even one virtual service that handles subset "details-default-dynamicenv-multiple-services-scenarios-n"' + totalCount: 1 + totalReady: 0 +... \ No newline at end of file diff --git a/e2e-testing/kuttl/multiple-services-scenarios-no-vs/00-bookinfo-details.yaml b/e2e-testing/kuttl/multiple-services-scenarios-no-vs/00-bookinfo-details.yaml new file mode 100644 index 0000000..85ca5d2 --- /dev/null +++ b/e2e-testing/kuttl/multiple-services-scenarios-no-vs/00-bookinfo-details.yaml @@ -0,0 +1,119 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: multiple-services-scenarios-no-vs + labels: + istio-injection: enabled +--- +apiVersion: v1 +kind: Service +metadata: + name: details + namespace: multiple-services-scenarios-no-vs + labels: + app: details + service: details +spec: + ports: + - port: 9080 + name: http + selector: + app: details +--- +apiVersion: v1 +kind: Service +metadata: + name: details-alt + namespace: multiple-services-scenarios-no-vs + labels: + app: details + service: details +spec: + ports: + - port: 9081 + name: http + selector: + app: details +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-details + namespace: multiple-services-scenarios-no-vs + labels: + account: details +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details + namespace: multiple-services-scenarios-no-vs + labels: + app: details + version: shared +spec: + replicas: 2 + selector: + matchLabels: + app: details + version: shared + template: + metadata: + labels: + app: details + version: shared + spec: + serviceAccountName: bookinfo-details + containers: + - name: details + image: docker.io/istio/examples-bookinfo-details-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + securityContext: + runAsUser: 1000 +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details + namespace: multiple-services-scenarios-no-vs +spec: + host: details + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details-alt + namespace: multiple-services-scenarios-no-vs +spec: + host: details-alt + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-multiple-services-scenarios-no-vs +spec: + istioMatches: + - headers: + end-user: + prefix: jason + subsets: + - name: details + namespace: multiple-services-scenarios-no-vs + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test +... \ No newline at end of file diff --git a/e2e-testing/kuttl/multiple-services-scenarios-no-vs/Readme.md b/e2e-testing/kuttl/multiple-services-scenarios-no-vs/Readme.md new file mode 100644 index 0000000..46235ed --- /dev/null +++ b/e2e-testing/kuttl/multiple-services-scenarios-no-vs/Readme.md @@ -0,0 +1,3 @@ +# Multiple Service Without Relevant Virtual Service + +Testing the behavior of multiple services when no valid VS exists... \ No newline at end of file diff --git a/e2e-testing/kuttl/multiple-services-scenarios-no-working-single-host/00-assert.yaml b/e2e-testing/kuttl/multiple-services-scenarios-no-working-single-host/00-assert.yaml new file mode 100644 index 0000000..222f3d5 --- /dev/null +++ b/e2e-testing/kuttl/multiple-services-scenarios-no-working-single-host/00-assert.yaml @@ -0,0 +1,46 @@ +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-multiple-services-scenarios-no-working-single-host + namespace: default +spec: + istioMatches: + - headers: + end-user: + prefix: jason + subsets: + - containers: + - containerName: details + env: + - name: TOPIC_NAME + value: test + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + name: details + namespace: multiple-services-scenarios-no-working-single-host +status: + state: degraded + subsetsStatus: + details-default-dynamicenv-multiple-services-scenarios-n: + deployment: + name: details-default-dynamicenv-multiple-services-scenarios-n + namespace: multiple-services-scenarios-no-working-single-host + status: running + destinationRules: + - name: details-default-dynamicenv-multiple-services-scenarios-n-details + namespace: multiple-services-scenarios-no-working-single-host + status: ignored-missing-destination-rule + - name: details-default-dynamicenv-multiple-services-scenarios-n-details-alt + namespace: multiple-services-scenarios-no-working-single-host + status: running + subsetErrors: + subset: + - error: Couldn't find common active service hostname across DestinationRules + and VirtualServices + virtualServices: + - name: details + namespace: multiple-services-scenarios-no-working-single-host + status: running + totalCount: 1 + totalReady: 0 +... \ No newline at end of file diff --git a/e2e-testing/kuttl/multiple-services-scenarios-no-working-single-host/00-bookinfo-details.yaml b/e2e-testing/kuttl/multiple-services-scenarios-no-working-single-host/00-bookinfo-details.yaml new file mode 100644 index 0000000..c0eec77 --- /dev/null +++ b/e2e-testing/kuttl/multiple-services-scenarios-no-working-single-host/00-bookinfo-details.yaml @@ -0,0 +1,121 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: multiple-services-scenarios-no-working-single-host + labels: + istio-injection: enabled +--- +apiVersion: v1 +kind: Service +metadata: + name: details + namespace: multiple-services-scenarios-no-working-single-host + labels: + app: details + service: details +spec: + ports: + - port: 9080 + name: http + selector: + app: details +--- +apiVersion: v1 +kind: Service +metadata: + name: details-alt + namespace: multiple-services-scenarios-no-working-single-host + labels: + app: details + service: details +spec: + ports: + - port: 9081 + name: http + selector: + app: details +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-details + namespace: multiple-services-scenarios-no-working-single-host + labels: + account: details +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details + namespace: multiple-services-scenarios-no-working-single-host + labels: + app: details + version: shared +spec: + replicas: 2 + selector: + matchLabels: + app: details + version: shared + template: + metadata: + labels: + app: details + version: shared + spec: + serviceAccountName: bookinfo-details + containers: + - name: details + image: docker.io/istio/examples-bookinfo-details-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + securityContext: + runAsUser: 1000 +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details-alt + namespace: multiple-services-scenarios-no-working-single-host +spec: + host: details-alt + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: details + namespace: multiple-services-scenarios-no-working-single-host +spec: + hosts: + - details + http: + - route: + - destination: + host: details + subset: shared +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-multiple-services-scenarios-no-working-single-host +spec: + istioMatches: + - headers: + end-user: + prefix: jason + subsets: + - name: details + namespace: multiple-services-scenarios-no-working-single-host + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test +... \ No newline at end of file diff --git a/e2e-testing/kuttl/multiple-services-scenarios/00-assert.yaml b/e2e-testing/kuttl/multiple-services-scenarios/00-assert.yaml new file mode 100644 index 0000000..ce5fa84 --- /dev/null +++ b/e2e-testing/kuttl/multiple-services-scenarios/00-assert.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-multiple-services-per-deployment + namespace: default +status: + state: ready + subsetsStatus: + details-default-dynamicenv-multiple-services-per-deploym: + deployment: + name: details-default-dynamicenv-multiple-services-per-deploym + namespace: multiple-services-per-deployment + status: running + destinationRules: + - name: details-default-dynamicenv-multiple-services-per-deploym-details + namespace: multiple-services-per-deployment + status: running + - name: details-default-dynamicenv-multiple-services-per-deploym-details-alt + namespace: multiple-services-per-deployment + status: running + virtualServices: + - name: details + namespace: multiple-services-per-deployment + status: running + totalCount: 1 + totalReady: 1 +... \ No newline at end of file diff --git a/e2e-testing/kuttl/multiple-services-scenarios/00-bookinfo-details.yaml b/e2e-testing/kuttl/multiple-services-scenarios/00-bookinfo-details.yaml new file mode 100644 index 0000000..6ee84ca --- /dev/null +++ b/e2e-testing/kuttl/multiple-services-scenarios/00-bookinfo-details.yaml @@ -0,0 +1,133 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: multiple-services-per-deployment + labels: + istio-injection: enabled +--- +apiVersion: v1 +kind: Service +metadata: + name: details + namespace: multiple-services-per-deployment + labels: + app: details + service: details +spec: + ports: + - port: 9080 + name: http + selector: + app: details +--- +apiVersion: v1 +kind: Service +metadata: + name: details-alt + namespace: multiple-services-per-deployment + labels: + app: details + service: details +spec: + ports: + - port: 9081 + name: http + selector: + app: details +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-details + namespace: multiple-services-per-deployment + labels: + account: details +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details + namespace: multiple-services-per-deployment + labels: + app: details + version: shared +spec: + replicas: 2 + selector: + matchLabels: + app: details + version: shared + template: + metadata: + labels: + app: details + version: shared + spec: + serviceAccountName: bookinfo-details + containers: + - name: details + image: docker.io/istio/examples-bookinfo-details-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + securityContext: + runAsUser: 1000 +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details + namespace: multiple-services-per-deployment +spec: + host: details + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details-alt + namespace: multiple-services-per-deployment +spec: + host: details-alt + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: details + namespace: multiple-services-per-deployment +spec: + hosts: + - details + http: + - route: + - destination: + host: details + subset: shared +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-multiple-services-per-deployment +spec: + istioMatches: + - headers: + end-user: + prefix: jason + subsets: + - name: details + namespace: multiple-services-per-deployment + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test +... \ No newline at end of file diff --git a/e2e-testing/kuttl/multiple-services-scenarios/01-remove-base-destination-rule.yaml b/e2e-testing/kuttl/multiple-services-scenarios/01-remove-base-destination-rule.yaml new file mode 100644 index 0000000..dfa4cde --- /dev/null +++ b/e2e-testing/kuttl/multiple-services-scenarios/01-remove-base-destination-rule.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: networking.istio.io/v1alpha3 + kind: DestinationRule + name: details-alt + namespace: multiple-services-per-deployment diff --git a/e2e-testing/kuttl/multiple-services-scenarios/02-assert.yaml b/e2e-testing/kuttl/multiple-services-scenarios/02-assert.yaml new file mode 100644 index 0000000..f3d0531 --- /dev/null +++ b/e2e-testing/kuttl/multiple-services-scenarios/02-assert.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-multiple-services-per-deployment + namespace: default +status: + state: ready + subsetsStatus: + details-default-dynamicenv-multiple-services-per-deploym: + deployment: + name: details-default-dynamicenv-multiple-services-per-deploym + namespace: multiple-services-per-deployment + status: running + destinationRules: + - name: details-default-dynamicenv-multiple-services-per-deploym-details + namespace: multiple-services-per-deployment + status: running + - name: details-default-dynamicenv-multiple-services-per-deploym-details-alt + namespace: multiple-services-per-deployment + status: ignored-missing-destination-rule +# virtualServices: +# - name: details +# namespace: multiple-services-per-deployment +# status: running + totalCount: 1 + totalReady: 1 +... \ No newline at end of file diff --git a/e2e-testing/kuttl/multiple-services-scenarios/02-remove-DE-destination-rule.yaml b/e2e-testing/kuttl/multiple-services-scenarios/02-remove-DE-destination-rule.yaml new file mode 100644 index 0000000..1066b6a --- /dev/null +++ b/e2e-testing/kuttl/multiple-services-scenarios/02-remove-DE-destination-rule.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: networking.istio.io/v1alpha3 + kind: DestinationRule + name: details-default-dynamicenv-multiple-services-per-deploym-details-alt + namespace: multiple-services-per-deployment diff --git a/e2e-testing/kuttl/multiple-services-scenarios/03-remove-second-base-destination-rule.yaml b/e2e-testing/kuttl/multiple-services-scenarios/03-remove-second-base-destination-rule.yaml new file mode 100644 index 0000000..4b7a343 --- /dev/null +++ b/e2e-testing/kuttl/multiple-services-scenarios/03-remove-second-base-destination-rule.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: networking.istio.io/v1alpha3 + kind: DestinationRule + name: details + namespace: multiple-services-per-deployment diff --git a/e2e-testing/kuttl/multiple-services-scenarios/04-assert.yaml b/e2e-testing/kuttl/multiple-services-scenarios/04-assert.yaml new file mode 100644 index 0000000..64008e8 --- /dev/null +++ b/e2e-testing/kuttl/multiple-services-scenarios/04-assert.yaml @@ -0,0 +1,31 @@ +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-multiple-services-per-deployment + namespace: default +status: + state: degraded + subsetsStatus: + details-default-dynamicenv-multiple-services-per-deploym: + deployment: + name: details-default-dynamicenv-multiple-services-per-deploym + namespace: multiple-services-per-deployment + status: running + destinationRules: + - name: details-default-dynamicenv-multiple-services-per-deploym-details + namespace: multiple-services-per-deployment + status: ignored-missing-destination-rule + - name: details-default-dynamicenv-multiple-services-per-deploym-details-alt + namespace: multiple-services-per-deployment + status: ignored-missing-destination-rule +# virtualServices: +# - name: details +# namespace: multiple-services-per-deployment +# status: running + subsetErrors: + destinationRule: + - error: 'no base destination rules were found for subset: details-default-dynamicenv-multiple-services-per-deploym' + totalCount: 1 + totalReady: 1 +... \ No newline at end of file diff --git a/e2e-testing/kuttl/multiple-services-scenarios/04-remove-DE-second-destination-rule.yaml b/e2e-testing/kuttl/multiple-services-scenarios/04-remove-DE-second-destination-rule.yaml new file mode 100644 index 0000000..0a5b504 --- /dev/null +++ b/e2e-testing/kuttl/multiple-services-scenarios/04-remove-DE-second-destination-rule.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: networking.istio.io/v1alpha3 + kind: DestinationRule + name: details-default-dynamicenv-multiple-services-per-deploym-details + namespace: multiple-services-per-deployment diff --git a/e2e-testing/kuttl/multiple-services-scenarios/05-add-virtual-service.yaml b/e2e-testing/kuttl/multiple-services-scenarios/05-add-virtual-service.yaml new file mode 100644 index 0000000..4718fee --- /dev/null +++ b/e2e-testing/kuttl/multiple-services-scenarios/05-add-virtual-service.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: details-alt + namespace: multiple-services-per-deployment +spec: + hosts: + - details-alt + http: + - route: + - destination: + host: details-alt + subset: shared +... \ No newline at end of file diff --git a/e2e-testing/kuttl/multiple-services-scenarios/05-assert.yaml b/e2e-testing/kuttl/multiple-services-scenarios/05-assert.yaml new file mode 100644 index 0000000..d6f8d60 --- /dev/null +++ b/e2e-testing/kuttl/multiple-services-scenarios/05-assert.yaml @@ -0,0 +1,31 @@ +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-multiple-services-per-deployment + namespace: default +status: + state: ready + subsetsStatus: + details-default-dynamicenv-multiple-services-per-deploym: + deployment: + name: details-default-dynamicenv-multiple-services-per-deploym + namespace: multiple-services-per-deployment + status: running + destinationRules: + - name: details-default-dynamicenv-multiple-services-per-deploym-details + namespace: multiple-services-per-deployment + status: running + - name: details-default-dynamicenv-multiple-services-per-deploym-details-alt + namespace: multiple-services-per-deployment + status: running + virtualServices: + - name: details + namespace: multiple-services-per-deployment + status: running + - name: details-alt + namespace: multiple-services-per-deployment + status: running + totalCount: 1 + totalReady: 1 +... diff --git a/e2e-testing/kuttl/multiple-services-scenarios/05-restore-all-destination-rules.yaml b/e2e-testing/kuttl/multiple-services-scenarios/05-restore-all-destination-rules.yaml new file mode 100644 index 0000000..c9c7b8f --- /dev/null +++ b/e2e-testing/kuttl/multiple-services-scenarios/05-restore-all-destination-rules.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details + namespace: multiple-services-per-deployment +spec: + host: details + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details-alt + namespace: multiple-services-per-deployment +spec: + host: details-alt + subsets: + - name: shared + labels: + version: shared +--- \ No newline at end of file diff --git a/e2e-testing/kuttl/multiple-services-scenarios/Readme.md b/e2e-testing/kuttl/multiple-services-scenarios/Readme.md new file mode 100644 index 0000000..59724bd --- /dev/null +++ b/e2e-testing/kuttl/multiple-services-scenarios/Readme.md @@ -0,0 +1,10 @@ +# Multiple Services Scenarios (per single deployment) + +Several scenarios of multiple services + +* Step 0: multiple services with everything ok. +* Step 1+2: multiple services with one of two base destination rules missing - status should be + running. +* Step 3+4: multiple services with all destination rules missing - status should be degraded with + warnings. +* Step 5: Full virtual services and destination rules \ No newline at end of file diff --git a/e2e-testing/kuttl/reconcile-flow/00-assert.yaml b/e2e-testing/kuttl/reconcile-flow/00-assert.yaml new file mode 100644 index 0000000..a778494 --- /dev/null +++ b/e2e-testing/kuttl/reconcile-flow/00-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-reconcile-flow +status: + state: degraded + totalCount: 1 + totalReady: 0 + subsetsStatus: + details-default-dynamicenv-reconcile-flow: + subsetErrors: + deployment: + - error: 'couldn''t find the deployment we need to override (name: details, + ns: reconcile-flow), Deployment.apps "details" not found' diff --git a/e2e-testing/kuttl/reconcile-flow/00-base.yaml b/e2e-testing/kuttl/reconcile-flow/00-base.yaml new file mode 100644 index 0000000..25b9a93 --- /dev/null +++ b/e2e-testing/kuttl/reconcile-flow/00-base.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-reconcile-flow +spec: + istioMatches: + - headers: + end-user: + prefix: jason + subsets: + - name: details + namespace: reconcile-flow + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 diff --git a/e2e-testing/kuttl/reconcile-flow/Readme.md b/e2e-testing/kuttl/reconcile-flow/Readme.md new file mode 100644 index 0000000..5512d2f --- /dev/null +++ b/e2e-testing/kuttl/reconcile-flow/Readme.md @@ -0,0 +1,4 @@ +## Testing Reconcile Loop Flow + +This simple function verifies that even with fatal error (can not find the deployment to override) +the controller does not exit mid-function but finishes the loop and sets the status correctly. \ No newline at end of file diff --git a/e2e-testing/kuttl/replica-size/00-assert.yaml b/e2e-testing/kuttl/replica-size/00-assert.yaml new file mode 100644 index 0000000..c9eb3d9 --- /dev/null +++ b/e2e-testing/kuttl/replica-size/00-assert.yaml @@ -0,0 +1,33 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: details + version: default-dynamicenv-replica-size + name: details-default-dynamicenv-replica-size + namespace: replica-size +status: + replicas: 2 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: details + version: default-dynamicenv-replica-size + name: details-worker-default-dynamicenv-replica-size + namespace: replica-size +status: + replicas: 1 +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details-default-dynamicenv-replica-size-details + namespace: replica-size +spec: + host: details + subsets: + - name: default-dynamicenv-replica-size + labels: + version: default-dynamicenv-replica-size \ No newline at end of file diff --git a/e2e-testing/kuttl/replica-size/00-bookinfo-details.yaml b/e2e-testing/kuttl/replica-size/00-bookinfo-details.yaml new file mode 100644 index 0000000..aeef4fe --- /dev/null +++ b/e2e-testing/kuttl/replica-size/00-bookinfo-details.yaml @@ -0,0 +1,144 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: replica-size + labels: + istio-injection: enabled +--- +apiVersion: v1 +kind: Service +metadata: + name: details + namespace: replica-size + labels: + app: details + service: details +spec: + ports: + - port: 9080 + name: http + selector: + app: details +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-details + namespace: replica-size + labels: + account: details +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details + namespace: replica-size + labels: + app: details + version: shared +spec: + replicas: 2 + selector: + matchLabels: + app: details + version: shared + template: + metadata: + labels: + app: details + version: shared + spec: + serviceAccountName: bookinfo-details + containers: + - name: details + image: docker.io/istio/examples-bookinfo-details-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + securityContext: + runAsUser: 1000 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details-worker + namespace: replica-size + labels: + app: details + version: shared +spec: + replicas: 2 + selector: + matchLabels: + app: details-worker + version: shared + template: + metadata: + labels: + app: details-worker + version: shared + spec: + serviceAccountName: bookinfo-details + containers: + - name: details + image: docker.io/istio/examples-bookinfo-details-v1:1.16.2 + imagePullPolicy: IfNotPresent + securityContext: + runAsUser: 1000 +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details + namespace: replica-size +spec: + host: details + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: details + namespace: replica-size +spec: + hosts: + - details + http: + - route: + - destination: + host: details + subset: shared +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-replica-size +spec: + istioMatches: + - headers: + end-user: + prefix: jason + subsets: + - name: details + namespace: replica-size + replicas: 2 + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test + consumers: + - name: details-worker + namespace: replica-size + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test-worker +... \ No newline at end of file diff --git a/e2e-testing/kuttl/replica-size/01-assert.yaml b/e2e-testing/kuttl/replica-size/01-assert.yaml new file mode 100644 index 0000000..4774a9d --- /dev/null +++ b/e2e-testing/kuttl/replica-size/01-assert.yaml @@ -0,0 +1,22 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: details + version: default-dynamicenv-replica-size + name: details-default-dynamicenv-replica-size + namespace: replica-size +status: + replicas: 1 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: details + version: default-dynamicenv-replica-size + name: details-worker-default-dynamicenv-replica-size + namespace: replica-size +status: + replicas: 2 +--- \ No newline at end of file diff --git a/e2e-testing/kuttl/replica-size/01-dynamicenv.yaml b/e2e-testing/kuttl/replica-size/01-dynamicenv.yaml new file mode 100644 index 0000000..d999114 --- /dev/null +++ b/e2e-testing/kuttl/replica-size/01-dynamicenv.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-replica-size +spec: + istioMatches: + - headers: + end-user: + prefix: jason + subsets: + - name: details + namespace: replica-size + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test + consumers: + - name: details-worker + namespace: replica-size + replicas: 2 + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test-worker +... \ No newline at end of file diff --git a/e2e-testing/kuttl/replica-size/02-delete-dynamic-env.yaml b/e2e-testing/kuttl/replica-size/02-delete-dynamic-env.yaml new file mode 100644 index 0000000..3090a50 --- /dev/null +++ b/e2e-testing/kuttl/replica-size/02-delete-dynamic-env.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: riskified.com/v1alpha1 + kind: DynamicEnv + name: dynamicenv-replica-size + namespace: default diff --git a/e2e-testing/kuttl/replica-size/02-errors.yaml b/e2e-testing/kuttl/replica-size/02-errors.yaml new file mode 100644 index 0000000..97bf31f --- /dev/null +++ b/e2e-testing/kuttl/replica-size/02-errors.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-replica-size +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details-default-dynamicenv-replica-size + namespace: replica-size +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details-default-dynamicenv-replica-size + namespace: replica-size \ No newline at end of file diff --git a/e2e-testing/kuttl/replica-size/Readme.md b/e2e-testing/kuttl/replica-size/Readme.md new file mode 100644 index 0000000..38eed3c --- /dev/null +++ b/e2e-testing/kuttl/replica-size/Readme.md @@ -0,0 +1,10 @@ +## Correct replica size + +This test verifies that when creating *DynamicEnv* it will create the correct *Deployment* replica +size. If specified it will set the specified number of replicas, of not then the default (1). It +checks on both consumers and subsets (creation and updates). + + +> Note: the original test also contains validating of _DestinationRule_. Not really related for this +> test, but I'll leave it anyway. In case the destination rule test fails you can probably delete it +(but you have to _figure out why and make sure you have alternative test_). diff --git a/e2e-testing/kuttl/response-headers/00-assert.yaml b/e2e-testing/kuttl/response-headers/00-assert.yaml new file mode 100644 index 0000000..38c05d2 --- /dev/null +++ b/e2e-testing/kuttl/response-headers/00-assert.yaml @@ -0,0 +1,137 @@ +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: test-app-vs + namespace: response-headers-2 +spec: + hosts: + - test-app-service.response-headers.svc.cluster.local + http: + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - mesh + headers: + end-user: + prefix: jason +# name: dynamic-environment-default-dynamicenv-response-headers-test-app-eab8f3914d + route: + - destination: + host: test-app-service.response-headers.svc.cluster.local + port: + number: 80 + subset: default-dynamicenv-response-headers + headers: + response: + add: + x-dynamic-env: test-app-default-dynamicenv-response-headers + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - mesh + name: in-cluster-default + route: + - destination: + host: test-app-service.response-headers.svc.cluster.local + port: + number: 80 + subset: shared + weight: 100 + - delegate: + name: test-app-vs-delegated + namespace: response-headers + match: + - uri: + prefix: /nisan +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: test-app-vs-delegated + namespace: response-headers +spec: + http: + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - headers: + end-user: + prefix: jason + uri: + prefix: /haim +# name: dynamic-environment-default-dynamicenv-response-headers-test-app-d559661c4d + route: + - destination: + host: test-app-service.response-headers.svc.cluster.local + port: + number: 80 + subset: default-dynamicenv-response-headers + headers: + response: + add: + x-dynamic-env: test-app-default-dynamicenv-response-headers + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - uri: + prefix: /haim + name: haim + route: + - destination: + host: test-app-service.response-headers.svc.cluster.local + port: + number: 80 + subset: shared + weight: 100 +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-response-headers + namespace: default +status: + subsetsStatus: + test-app-default-dynamicenv-response-headers: + virtualServices: + - name: test-app-vs + namespace: response-headers-2 + status: running + - name: test-app-vs-delegated + namespace: response-headers + status: running +... \ No newline at end of file diff --git a/e2e-testing/kuttl/response-headers/00-base.yaml b/e2e-testing/kuttl/response-headers/00-base.yaml new file mode 100644 index 0000000..1ca28fe --- /dev/null +++ b/e2e-testing/kuttl/response-headers/00-base.yaml @@ -0,0 +1,176 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: response-headers + labels: + istio-injection: enabled +--- +apiVersion: v1 +kind: Namespace +metadata: + name: response-headers-2 + labels: + istio-injection: enabled +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + deployment.kubernetes.io/revision: '7' + labels: + app: test-app + version: shared + name: test-app + namespace: response-headers +spec: + progressDeadlineSeconds: 1200 + replicas: 2 + selector: + matchLabels: + app: test-app + version: shared + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + labels: + app: test-app + app.kubernetes.io/instance: test-app + version: shared + spec: + containers: + - image: willejs/go-hello-world + imagePullPolicy: IfNotPresent + name: server + ports: + - containerPort: 5000 + name: http + protocol: TCP +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: test-app + purpose: main + name: test-app-service + namespace: response-headers +spec: + ports: + - name: http + port: 80 + protocol: TCP + targetPort: 5000 + selector: + app: test-app + type: ClusterIP +--- +apiVersion: networking.istio.io/v1beta1 +kind: DestinationRule +metadata: + labels: + app: test-app + name: test-app + namespace: response-headers +spec: + host: test-app-service.response-headers.svc.cluster.local + subsets: + - labels: + version: shared + name: shared +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + labels: + app: test-app + name: test-app-vs + namespace: response-headers-2 +spec: + gateways: + - gateways/private-default-1 + - mesh + hosts: + - test-app-service.response-headers.svc.cluster.local + http: + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - mesh + name: in-cluster-default + route: + - destination: + host: test-app-service.response-headers.svc.cluster.local + port: + number: 80 + subset: shared + weight: 100 + - match: + - uri: + prefix: /nisan + delegate: + name: test-app-vs-delegated + namespace: response-headers +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + labels: + app: test-app + name: test-app-vs-delegated + namespace: response-headers +spec: + http: + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - uri: + prefix: /haim + name: haim + route: + - destination: + host: test-app-service.response-headers.svc.cluster.local + port: + number: 80 + subset: shared + weight: 100 +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-response-headers +spec: + istioMatches: + - headers: + end-user: + prefix: jason + subsets: + - name: test-app + namespace: response-headers + containers: + - containerName: server + env: + - name: TOPIC_NAME + value: test +... \ No newline at end of file diff --git a/e2e-testing/kuttl/response-headers/Readme.md b/e2e-testing/kuttl/response-headers/Readme.md new file mode 100644 index 0000000..817107e --- /dev/null +++ b/e2e-testing/kuttl/response-headers/Readme.md @@ -0,0 +1,4 @@ +## DEV-55208 - Add Response Headers + +This test verifies that we add response headers both in direct virtual service manipulation and in +delegates. diff --git a/e2e-testing/kuttl/simple-test/00-assert.yaml b/e2e-testing/kuttl/simple-test/00-assert.yaml new file mode 100644 index 0000000..1c523e7 --- /dev/null +++ b/e2e-testing/kuttl/simple-test/00-assert.yaml @@ -0,0 +1,37 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: details + dynamic-env: "true" + version: default-dynamicenv-simple-test + name: details-default-dynamicenv-simple-test + namespace: simple-test +spec: + template: + metadata: + labels: + dynamic-env: "true" +status: + replicas: 1 +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details-default-dynamicenv-simple-test-details + namespace: simple-test +spec: + host: details + subsets: + - name: default-dynamicenv-simple-test + labels: + version: default-dynamicenv-simple-test +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-simple-test +status: + state: ready + totalCount: 1 + totalReady: 1 \ No newline at end of file diff --git a/e2e-testing/kuttl/simple-test/00-bookinfo-details.yaml b/e2e-testing/kuttl/simple-test/00-bookinfo-details.yaml new file mode 100644 index 0000000..c13ccc7 --- /dev/null +++ b/e2e-testing/kuttl/simple-test/00-bookinfo-details.yaml @@ -0,0 +1,106 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: simple-test + labels: + istio-injection: enabled +--- +apiVersion: v1 +kind: Service +metadata: + name: details + namespace: simple-test + labels: + app: details + service: details +spec: + ports: + - port: 9080 + name: http + selector: + app: details +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-details + namespace: simple-test + labels: + account: details +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details + namespace: simple-test + labels: + app: details + version: shared +spec: + replicas: 1 + selector: + matchLabels: + app: details + version: shared + template: + metadata: + labels: + app: details + version: shared + spec: + serviceAccountName: bookinfo-details + containers: + - name: details + image: docker.io/istio/examples-bookinfo-details-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + securityContext: + runAsUser: 1000 +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details + namespace: simple-test +spec: + host: details + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: details + namespace: simple-test +spec: + hosts: + - details + http: + - route: + - destination: + host: details + subset: shared +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-simple-test +spec: + istioMatches: + - headers: + end-user: + prefix: jason + subsets: + - name: "details" + namespace: "simple-test" + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test +... diff --git a/e2e-testing/kuttl/simple-test/01-delete-dynamic-env.yaml b/e2e-testing/kuttl/simple-test/01-delete-dynamic-env.yaml new file mode 100644 index 0000000..4c5f74c --- /dev/null +++ b/e2e-testing/kuttl/simple-test/01-delete-dynamic-env.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: riskified.com/v1alpha1 + kind: DynamicEnv + name: dynamicenv-simple-test + namespace: default diff --git a/e2e-testing/kuttl/simple-test/01-errors.yaml b/e2e-testing/kuttl/simple-test/01-errors.yaml new file mode 100644 index 0000000..80fe4f6 --- /dev/null +++ b/e2e-testing/kuttl/simple-test/01-errors.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-simple-test +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details-default-dynamicenv-simple-test + namespace: simple-test +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details-default-dynamicenv-simple-test + namespace: simple-test diff --git a/e2e-testing/kuttl/simple-test/Readme.md b/e2e-testing/kuttl/simple-test/Readme.md new file mode 100644 index 0000000..2c92ca9 --- /dev/null +++ b/e2e-testing/kuttl/simple-test/Readme.md @@ -0,0 +1,8 @@ +## Validation of very basic functionality + +This basic test validates the following: + +* Basic *Deployment* and *DestinationRule* creation. +* Basic global state is set to *ready*. +* A `dynamic-env: "true"` label is added to both the launched Deployment resources and the pods. + diff --git a/e2e-testing/kuttl/status-updates/00-assert.yaml b/e2e-testing/kuttl/status-updates/00-assert.yaml new file mode 100644 index 0000000..2d748e5 --- /dev/null +++ b/e2e-testing/kuttl/status-updates/00-assert.yaml @@ -0,0 +1,23 @@ +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-status-updates + namespace: default +status: + state: ready + totalCount: 1 + totalReady: 1 + subsetsStatus: + details-default-dynamicenv-status-updates: + deployment: + name: details-default-dynamicenv-status-updates + namespace: status-updates + status: running + destinationRules: + - name: details-default-dynamicenv-status-updates-details + namespace: status-updates + status: running + virtualServices: + - name: details + namespace: status-updates + status: running diff --git a/e2e-testing/kuttl/status-updates/00-bookinfo-details.yaml b/e2e-testing/kuttl/status-updates/00-bookinfo-details.yaml new file mode 100644 index 0000000..2ee588e --- /dev/null +++ b/e2e-testing/kuttl/status-updates/00-bookinfo-details.yaml @@ -0,0 +1,106 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: status-updates + labels: + istio-injection: enabled +--- +apiVersion: v1 +kind: Service +metadata: + name: details + namespace: status-updates + labels: + app: details + service: details +spec: + ports: + - port: 9080 + name: http + selector: + app: details +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-details + namespace: status-updates + labels: + account: details +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details + namespace: status-updates + labels: + app: details + version: shared +spec: + replicas: 1 + selector: + matchLabels: + app: details + version: shared + template: + metadata: + labels: + app: details + version: shared + spec: + serviceAccountName: bookinfo-details + containers: + - name: details + image: docker.io/istio/examples-bookinfo-details-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + securityContext: + runAsUser: 1000 +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details + namespace: status-updates +spec: + host: details + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: details + namespace: status-updates +spec: + hosts: + - details + http: + - route: + - destination: + host: details + subset: shared +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-status-updates +spec: + istioMatches: + - headers: + end-user: + prefix: jason + subsets: + - name: "details" + namespace: "status-updates" + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test +... diff --git a/e2e-testing/kuttl/status-updates/Readme.md b/e2e-testing/kuttl/status-updates/Readme.md new file mode 100644 index 0000000..2835ed0 --- /dev/null +++ b/e2e-testing/kuttl/status-updates/Readme.md @@ -0,0 +1,4 @@ +## DEV-54056 detailed status + +Validating that when launching dynamic-environment it's status reaches running state. + diff --git a/e2e-testing/kuttl/update-subset-resources/00-assert.yaml b/e2e-testing/kuttl/update-subset-resources/00-assert.yaml new file mode 100644 index 0000000..c2d9dda --- /dev/null +++ b/e2e-testing/kuttl/update-subset-resources/00-assert.yaml @@ -0,0 +1,31 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: details + version: default-dynamicenv-update-subset-resources + name: details-default-dynamicenv-update-subset-resources + namespace: update-subset-resources +status: + replicas: 1 +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details-default-dynamicenv-update-subset-resources-details + namespace: update-subset-resources +spec: + host: details + subsets: + - name: default-dynamicenv-update-subset-resources + labels: + version: default-dynamicenv-update-subset-resources +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-update-subset-resources +status: + subsetsStatus: + details-default-dynamicenv-update-subset-resources: + hash: -5576677875946656812 diff --git a/e2e-testing/kuttl/update-subset-resources/00-bookinfo-details.yaml b/e2e-testing/kuttl/update-subset-resources/00-bookinfo-details.yaml new file mode 100644 index 0000000..0fcd330 --- /dev/null +++ b/e2e-testing/kuttl/update-subset-resources/00-bookinfo-details.yaml @@ -0,0 +1,106 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: update-subset-resources + labels: + istio-injection: enabled +--- +apiVersion: v1 +kind: Service +metadata: + name: details + namespace: update-subset-resources + labels: + app: details + service: details +spec: + ports: + - port: 9080 + name: http + selector: + app: details +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-details + namespace: update-subset-resources + labels: + account: details +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details + namespace: update-subset-resources + labels: + app: details + version: shared +spec: + replicas: 1 + selector: + matchLabels: + app: details + version: shared + template: + metadata: + labels: + app: details + version: shared + spec: + serviceAccountName: bookinfo-details + containers: + - name: details + image: docker.io/istio/examples-bookinfo-details-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + securityContext: + runAsUser: 1000 +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details + namespace: update-subset-resources +spec: + host: details + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: details + namespace: update-subset-resources +spec: + hosts: + - details + http: + - route: + - destination: + host: details + subset: shared +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-update-subset-resources +spec: + istioMatches: + - headers: + end-user: + prefix: jason + subsets: + - name: details + namespace: update-subset-resources + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test +... diff --git a/e2e-testing/kuttl/update-subset-resources/01-assert.yaml b/e2e-testing/kuttl/update-subset-resources/01-assert.yaml new file mode 100644 index 0000000..961c857 --- /dev/null +++ b/e2e-testing/kuttl/update-subset-resources/01-assert.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details-default-dynamicenv-update-subset-resources + namespace: update-subset-resources +spec: + template: + spec: + containers: + - image: docker.io/istio/examples-bookinfo-details-v2:1.16.3 + env: + - name: TOPIC_NAME + value: test2 +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-update-subset-resources +status: + subsetsStatus: + details-default-dynamicenv-update-subset-resources: + hash: 3856039101763933687 + deployment: + name: details-default-dynamicenv-update-subset-resources + namespace: update-subset-resources + status: running +... \ No newline at end of file diff --git a/e2e-testing/kuttl/update-subset-resources/01-update-subset.yaml b/e2e-testing/kuttl/update-subset-resources/01-update-subset.yaml new file mode 100644 index 0000000..5e4fb96 --- /dev/null +++ b/e2e-testing/kuttl/update-subset-resources/01-update-subset.yaml @@ -0,0 +1,18 @@ +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-update-subset-resources +spec: + istioMatches: + - headers: + end-user: + prefix: jason + subsets: + - name: details + namespace: update-subset-resources + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.3 + env: + - name: TOPIC_NAME + value: test2 diff --git a/e2e-testing/kuttl/update-subset-resources/Readme.md b/e2e-testing/kuttl/update-subset-resources/Readme.md new file mode 100644 index 0000000..3ccfdf1 --- /dev/null +++ b/e2e-testing/kuttl/update-subset-resources/Readme.md @@ -0,0 +1,8 @@ +## DEV-55206 - Testing update subset resources + +Verify that we can update certain fields inside each subset. We also test that the hashes are +updating. + +> Note, because of the way Kuttl queries the cluster we can not check intermediate status +> (`updating`). There result is unstable because the status is updated quickly. + diff --git a/e2e-testing/kuttl/valid-state/00-assert.yaml b/e2e-testing/kuttl/valid-state/00-assert.yaml new file mode 100644 index 0000000..5f942cf --- /dev/null +++ b/e2e-testing/kuttl/valid-state/00-assert.yaml @@ -0,0 +1,16 @@ +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-valid-state +status: + subsetsStatus: + details-default-dynamicenv-valid-state: + deployment: + name: details-default-dynamicenv-valid-state + namespace: valid-state + destinationRules: + - name: details-default-dynamicenv-valid-state-details + namespace: valid-state + virtualServices: + - name: details + namespace: valid-state diff --git a/e2e-testing/kuttl/valid-state/00-bookinfo-details.yaml b/e2e-testing/kuttl/valid-state/00-bookinfo-details.yaml new file mode 100644 index 0000000..afab3e2 --- /dev/null +++ b/e2e-testing/kuttl/valid-state/00-bookinfo-details.yaml @@ -0,0 +1,106 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: valid-state + labels: + istio-injection: enabled +--- +apiVersion: v1 +kind: Service +metadata: + name: details + namespace: valid-state + labels: + app: details + service: details +spec: + ports: + - port: 9080 + name: http + selector: + app: details +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-details + namespace: valid-state + labels: + account: details +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details + namespace: valid-state + labels: + app: details + version: shared +spec: + replicas: 1 + selector: + matchLabels: + app: details + version: shared + template: + metadata: + labels: + app: details + version: shared + spec: + serviceAccountName: bookinfo-details + containers: + - name: details + image: docker.io/istio/examples-bookinfo-details-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + securityContext: + runAsUser: 1000 +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details + namespace: valid-state +spec: + host: details + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: details + namespace: valid-state +spec: + hosts: + - details + http: + - route: + - destination: + host: details + subset: shared +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-valid-state +spec: + istioMatches: + - headers: + end-user: + prefix: jason + subsets: + - name: "details" + namespace: "valid-state" + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test +... diff --git a/e2e-testing/kuttl/valid-state/Readme.md b/e2e-testing/kuttl/valid-state/Readme.md new file mode 100644 index 0000000..0858812 --- /dev/null +++ b/e2e-testing/kuttl/valid-state/Readme.md @@ -0,0 +1,7 @@ +## DEV-46153 Correct state + +This test verifies that the *DynamicEnv* contains all created *Deployment*, *DestinationRule* and +*VirtualService*. + +> Note: we do not test for empty subset errors as Kuttl doesn't seem to support testing the omission +> of key regardless of its value. diff --git a/e2e-testing/kuttl/vs-with-multiple-services/00-assert.yaml b/e2e-testing/kuttl/vs-with-multiple-services/00-assert.yaml new file mode 100644 index 0000000..3de0b4a --- /dev/null +++ b/e2e-testing/kuttl/vs-with-multiple-services/00-assert.yaml @@ -0,0 +1,77 @@ +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: all-services + namespace: vs-with-multiple-services +spec: + hosts: + - details + - reviews + - ratings + - productpage + http: + - match: + - headers: + end-user: + prefix: jason + uri: + exact: /details +# name: dynamic-environment-default-dynamicenv-vs-with-multiple-services-details-... + route: + - destination: + host: details + subset: default-dynamicenv-vs-with-multiple-services + - match: + - uri: + exact: /details + route: + - destination: + host: details + subset: shared + - match: + - headers: + end-user: + prefix: jason + uri: + exact: /reviews + # name: dynamic-environment-default-dynamicenv-vs-with-multiple-services-reviews-... + route: + - destination: + host: reviews + subset: default-dynamicenv-vs-with-multiple-services + - match: + - uri: + exact: /reviews + route: + - destination: + host: reviews + subset: shared + - match: + - uri: + exact: /ratings + route: + - destination: + host: ratings + subset: shared + - match: + - uri: + exact: /productpage + route: + - destination: + host: productpage + subset: shared +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-vs-with-multiple-services + namespace: default +status: + subsetsStatus: + details-default-dynamicenv-vs-with-multiple-services: + virtualServices: + - name: all-services + namespace: vs-with-multiple-services + status: running +... \ No newline at end of file diff --git a/e2e-testing/kuttl/vs-with-multiple-services/00-full-bookinfo-details.yaml b/e2e-testing/kuttl/vs-with-multiple-services/00-full-bookinfo-details.yaml new file mode 100644 index 0000000..76bf725 --- /dev/null +++ b/e2e-testing/kuttl/vs-with-multiple-services/00-full-bookinfo-details.yaml @@ -0,0 +1,363 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: vs-with-multiple-services + labels: + istio-injection: enabled +--- +apiVersion: v1 +kind: Service +metadata: + name: details + namespace: vs-with-multiple-services + labels: + app: details + service: details +spec: + ports: + - port: 9080 + name: http + selector: + app: details +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-details + namespace: vs-with-multiple-services + labels: + account: details +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details + namespace: vs-with-multiple-services + labels: + app: details + version: shared +spec: + replicas: 1 + selector: + matchLabels: + app: details + version: shared + template: + metadata: + labels: + app: details + version: shared + spec: + serviceAccountName: bookinfo-details + containers: + - name: details + image: docker.io/istio/examples-bookinfo-details-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + securityContext: + runAsUser: 1000 +--- +################################################################################################## +# Ratings service +################################################################################################## +apiVersion: v1 +kind: Service +metadata: + name: ratings + namespace: vs-with-multiple-services + labels: + app: ratings + service: ratings +spec: + ports: + - port: 9080 + name: http + selector: + app: ratings +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-ratings + namespace: vs-with-multiple-services + labels: + account: ratings +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ratings + namespace: vs-with-multiple-services + labels: + app: ratings + version: shared +spec: + replicas: 1 + selector: + matchLabels: + app: ratings + version: shared + template: + metadata: + labels: + app: ratings + version: shared + spec: + serviceAccountName: bookinfo-ratings + containers: + - name: ratings + image: docker.io/istio/examples-bookinfo-ratings-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + securityContext: + runAsUser: 1000 +--- +################################################################################################## +# Reviews service +################################################################################################## +apiVersion: v1 +kind: Service +metadata: + name: reviews + namespace: vs-with-multiple-services + labels: + app: reviews + service: reviews +spec: + ports: + - port: 9080 + name: http + selector: + app: reviews +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-reviews + namespace: vs-with-multiple-services + labels: + account: reviews +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: reviews + namespace: vs-with-multiple-services + labels: + app: reviews + version: shared +spec: + replicas: 1 + selector: + matchLabels: + app: reviews + version: shared + template: + metadata: + labels: + app: reviews + version: shared + spec: + serviceAccountName: bookinfo-reviews + containers: + - name: reviews + image: docker.io/istio/examples-bookinfo-reviews-v2:1.16.2 + imagePullPolicy: IfNotPresent + env: + - name: LOG_DIR + value: "/tmp/logs" + ports: + - containerPort: 9080 + volumeMounts: + - name: tmp + mountPath: /tmp + - name: wlp-output + mountPath: /opt/ibm/wlp/output + securityContext: + runAsUser: 1000 + volumes: + - name: wlp-output + emptyDir: { } + - name: tmp + emptyDir: { } +--- +################################################################################################## +# Productpage services +################################################################################################## +apiVersion: v1 +kind: Service +metadata: + name: productpage + namespace: vs-with-multiple-services + labels: + app: productpage + service: productpage +spec: + ports: + - port: 9080 + name: http + selector: + app: productpage +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-productpage + namespace: vs-with-multiple-services + labels: + account: productpage +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: productpage + namespace: vs-with-multiple-services + labels: + app: productpage + version: shared +spec: + replicas: 1 + selector: + matchLabels: + app: productpage + version: shared + template: + metadata: + labels: + app: productpage + version: shared + spec: + serviceAccountName: bookinfo-productpage + containers: + - name: productpage + image: docker.io/istio/examples-bookinfo-productpage-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + volumeMounts: + - name: tmp + mountPath: /tmp + securityContext: + runAsUser: 1000 + volumes: + - name: tmp + emptyDir: { } +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: productpage + namespace: vs-with-multiple-services +spec: + host: productpage + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: reviews + namespace: vs-with-multiple-services +spec: + host: reviews + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: ratings + namespace: vs-with-multiple-services +spec: + host: ratings + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details + namespace: vs-with-multiple-services +spec: + host: details + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: all-services + namespace: vs-with-multiple-services +spec: + hosts: + - details + - reviews + - ratings + - productpage + http: + - match: + - uri: + exact: /details + route: + - destination: + host: details + subset: shared + - match: + - uri: + exact: /reviews + route: + - destination: + host: reviews + subset: shared + - match: + - uri: + exact: /ratings + route: + - destination: + host: ratings + subset: shared + - match: + - uri: + exact: /productpage + route: + - destination: + host: productpage + subset: shared +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-vs-with-multiple-services +spec: + istioMatches: + - headers: + end-user: + prefix: jason + subsets: + - name: details + namespace: vs-with-multiple-services + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test + - name: reviews + namespace: vs-with-multiple-services + containers: + - containerName: reviews + image: docker.io/istio/examples-bookinfo-reviews-v3:1.16.2 + env: + - name: TOPIC_NAME + value: test +... \ No newline at end of file diff --git a/e2e-testing/kuttl/vs-with-multiple-services/Readme.md b/e2e-testing/kuttl/vs-with-multiple-services/Readme.md new file mode 100644 index 0000000..5d1ca9f --- /dev/null +++ b/e2e-testing/kuttl/vs-with-multiple-services/Readme.md @@ -0,0 +1,12 @@ +## DEV-55209 VirtualService with multiple services bug + +This bug happens when more than one subset is handled by the same virtual service. Once we handle it +in the first subset we consider it as done and don't touch it again in the second subset which +should handle different section. + +> Note: you can not test this app in a browser - in order to merge all *VirtualService*s I added an +> invalid *uri path*. + +In this test we verify that: + +* Both `details` and `reviews` are modified in the single `VirtualService`. \ No newline at end of file diff --git a/e2e-testing/kuttl/watch-annotations/00-assert.yaml b/e2e-testing/kuttl/watch-annotations/00-assert.yaml new file mode 100644 index 0000000..8812f92 --- /dev/null +++ b/e2e-testing/kuttl/watch-annotations/00-assert.yaml @@ -0,0 +1,34 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + riskified.com/dynamic-environment: "default/dynamicenv-watch-annotations" + labels: + app: details + version: default-dynamicenv-watch-annotations + name: details-default-dynamicenv-watch-annotations + namespace: watch-annotations +status: + replicas: 1 +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + annotations: + riskified.com/dynamic-environment: "default/dynamicenv-watch-annotations" + name: details-default-dynamicenv-watch-annotations-details + namespace: watch-annotations +spec: + host: details + subsets: + - name: default-dynamicenv-watch-annotations + labels: + version: default-dynamicenv-watch-annotations +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: details + namespace: watch-annotations + annotations: + riskified.com/dynamic-environment: "default/dynamicenv-watch-annotations" diff --git a/e2e-testing/kuttl/watch-annotations/00-bookinfo-details.yaml b/e2e-testing/kuttl/watch-annotations/00-bookinfo-details.yaml new file mode 100644 index 0000000..128eeed --- /dev/null +++ b/e2e-testing/kuttl/watch-annotations/00-bookinfo-details.yaml @@ -0,0 +1,106 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: watch-annotations + labels: + istio-injection: enabled +--- +apiVersion: v1 +kind: Service +metadata: + name: details + namespace: watch-annotations + labels: + app: details + service: details +spec: + ports: + - port: 9080 + name: http + selector: + app: details +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bookinfo-details + namespace: watch-annotations + labels: + account: details +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details + namespace: watch-annotations + labels: + app: details + version: shared +spec: + replicas: 1 + selector: + matchLabels: + app: details + version: shared + template: + metadata: + labels: + app: details + version: shared + spec: + serviceAccountName: bookinfo-details + containers: + - name: details + image: docker.io/istio/examples-bookinfo-details-v1:1.16.2 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9080 + securityContext: + runAsUser: 1000 +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details + namespace: watch-annotations +spec: + host: details + subsets: + - name: shared + labels: + version: shared +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: details + namespace: watch-annotations +spec: + hosts: + - details + http: + - route: + - destination: + host: details + subset: shared +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-watch-annotations +spec: + istioMatches: + - headers: + end-user: + prefix: jason + subsets: + - name: details + namespace: watch-annotations + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test +... diff --git a/e2e-testing/kuttl/watch-annotations/01-assert.yaml b/e2e-testing/kuttl/watch-annotations/01-assert.yaml new file mode 100644 index 0000000..31d804c --- /dev/null +++ b/e2e-testing/kuttl/watch-annotations/01-assert.yaml @@ -0,0 +1,21 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + riskified.com/dynamic-environment: "default/dynamicenv-watch-annotations" + labels: + app: details + version: default-dynamicenv-watch-annotations + name: details-default-dynamicenv-watch-annotations + namespace: watch-annotations +spec: + template: + spec: + containers: + - env: + - name: TOPIC_NAME + value: test2 +status: + replicas: 1 +... \ No newline at end of file diff --git a/e2e-testing/kuttl/watch-annotations/01-update.yaml b/e2e-testing/kuttl/watch-annotations/01-update.yaml new file mode 100644 index 0000000..1a94a6f --- /dev/null +++ b/e2e-testing/kuttl/watch-annotations/01-update.yaml @@ -0,0 +1,20 @@ +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-watch-annotations +spec: + istioMatches: + - headers: + end-user: + prefix: jason + subsets: + - name: details + namespace: watch-annotations + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test2 +... diff --git a/e2e-testing/kuttl/watch-annotations/02-assert.yaml b/e2e-testing/kuttl/watch-annotations/02-assert.yaml new file mode 100644 index 0000000..cdf0478 --- /dev/null +++ b/e2e-testing/kuttl/watch-annotations/02-assert.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: details + namespace: watch-annotations + annotations: + riskified.com/dynamic-environment: "" diff --git a/e2e-testing/kuttl/watch-annotations/02-delete-dynamic-env.yaml b/e2e-testing/kuttl/watch-annotations/02-delete-dynamic-env.yaml new file mode 100644 index 0000000..37acff3 --- /dev/null +++ b/e2e-testing/kuttl/watch-annotations/02-delete-dynamic-env.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: riskified.com/v1alpha1 + kind: DynamicEnv + name: dynamicenv-watch-annotations + namespace: default diff --git a/e2e-testing/kuttl/watch-annotations/02-errors.yaml b/e2e-testing/kuttl/watch-annotations/02-errors.yaml new file mode 100644 index 0000000..793d08d --- /dev/null +++ b/e2e-testing/kuttl/watch-annotations/02-errors.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-watch-annotations +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details-default-dynamicenv-watch-annotations + namespace: watch-annotations +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: details-default-dynamicenv-watch-annotations + namespace: watch-annotations diff --git a/e2e-testing/kuttl/watch-annotations/Readme.md b/e2e-testing/kuttl/watch-annotations/Readme.md new file mode 100644 index 0000000..b6954bf --- /dev/null +++ b/e2e-testing/kuttl/watch-annotations/Readme.md @@ -0,0 +1,10 @@ +## DEV-54056 Verifying Watch Annotations + +This test verifies that all created resources have the watch annotation added +and managed correctly: + +* Watch annotations are added to deployment +* Watch annotations are added to destination rules +* Watch annotations are added to virtual service upon modifications and removed + upon rollback. +* Watch annotations persist after deployment modification diff --git a/e2e-testing/kuttl/weighted-routes/00-assert.yaml b/e2e-testing/kuttl/weighted-routes/00-assert.yaml new file mode 100644 index 0000000..5796cd8 --- /dev/null +++ b/e2e-testing/kuttl/weighted-routes/00-assert.yaml @@ -0,0 +1,276 @@ +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + annotations: + riskified.com/dynamic-environment: default/dynamicenv-weighted-routes + labels: + app: test-app + name: test-app-vs + namespace: weighted-routes +spec: + gateways: + - gateways/private-default-1 + - mesh + hosts: + - test-app-service.weighted-routes.svc.cluster.local + - workshop-nisan.staging.riskxint.com + http: + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - mesh + headers: + end-user: + prefix: jason + # name: dynamic-environment-default-dynamicenv-weighted-routes-a205bdfcab + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: default-dynamicenv-weighted-routes + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - mesh + name: in-cluster-default + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: shared + weight: 100 + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: canary + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - gateways/private-default-1 + headers: + end-user: + prefix: jason + uri: + prefix: /nisan + # name: dynamic-environment-default-dynamicenv-weighted-routes-a3918d5502 + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: default-dynamicenv-weighted-routes + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - gateways/private-default-1 + uri: + prefix: /nisan + name: nisan + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: shared + weight: 100 + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: canary + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - gateways/private-default-1 + headers: + end-user: + prefix: jason + uri: + prefix: /haim + # name: dynamic-environment-default-dynamicenv-weighted-routes-c003a3d137 + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: default-dynamicenv-weighted-routes + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - gateways/private-default-1 + uri: + prefix: /haim + name: haim + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: shared + weight: 100 + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: canary + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - gateways/private-default-1 + headers: + end-user: + prefix: jason + uri: + prefix: / + # name: dynamic-environment-default-dynamicenv-weighted-routes-c413deda5d + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: default-dynamicenv-weighted-routes + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - gateways/private-default-1 + uri: + prefix: / + name: default + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: shared + weight: 100 + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: canary +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: test-app + version: default-dynamicenv-weighted-routes + name: test-app-default-dynamicenv-weighted-routes + namespace: weighted-routes +spec: + replicas: 1 + selector: + matchLabels: + app: test-app + version: default-dynamicenv-weighted-routes + template: + metadata: + labels: + app: test-app + app.kubernetes.io/instance: test-app + version: default-dynamicenv-weighted-routes + spec: + containers: + - env: + - name: TOPIC_NAME + value: test + image: willejs/go-hello-world + imagePullPolicy: IfNotPresent + name: server + ports: + - containerPort: 5000 + name: http + protocol: TCP + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File +--- +apiVersion: networking.istio.io/v1beta1 +kind: DestinationRule +metadata: + labels: + version: default-dynamicenv-weighted-routes + name: test-app-default-dynamicenv-weighted-routes-test-app-service + namespace: weighted-routes +spec: + host: test-app-service.weighted-routes.svc.cluster.local + subsets: + - labels: + version: default-dynamicenv-weighted-routes + name: default-dynamicenv-weighted-routes +... \ No newline at end of file diff --git a/e2e-testing/kuttl/weighted-routes/00-base.yaml b/e2e-testing/kuttl/weighted-routes/00-base.yaml new file mode 100644 index 0000000..8c8a243 --- /dev/null +++ b/e2e-testing/kuttl/weighted-routes/00-base.yaml @@ -0,0 +1,241 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: weighted-routes + labels: + istio-injection: enabled +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + deployment.kubernetes.io/revision: '7' + labels: + app: test-app + version: shared + name: test-app + namespace: weighted-routes +spec: + progressDeadlineSeconds: 1200 + replicas: 2 + selector: + matchLabels: + app: test-app + version: shared + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + labels: + app: test-app + app.kubernetes.io/instance: test-app + version: shared + spec: + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: node.riskified.com/capacityType + operator: In + values: + - spot + weight: 100 + containers: + - image: willejs/go-hello-world + imagePullPolicy: IfNotPresent + name: server + ports: + - containerPort: 5000 + name: http + protocol: TCP +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: test-app + purpose: main + name: test-app-service + namespace: weighted-routes +spec: + ports: + - name: http + port: 80 + protocol: TCP + targetPort: 5000 + selector: + app: test-app + type: ClusterIP +--- +apiVersion: networking.istio.io/v1beta1 +kind: DestinationRule +metadata: + labels: + app: test-app + name: test-app + namespace: weighted-routes +spec: + host: test-app-service.weighted-routes.svc.cluster.local + subsets: + - labels: + version: shared + rollouts-pod-template-hash: 84f87694c8 + name: shared + - labels: + version: shared + rollouts-pod-template-hash: 84f87694c8 + name: canary +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + labels: + app: test-app + name: test-app-vs + namespace: weighted-routes +spec: + gateways: + - gateways/private-default-1 + - mesh + hosts: + - test-app-service.weighted-routes.svc.cluster.local + - workshop-nisan.staging.riskxint.com + http: + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - mesh + name: in-cluster-default + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: shared + weight: 100 + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: canary + weight: 0 + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - gateways/private-default-1 + uri: + prefix: /nisan + name: nisan + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: shared + weight: 100 + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: canary + weight: 0 + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - gateways/private-default-1 + uri: + prefix: /haim + name: haim + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: shared + weight: 100 + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: canary + weight: 0 + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - gateways/private-default-1 + uri: + prefix: / + name: default + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: shared + weight: 100 + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: canary + weight: 0 +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-weighted-routes +spec: + istioMatches: + - headers: + end-user: + prefix: jason + subsets: + - name: test-app + namespace: weighted-routes + containers: + - containerName: server + env: + - name: TOPIC_NAME + value: test +... \ No newline at end of file diff --git a/e2e-testing/kuttl/weighted-routes/01-assert.yaml b/e2e-testing/kuttl/weighted-routes/01-assert.yaml new file mode 100644 index 0000000..a4f4c54 --- /dev/null +++ b/e2e-testing/kuttl/weighted-routes/01-assert.yaml @@ -0,0 +1,126 @@ +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + labels: + app: test-app + name: test-app-vs + namespace: weighted-routes +spec: + gateways: + - gateways/private-default-1 + - mesh + hosts: + - test-app-service.weighted-routes.svc.cluster.local + - workshop-nisan.staging.riskxint.com + http: + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - mesh + name: in-cluster-default + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: shared + weight: 100 + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: canary + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - gateways/private-default-1 + uri: + prefix: /nisan + name: nisan + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: shared + weight: 100 + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: canary + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - gateways/private-default-1 + uri: + prefix: /haim + name: haim + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: shared + weight: 100 + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: canary + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - gateways/private-default-1 + uri: + prefix: / + name: default + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: shared + weight: 100 + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: canary \ No newline at end of file diff --git a/e2e-testing/kuttl/weighted-routes/01-delete-dynamicenv.yaml b/e2e-testing/kuttl/weighted-routes/01-delete-dynamicenv.yaml new file mode 100644 index 0000000..98328b9 --- /dev/null +++ b/e2e-testing/kuttl/weighted-routes/01-delete-dynamicenv.yaml @@ -0,0 +1,7 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: riskified.com/v1alpha1 + kind: DynamicEnv + name: dynamicenv-weighted-routes + namespace: default diff --git a/e2e-testing/kuttl/weighted-routes/01-errors.yaml b/e2e-testing/kuttl/weighted-routes/01-errors.yaml new file mode 100644 index 0000000..05b9e42 --- /dev/null +++ b/e2e-testing/kuttl/weighted-routes/01-errors.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-app-default-dynamicenv-weighted-routes + namespace: weighted-routes +--- +apiVersion: networking.istio.io/v1beta1 +kind: DestinationRule +metadata: + name: test-app-default-dynamicenv-weighted-routes + namespace: weighted-routes +... diff --git a/e2e-testing/kuttl/weighted-routes/02-assert.yaml b/e2e-testing/kuttl/weighted-routes/02-assert.yaml new file mode 100644 index 0000000..6a2741d --- /dev/null +++ b/e2e-testing/kuttl/weighted-routes/02-assert.yaml @@ -0,0 +1,230 @@ +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + annotations: + riskified.com/dynamic-environment: default/dynamicenv-weighted-routes + labels: + app: test-app + name: test-app-vs + namespace: weighted-routes +spec: + gateways: + - gateways/private-default-1 + - mesh + hosts: + - test-app-service.weighted-routes.svc.cluster.local + - workshop-nisan.staging.riskxint.com + http: + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - mesh + headers: + end-user: + prefix: jason + # name: dynamic-environment-default-dynamicenv-weighted-routes-a205bdfcab + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: default-dynamicenv-weighted-routes + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - mesh + name: in-cluster-default + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: shared + weight: 80 + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: canary + weight: 20 + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - gateways/private-default-1 + headers: + end-user: + prefix: jason + uri: + prefix: /nisan + # name: dynamic-environment-default-dynamicenv-weighted-routes-a3918d5502 + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: default-dynamicenv-weighted-routes + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - gateways/private-default-1 + uri: + prefix: /nisan + name: nisan + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: shared + weight: 80 + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: canary + weight: 20 + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - gateways/private-default-1 + headers: + end-user: + prefix: jason + uri: + prefix: /haim + # name: dynamic-environment-default-dynamicenv-weighted-routes-c003a3d137 + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: default-dynamicenv-weighted-routes + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - gateways/private-default-1 + uri: + prefix: /haim + name: haim + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: shared + weight: 80 + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: canary + weight: 20 + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - gateways/private-default-1 + headers: + end-user: + prefix: jason + uri: + prefix: / + # name: dynamic-environment-default-dynamicenv-weighted-routes-c413deda5d + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: default-dynamicenv-weighted-routes + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - gateways/private-default-1 + uri: + prefix: / + name: default + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: shared + weight: 80 + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: canary + weight: 20 \ No newline at end of file diff --git a/e2e-testing/kuttl/weighted-routes/02-canary-virtual-service.yaml b/e2e-testing/kuttl/weighted-routes/02-canary-virtual-service.yaml new file mode 100644 index 0000000..5f79a04 --- /dev/null +++ b/e2e-testing/kuttl/weighted-routes/02-canary-virtual-service.yaml @@ -0,0 +1,150 @@ +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + labels: + app: test-app + version: staging-master-39a4e1e + name: test-app-vs + namespace: weighted-routes +spec: + gateways: + - gateways/private-default-1 + - mesh + hosts: + - test-app-service.weighted-routes.svc.cluster.local + - workshop-nisan.staging.riskxint.com + http: + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - mesh + name: in-cluster-default + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: shared + weight: 80 + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: canary + weight: 20 + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - gateways/private-default-1 + uri: + prefix: /nisan + name: nisan + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: shared + weight: 80 + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: canary + weight: 20 + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - gateways/private-default-1 + uri: + prefix: /haim + name: haim + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: shared + weight: 80 + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: canary + weight: 20 + - headers: + request: + add: + x-request-start: t=%START_TIME(%s.%3f)% + set: + X-FORWARDED-PROTO: https + response: + remove: + - x-envoy-upstream-healthchecked-cluster + - x-envoy-upstream-service-time + match: + - gateways: + - gateways/private-default-1 + uri: + prefix: / + name: default + route: + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: shared + weight: 80 + - destination: + host: test-app-service.weighted-routes.svc.cluster.local + port: + number: 80 + subset: canary + weight: 20 +--- +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-weighted-routes +spec: + istioMatches: + - headers: + end-user: + prefix: jason + subsets: + - name: test-app + namespace: weighted-routes + containers: + - containerName: server + env: + - name: TOPIC_NAME + value: test +... \ No newline at end of file diff --git a/e2e-testing/kuttl/weighted-routes/Readme.md b/e2e-testing/kuttl/weighted-routes/Readme.md new file mode 100644 index 0000000..8cfe970 --- /dev/null +++ b/e2e-testing/kuttl/weighted-routes/Readme.md @@ -0,0 +1,12 @@ +# Testing DynamicEnv in Weighted Routes Scenarios + +In this test we're testing two cases of weighted scenarios (e.g. could happen +during the progress of canary deploy): + +1. This scenario is when one route is 100% and the other is 0% (e.g. before or + after canary deploy). +2. This scenario is when both routes have some weight (e.g. in the middle of + deploy). These weights should be kept in existing routes. + +Note, This test currently does not handle modifications during deploy as we do +not yet support it in our code. diff --git a/e2e-testing/scripts/setup.sh b/e2e-testing/scripts/setup.sh new file mode 100755 index 0000000..4bbe222 --- /dev/null +++ b/e2e-testing/scripts/setup.sh @@ -0,0 +1,89 @@ +#!/bin/bash + +# A script to load all dependencies into already exists cluster. +# If you are using specific Kind Cluster name define $KIND_CLUSTER_NAME. + +bold=$(tput bold) +normal=$(tput sgr0) + +# enable -e after tput to avoid errors on github +set -e +TESTS_DIR=$(dirname `dirname $0`) +DEPENDENCIES_DIR="$TESTS_DIR/dependencies" + +if [ -n "$KIND_CLUSTER_NAME" ]; then + KIND_LOAD_OPTIONS="--name $KIND_CLUSTER_NAME" +fi + +yamls=("cert-manager_1.8.yaml") + +function deps() { + for y in ${yamls[@]}; do + kubectl apply -f ${DEPENDENCIES_DIR}/$y + done + istioctl install --set profile=demo -y + # cert-manager seems to start slowly + echo "" + echo -n "Waiting 30 seconds for everything to initialize properly ..." + sleep 30 + echo " Done" +} + +function deploy() { + make docker-build + kind load docker-image $KIND_LOAD_OPTIONS controller:latest + echo "" + make deploy +} + +function remove() { + make undeploy + for y in ${yamls[@]}; do + kubectl delete -f ${DEPENDENCIES_DIR}/$y + done + echo "${bold}Note: this script does not uninstall Istio!${normal}" +} + +function create() { + env_file="${TESTS_DIR}/env" + . $env_file + kind create cluster --image $KIND_CUSTOM_IMAGE +} + + +case "$1" in + up) + deps + deploy + ;; + down) + remove + ;; + create) + create + ;; + delete) + kind delete cluster + ;; + deps) + deps + ;; + deploy) + deploy + ;; + *) + echo "Usage $0 " + echo "" + echo "COMMAND is one of:" + echo " - up: Populate cluster with required dependencies and controller." + echo " - down: Cleanup cluster from all deps and controller." + echo " - create: Creates a kind cluster with default name." + echo " - delete: Deletes the default cluster." + echo " - deps: Install required dependencies." + echo " - deploy: Deploys the controller." + echo "" + echo "Only for deploying the controller: if the cluster name is not the default one use KIND_CLUSTER_NAME environment variable to define the cluster name." + echo "" + echo "${bold}NOTE: All commands assumes your kubernetes config points to the KIND cluster!${normal}" + ;; +esac diff --git a/extensions/deployment_extensions.go b/extensions/deployment_extensions.go new file mode 100644 index 0000000..70848f4 --- /dev/null +++ b/extensions/deployment_extensions.go @@ -0,0 +1,25 @@ +package extensions + +import ( + riskifiedv1alpha1 "github.com/riskified/dynamic-environment/api/v1alpha1" + appsv1 "k8s.io/api/apps/v1" +) + +// DeploymentExtensionData contains handler context data to be passed to the extension methods that +// might be required by the extension method. +type DeploymentExtensionData struct { + // The deployment we should use as base + BaseDeployment *appsv1.Deployment + // The DynamicEnv matchers + Matches []riskifiedv1alpha1.IstioMatch + // The subset that contains the data of our deployment + Subset riskifiedv1alpha1.Subset +} + +// ExtendOverridingDeployment is a function that could be used to customize the overriding +// deployment. Optionally using the `DeploymentExtensionData` data you can overwrite parts of the +// `deployment` ref. +func ExtendOverridingDeployment(deployment *appsv1.Deployment, data DeploymentExtensionData) error { + + return nil +} diff --git a/extensions/doc.go b/extensions/doc.go new file mode 100644 index 0000000..ad79517 --- /dev/null +++ b/extensions/doc.go @@ -0,0 +1,9 @@ +// The extension package is a package to aid local customizations of handlers. Every user of this +// operator might have custom needs for operating in the local environment (e.g. node selectors, +// spacial tags, etc). In order to overload the `Subset` object with all possible customizations we +// created this package that does not contain any implementations, just empty functions to be called +// from within specific points in the `Reconcile` loop. If a company has spacial requirements it can +// populate these functions in a local fork of the repository. While it's still forces the company +// to handle merges, the merges are in a specific package where chances of merge conflicts are +// reduced to effectively zero. +package extensions diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4128e3c --- /dev/null +++ b/go.mod @@ -0,0 +1,83 @@ +module github.com/riskified/dynamic-environment + +go 1.19 + +require ( + github.com/briandowns/spinner v1.23.0 + github.com/go-logr/logr v1.2.3 + github.com/mitchellh/hashstructure/v2 v2.0.2 + github.com/onsi/ginkgo/v2 v2.6.0 + github.com/onsi/gomega v1.24.1 + github.com/prometheus/client_golang v1.14.0 + github.com/spf13/cobra v1.6.0 + github.com/stretchr/testify v1.8.0 + istio.io/api v0.0.0-20230217221049-9d422bf48675 + istio.io/client-go v1.17.1 + k8s.io/api v0.26.2 + k8s.io/apimachinery v0.26.2 + k8s.io/client-go v0.26.2 + sigs.k8s.io/controller-runtime v0.14.5 + sigs.k8s.io/yaml v1.3.0 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/emicklei/go-restful/v3 v3.9.0 // indirect + github.com/evanphx/json-patch/v5 v5.6.0 // indirect + github.com/fatih/color v1.7.0 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-logr/zapr v1.2.3 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/swag v0.19.14 // 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.2 // indirect + github.com/google/gnostic v0.5.7-v3refs // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/gofuzz v1.1.0 // indirect + github.com/google/uuid v1.1.2 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.6 // indirect + github.com/mattn/go-colorable v0.1.2 // indirect + github.com/mattn/go-isatty v0.0.8 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.37.0 // indirect + github.com/prometheus/procfs v0.8.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/objx v0.4.0 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + go.uber.org/zap v1.24.0 // indirect + golang.org/x/net v0.7.0 // indirect + golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/term v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect + golang.org/x/time v0.3.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20221018160656-63c7b68cfc55 // indirect + google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiextensions-apiserver v0.26.2 // indirect + k8s.io/component-base v0.26.2 // indirect + k8s.io/klog/v2 v2.80.1 // indirect + k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect + k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect + sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..dcc0cdf --- /dev/null +++ b/go.sum @@ -0,0 +1,642 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +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/briandowns/spinner v1.23.0 h1:alDF2guRWqa/FOZZYWjlMIx2L6H0wyewPxo/CH4Pt2A= +github.com/briandowns/spinner v1.23.0/go.mod h1:rPG4gmXeN3wQV/TsAY4w8lPdIM6RX3yqeBQJSrbXjuE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +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/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= +github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= +github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +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-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= +github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +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-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +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/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +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.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +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.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= +github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= +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.4.1/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.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +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/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +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/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/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.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +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/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM= +github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= +github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= +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/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/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/onsi/ginkgo/v2 v2.6.0 h1:9t9b9vRUbFq3C4qKFCGkVuq/fIHji802N1nrtkh1mNc= +github.com/onsi/ginkgo/v2 v2.6.0/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= +github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= +github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/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/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.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +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.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +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.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI= +github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +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/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +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 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +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 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +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/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-20190108225652-1e06a53dbb7e/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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/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-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/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-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +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-20190227155943-e225da77a7e6/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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +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.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= +gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +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.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +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-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20221018160656-63c7b68cfc55 h1:U1u4KB2kx6KR/aJDjQ97hZ15wQs8ZPvDcGcRynBhkvg= +google.golang.org/genproto v0.0.0-20221018160656-63c7b68cfc55/go.mod h1:45EK0dUbEZ2NHjCeAd2LXmyjAgGUGrpGROgjhC3ADck= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +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.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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +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.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/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-20210107192922-496545a6307b/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= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +istio.io/api v0.0.0-20230217221049-9d422bf48675 h1:dxHqYbJwurfq+x2OOG4WP+NkbyjURgcP9PQTsxh7HXM= +istio.io/api v0.0.0-20230217221049-9d422bf48675/go.mod h1:owGDRg9uqMob8CN1gxaOzk6nJxnbT8wrP7PmggpJHHY= +istio.io/client-go v1.17.1 h1:W0kQXYCzIluA/20zLzxeNF7bNMJXXArmGYRt/MIg2io= +istio.io/client-go v1.17.1/go.mod h1:mLTRYYFxHctzUbt8Iclgj+Sueq34+qC2ZEJTn6BxRuE= +k8s.io/api v0.26.2 h1:dM3cinp3PGB6asOySalOZxEG4CZ0IAdJsrYZXE/ovGQ= +k8s.io/api v0.26.2/go.mod h1:1kjMQsFE+QHPfskEcVNgL3+Hp88B80uj0QtSOlj8itU= +k8s.io/apiextensions-apiserver v0.26.2 h1:/yTG2B9jGY2Q70iGskMf41qTLhL9XeNN2KhI0uDgwko= +k8s.io/apiextensions-apiserver v0.26.2/go.mod h1:Y7UPgch8nph8mGCuVk0SK83LnS8Esf3n6fUBgew8SH8= +k8s.io/apimachinery v0.26.2 h1:da1u3D5wfR5u2RpLhE/ZtZS2P7QvDgLZTi9wrNZl/tQ= +k8s.io/apimachinery v0.26.2/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= +k8s.io/client-go v0.26.2 h1:s1WkVujHX3kTp4Zn4yGNFK+dlDXy1bAAkIl+cFAiuYI= +k8s.io/client-go v0.26.2/go.mod h1:u5EjOuSyBa09yqqyY7m3abZeovO/7D/WehVVlZ2qcqU= +k8s.io/component-base v0.26.2 h1:IfWgCGUDzrD6wLLgXEstJKYZKAFS2kO+rBRi0p3LqcI= +k8s.io/component-base v0.26.2/go.mod h1:DxbuIe9M3IZPRxPIzhch2m1eT7uFrSBJUBuVCQEBivs= +k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= +k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= +k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= +k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 h1:KTgPnR10d5zhztWptI952TNtt/4u5h3IzDXkdIMuo2Y= +k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/controller-runtime v0.14.5 h1:6xaWFqzT5KuAQ9ufgUaj1G/+C4Y1GRkhrxl+BJ9i+5s= +sigs.k8s.io/controller-runtime v0.14.5/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt new file mode 100644 index 0000000..45dbbbb --- /dev/null +++ b/hack/boilerplate.go.txt @@ -0,0 +1,15 @@ +/* +Copyright 2021. + +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/helm/dynamic-environment/.helmignore b/helm/dynamic-environment/.helmignore new file mode 100644 index 0000000..fbe01f8 --- /dev/null +++ b/helm/dynamic-environment/.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/ \ No newline at end of file diff --git a/helm/dynamic-environment/Chart.yaml b/helm/dynamic-environment/Chart.yaml new file mode 100644 index 0000000..6167592 --- /dev/null +++ b/helm/dynamic-environment/Chart.yaml @@ -0,0 +1,18 @@ +apiVersion: v2 +name: dynamic-environment-operator +description: Dynamic environment operator for Kubernetes +version: 1.0.0 +appVersion: v1.0.0 +home: https://github.com/Riskified/dynamic-environment +sources: + - https://github.com/Riskified/dynamic-environment +maintainers: + - name: nisan270390 + url: https://github.com/nisan270390 + email: nisan.itzhakov@riskified.com + - name: babysnakes + url: https://github.com/babysnakes + email: haim.ashkenazi@riskified.com +keywords: + - k8s + - dynamic environment \ No newline at end of file diff --git a/helm/dynamic-environment/templates/_helpers.tpl b/helm/dynamic-environment/templates/_helpers.tpl new file mode 100644 index 0000000..7bb5464 --- /dev/null +++ b/helm/dynamic-environment/templates/_helpers.tpl @@ -0,0 +1,113 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Expand the name of the chart. +*/}} +{{- define "dynamic-environment-operator.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). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "dynamic-environment-operator.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 -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "dynamic-environment-operator.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Chart name prefix for resource names +Strip the "-operator" suffix from the default .Chart.Name if the nameOverride is not specified. +This enables using a shorter name for the resources, for example dynamic-environment-webhook. +*/}} +{{- define "dynamic-environment-operator.namePrefix" -}} +{{- $defaultNamePrefix := .Chart.Name | trimSuffix "-operator" -}} +{{- default $defaultNamePrefix .Values.nameOverride | trunc 42 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create the name of the webhook service +*/}} +{{- define "dynamic-environment-operator.webhookService" -}} +{{- printf "%s-webhook-service" (include "dynamic-environment-operator.namePrefix" .) -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "dynamic-environment-operator.labels" -}} +helm.sh/chart: {{ include "dynamic-environment-operator.chart" . }} +{{ include "dynamic-environment-operator.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Selector labels +*/}} +{{- define "dynamic-environment-operator.selectorLabels" -}} +app.kubernetes.io/name: {{ include "dynamic-environment-operator.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} + +{{/* +Create the name of the service account to use +*/}} +{{- define "dynamic-environment-operator.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} + {{ default (include "dynamic-environment-operator.fullname" .) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Create the name of the webhook cert secret +*/}} +{{- define "dynamic-environment-operator.webhookCertSecret" -}} +{{- printf "%s-tls" (include "dynamic-environment-operator.namePrefix" .) -}} +{{- end -}} + +{{/* +Generate certificates for webhook +*/}} +{{- define "dynamic-environment-operator.webhookCerts" -}} +{{- $serviceName := (include "dynamic-environment-operator.webhookService" .) -}} +{{- $secretName := (include "dynamic-environment-operator.webhookCertSecret" .) -}} +{{- $secret := lookup "v1" "Secret" .Release.Namespace $secretName -}} +{{- if (and .Values.webhookTLS.caCert .Values.webhookTLS.cert .Values.webhookTLS.key) -}} +caCert: {{ .Values.webhookTLS.caCert | b64enc }} +clientCert: {{ .Values.webhookTLS.cert | b64enc }} +clientKey: {{ .Values.webhookTLS.key | b64enc }} +{{- else if and .Values.keepTLSSecret $secret -}} +caCert: {{ index $secret.data "ca.crt" }} +clientCert: {{ index $secret.data "tls.crt" }} +clientKey: {{ index $secret.data "tls.key" }} +{{- else -}} +{{- $altNames := list (printf "%s.%s" $serviceName .Release.Namespace) (printf "%s.%s.svc" $serviceName .Release.Namespace) (printf "%s.%s.svc.cluster.local" $serviceName .Release.Namespace) -}} +{{- $ca := genCA "dynamic-environment-operator-ca" 3650 -}} +{{- $cert := genSignedCert (include "dynamic-environment-operator.fullname" .) nil $altNames 3650 $ca -}} +caCert: {{ $ca.Cert | b64enc }} +clientCert: {{ $cert.Cert | b64enc }} +clientKey: {{ $cert.Key | b64enc }} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/helm/dynamic-environment/templates/crd.yaml b/helm/dynamic-environment/templates/crd.yaml new file mode 100644 index 0000000..b3d7a8a --- /dev/null +++ b/helm/dynamic-environment/templates/crd.yaml @@ -0,0 +1,779 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + controller-gen.kubebuilder.io/version: v0.9.2 + name: dynamicenvs.riskified.com +spec: + group: riskified.com + names: + kind: DynamicEnv + listKind: DynamicEnvList + plural: dynamicenvs + shortNames: + - de + singular: dynamicenv + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Status of the DynamicEnv + jsonPath: .status.state + name: Status + type: string + - description: displays desired subsets and consumers count + jsonPath: .status.totalCount + name: Desired + type: integer + - description: displays how many subsets and consumers are available + jsonPath: .status.totalReady + name: Current + type: integer + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: DynamicEnv is the Schema for the dynamicenvs 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: DynamicEnvSpec defines the desired state of DynamicEnv + properties: + consumers: + description: Consumers are like subsets but for deployments that do not open a service but connect to external resources for their work (e.g, offline workers). They are equivalent to subsets in the sense that they launch overriding deployments with custom image and/or settings. However, since they are only consumers no virtual service or destination route will be pointing to them. + items: + description: Subsets defines how to generate subsets from existing Deployments + properties: + containers: + description: A list of container overrides (at least one of Containers or InitContainers must not be empty) + items: + description: Defines the details of the container on which changes need to be made and the relevant overrides + properties: + command: + description: Entrypoint array overridden to the desired subset The docker image's ENTRYPOINT is used if this is not provided. + items: + type: string + type: array + containerName: + description: Container name to override in multiple containers' environment. If not specified we will use the first container. + type: string + env: + description: Additional environment variable to the given deployment + items: + description: EnvVar represents an environment variable present in a Container. + properties: + name: + description: Name of the environment variable. Must be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded using the previously defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['''']`, `metadata.annotations['''']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in the pod's namespace + properties: + key: + description: The key of the secret to select from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + image: + description: Docker image name overridden to the desired subset The Docker image found in the original deployment is used if this is not provided. + type: string + required: + - containerName + type: object + type: array + defaultVersion: + description: Default version for this subset (if different then the global default version). This is the version that will get the default route. + type: string + initContainers: + description: A list of init container overrides (at least one of Containers or InitContainers must not be empty) + items: + description: Defines the details of the container on which changes need to be made and the relevant overrides + properties: + command: + description: Entrypoint array overridden to the desired subset The docker image's ENTRYPOINT is used if this is not provided. + items: + type: string + type: array + containerName: + description: Container name to override in multiple containers' environment. If not specified we will use the first container. + type: string + env: + description: Additional environment variable to the given deployment + items: + description: EnvVar represents an environment variable present in a Container. + properties: + name: + description: Name of the environment variable. Must be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded using the previously defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['''']`, `metadata.annotations['''']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in the pod's namespace + properties: + key: + description: The key of the secret to select from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + image: + description: Docker image name overridden to the desired subset The Docker image found in the original deployment is used if this is not provided. + type: string + required: + - containerName + type: object + type: array + name: + description: Deployment name (without namespace) + type: string + namespace: + description: Namespace where the deployment is deployed + type: string + podLabels: + additionalProperties: + type: string + description: Labels to add to the pods of the deployment launched by this subset. Could be used in conjunction with 'SourceLabels' in the `IstioMatches`. + type: object + replicas: + description: 'Number of deployment replicas. Default is 1. Note: 0 is *invalid*.' + format: int32 + type: integer + required: + - name + - namespace + type: object + type: array + istioMatches: + description: A list of matchers (partly corresponds to IstioMatch). Each match will have a rule of its own (merged with existing rules) ordered by their order here. + items: + description: specifies a set of criterion to be met in order for the rule to be applied to the HTTP request This field is immutable after creation. + properties: + headers: + additionalProperties: + description: Describes how to match a given string in HTTP headers. Match is case-sensitive. one and only one of the fields needs to be defined (oneof) + properties: + exact: + type: string + prefix: + type: string + regex: + type: string + type: object + description: 'Header values are case-sensitive and formatted as follows:
- `exact: "value"` for exact string match
- `prefix: "value"` for prefix-based match
- `regex: "value"` for RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax).' + type: object + sourceLabels: + additionalProperties: + type: string + description: One or more labels that constrain the applicability of a rule to source (client) workloads with the given labels. + type: object + type: object + type: array + subsets: + description: Who should participate in the given dynamic environment + items: + description: Subsets defines how to generate subsets from existing Deployments + properties: + containers: + description: A list of container overrides (at least one of Containers or InitContainers must not be empty) + items: + description: Defines the details of the container on which changes need to be made and the relevant overrides + properties: + command: + description: Entrypoint array overridden to the desired subset The docker image's ENTRYPOINT is used if this is not provided. + items: + type: string + type: array + containerName: + description: Container name to override in multiple containers' environment. If not specified we will use the first container. + type: string + env: + description: Additional environment variable to the given deployment + items: + description: EnvVar represents an environment variable present in a Container. + properties: + name: + description: Name of the environment variable. Must be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded using the previously defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['''']`, `metadata.annotations['''']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in the pod's namespace + properties: + key: + description: The key of the secret to select from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + image: + description: Docker image name overridden to the desired subset The Docker image found in the original deployment is used if this is not provided. + type: string + required: + - containerName + type: object + type: array + defaultVersion: + description: Default version for this subset (if different then the global default version). This is the version that will get the default route. + type: string + initContainers: + description: A list of init container overrides (at least one of Containers or InitContainers must not be empty) + items: + description: Defines the details of the container on which changes need to be made and the relevant overrides + properties: + command: + description: Entrypoint array overridden to the desired subset The docker image's ENTRYPOINT is used if this is not provided. + items: + type: string + type: array + containerName: + description: Container name to override in multiple containers' environment. If not specified we will use the first container. + type: string + env: + description: Additional environment variable to the given deployment + items: + description: EnvVar represents an environment variable present in a Container. + properties: + name: + description: Name of the environment variable. Must be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded using the previously defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['''']`, `metadata.annotations['''']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in the pod's namespace + properties: + key: + description: The key of the secret to select from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + image: + description: Docker image name overridden to the desired subset The Docker image found in the original deployment is used if this is not provided. + type: string + required: + - containerName + type: object + type: array + name: + description: Deployment name (without namespace) + type: string + namespace: + description: Namespace where the deployment is deployed + type: string + podLabels: + additionalProperties: + type: string + description: Labels to add to the pods of the deployment launched by this subset. Could be used in conjunction with 'SourceLabels' in the `IstioMatches`. + type: object + replicas: + description: 'Number of deployment replicas. Default is 1. Note: 0 is *invalid*.' + format: int32 + type: integer + required: + - name + - namespace + type: object + type: array + required: + - istioMatches + - subsets + type: object + status: + description: DynamicEnvStatus defines the observed state of DynamicEnv + properties: + conditions: + description: Represents the latest available observations of a deployment's current state. + items: + description: Condition describes the state of a DynamicEnv at a certain point. + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status to another. + format: date-time + type: string + lastUpdateTime: + description: The last time this condition was updated. + format: date-time + type: string + message: + description: A human-readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition + type: string + required: + - status + - type + type: object + type: array + consumersStatus: + additionalProperties: + properties: + errors: + description: List of errors related to the consumer + items: + description: StatusError shows an error we want to display in the status with the last time it happened. This *does not* have to be the only time it happened. The idea is that a list of errors should only contain single occurrence of an error (just the last). + properties: + error: + description: The error message + type: string + lastOccurrence: + description: THe last occurrence of the error + format: date-time + type: string + required: + - error + - lastOccurrence + type: object + type: array + hash: + description: Hash of the current consumer - for internal use + format: int64 + type: integer + name: + description: The name of the resource + type: string + namespace: + description: The namespace where the resource is created + type: string + status: + description: The life cycle status of the resource + type: string + required: + - name + - namespace + - status + type: object + type: object + state: + type: string + subsetsStatus: + additionalProperties: + description: SubsetStatus Contains aggregation of all resources status connected to set subset. + properties: + deployment: + description: Status of the deployment that belongs to the subset + properties: + name: + description: The name of the resource + type: string + namespace: + description: The namespace where the resource is created + type: string + status: + description: The life cycle status of the resource + type: string + required: + - name + - namespace + - status + type: object + destinationRules: + description: Status of the destination-rule that belongs to the subset + items: + description: ResourceStatus shows the status of each item created/edited by DynamicEnv + properties: + name: + description: The name of the resource + type: string + namespace: + description: The namespace where the resource is created + type: string + status: + description: The life cycle status of the resource + type: string + required: + - name + - namespace + - status + type: object + type: array + hash: + description: Hash of the current subset - for internal use + format: int64 + type: integer + subsetErrors: + description: A list of global errors related to subset resources + properties: + deployment: + description: Subset's deployment global errors. + items: + description: StatusError shows an error we want to display in the status with the last time it happened. This *does not* have to be the only time it happened. The idea is that a list of errors should only contain single occurrence of an error (just the last). + properties: + error: + description: The error message + type: string + lastOccurrence: + description: THe last occurrence of the error + format: date-time + type: string + required: + - error + - lastOccurrence + type: object + type: array + destinationRule: + description: Subset's destination-rule global errors. + items: + description: StatusError shows an error we want to display in the status with the last time it happened. This *does not* have to be the only time it happened. The idea is that a list of errors should only contain single occurrence of an error (just the last). + properties: + error: + description: The error message + type: string + lastOccurrence: + description: THe last occurrence of the error + format: date-time + type: string + required: + - error + - lastOccurrence + type: object + type: array + subset: + description: Errors related to subset but not to any of the launched resources + items: + description: StatusError shows an error we want to display in the status with the last time it happened. This *does not* have to be the only time it happened. The idea is that a list of errors should only contain single occurrence of an error (just the last). + properties: + error: + description: The error message + type: string + lastOccurrence: + description: THe last occurrence of the error + format: date-time + type: string + required: + - error + - lastOccurrence + type: object + type: array + virtualServices: + description: Subset's virtual-services global errors. + items: + description: StatusError shows an error we want to display in the status with the last time it happened. This *does not* have to be the only time it happened. The idea is that a list of errors should only contain single occurrence of an error (just the last). + properties: + error: + description: The error message + type: string + lastOccurrence: + description: THe last occurrence of the error + format: date-time + type: string + required: + - error + - lastOccurrence + type: object + type: array + type: object + virtualServices: + description: Status of the virtual-service that belongs to the subset + items: + description: ResourceStatus shows the status of each item created/edited by DynamicEnv + properties: + name: + description: The name of the resource + type: string + namespace: + description: The namespace where the resource is created + type: string + status: + description: The life cycle status of the resource + type: string + required: + - name + - namespace + - status + type: object + type: array + type: object + type: object + totalCount: + description: desired subsets and consumers count + type: integer + totalReady: + description: number of available subsets and consumers + type: integer + required: + - subsetsStatus + - totalReady + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/dynamic-environment/templates/deployment.yaml b/helm/dynamic-environment/templates/deployment.yaml new file mode 100644 index 0000000..4e46c99 --- /dev/null +++ b/helm/dynamic-environment/templates/deployment.yaml @@ -0,0 +1,119 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "dynamic-environment-operator.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "dynamic-environment-operator.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "dynamic-environment-operator.selectorLabels" . | nindent 6 }} + {{- with .Values.updateStrategy }} + strategy: + {{- toYaml . | nindent 4 }} + {{- end }} + template: + metadata: + labels: + {{- include "dynamic-environment-operator.selectorLabels" . | nindent 8 }} + {{- if .Values.podLabels }} + {{- toYaml .Values.podLabels | nindent 8 }} + {{- end }} + annotations: + {{- if .Values.podAnnotations }} + {{- toYaml .Values.podAnnotations | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "dynamic-environment-operator.serviceAccountName" . }} + volumes: + - name: cert + secret: + defaultMode: 420 + secretName: {{ template "dynamic-environment-operator.webhookCertSecret" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + {{- if .Values.dnsPolicy }} + dnsPolicy: {{ .Values.dnsPolicy }} + {{- end }} + containers: + - args: + - --leader-elect + {{- if .Values.metricsBindAddr }} + - --metrics-bind-addr={{ .Values.metricsBindAddr }} + {{- end }} + {{- if .Values.command.defaultVersionLabel }} + - --version-label + - {{ .Values.command.defaultVersionLabel }} + {{- end }} + {{- if .Values.command.defaultVersion }} + - --default-version + - {{ .Values.command.defaultVersion }} + {{- end }} + {{- $removeLabels := join "," .Values.labelsToRemove }} + {{- if $removeLabels }} + - --remove-labels + - {{ $removeLabels }} + {{- end }} + command: + - /manager + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- with .Values.livenessProbe }} + livenessProbe: + {{- toYaml . | nindent 10 }} + {{- end }} + {{- with .Values.readinessProbe }} + readinessProbe: + {{- toYaml . | nindent 10 }} + {{- end }} + name: manager + ports: + - containerPort: 9443 + name: webhook-server + protocol: TCP + - name: metrics-server + containerPort: {{ (split ":" .Values.metricsBindAddr)._1 | default 8080 }} + protocol: TCP + resources: + {{- toYaml .Values.resources | nindent 10 }} + securityContext: + {{- toYaml .Values.securityContext | nindent 10 }} + volumeMounts: + - mountPath: /tmp/k8s-webhook-server/serving-certs + name: cert + readOnly: true + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.affinity }} + affinity: + {{- toYaml .Values.affinity | nindent 8 }} + {{- else }} + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app.kubernetes.io/name + operator: In + values: + - {{ include "dynamic-environment-operator.name" . }} + topologyKey: kubernetes.io/hostname + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName | quote }} + {{- end }} \ No newline at end of file diff --git a/helm/dynamic-environment/templates/pdb.yaml b/helm/dynamic-environment/templates/pdb.yaml new file mode 100644 index 0000000..b7486b5 --- /dev/null +++ b/helm/dynamic-environment/templates/pdb.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.podDisruptionBudget (gt (int .Values.replicaCount) 1) }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "dynamic-environment-operator.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "dynamic-environment-operator.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "dynamic-environment-operator.selectorLabels" . | nindent 6 }} + {{- toYaml .Values.podDisruptionBudget | nindent 2 }} +{{- end }} \ No newline at end of file diff --git a/helm/dynamic-environment/templates/rbac.yaml b/helm/dynamic-environment/templates/rbac.yaml new file mode 100644 index 0000000..6284f19 --- /dev/null +++ b/helm/dynamic-environment/templates/rbac.yaml @@ -0,0 +1,141 @@ +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ template "dynamic-environment-operator.fullname" . }}-leader-election-role + namespace: {{ .Release.Namespace }} + labels: + {{- include "dynamic-environment-operator.labels" . | nindent 4 }} +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ template "dynamic-environment-operator.fullname" . }}-leader-election-rolebinding + namespace: {{ .Release.Namespace }} + labels: + {{- include "dynamic-environment-operator.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "dynamic-environment-operator.fullname" . }}-leader-election-role +subjects: +- kind: ServiceAccount + name: {{ template "dynamic-environment-operator.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: {{ template "dynamic-environment-operator.fullname" . }}-role + labels: + {{- include "dynamic-environment-operator.labels" . | nindent 4 }} +rules: +- apiGroups: + - apps + resources: + - deployments + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch +- apiGroups: + - networking.istio.io + resources: + - '*' + verbs: + - '*' +- apiGroups: + - riskified.com + resources: + - dynamicenvs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - riskified.com + resources: + - dynamicenvs/finalizers + verbs: + - update +- apiGroups: + - riskified.com + resources: + - dynamicenvs/status + verbs: + - get + - patch + - update +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ template "dynamic-environment-operator.fullname" . }}-rolebinding + labels: + {{- include "dynamic-environment-operator.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "dynamic-environment-operator.fullname" . }}-role +subjects: +- kind: ServiceAccount + name: {{ template "dynamic-environment-operator.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/helm/dynamic-environment/templates/service.yaml b/helm/dynamic-environment/templates/service.yaml new file mode 100644 index 0000000..9a0abc4 --- /dev/null +++ b/helm/dynamic-environment/templates/service.yaml @@ -0,0 +1,37 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "dynamic-environment-operator.webhookService" . }} + namespace: {{ .Release.Namespace }} + {{- with .Values.serviceAnnotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "dynamic-environment-operator.labels" . | nindent 4 }} +spec: + ports: + - port: 443 + protocol: TCP + targetPort: 9443 + selector: + {{- include "dynamic-environment-operator.selectorLabels" . | nindent 4 }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ template "dynamic-environment-operator.fullname" . }} + namespace: {{ .Release.Namespace }} + {{- with .Values.serviceAnnotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "dynamic-environment-operator.labels" . | nindent 4 }} +spec: + ports: + - port: 8080 + name: metrics-server + targetPort: metrics-server + selector: + {{- include "dynamic-environment-operator.selectorLabels" . | nindent 4 }} \ No newline at end of file diff --git a/helm/dynamic-environment/templates/serviceaccount.yaml b/helm/dynamic-environment/templates/serviceaccount.yaml new file mode 100644 index 0000000..9c8bf5d --- /dev/null +++ b/helm/dynamic-environment/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "dynamic-environment-operator.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "dynamic-environment-operator.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end -}} \ No newline at end of file diff --git a/helm/dynamic-environment/templates/servicemonitor.yaml b/helm/dynamic-environment/templates/servicemonitor.yaml new file mode 100644 index 0000000..e3659fa --- /dev/null +++ b/helm/dynamic-environment/templates/servicemonitor.yaml @@ -0,0 +1,20 @@ +--- +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + labels: + prometheus: kube-prometheus + {{- include "dynamic-environment-operator.selectorLabels" . | nindent 4 }} + name: {{ include "dynamic-environment-operator.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + endpoints: + - path: /metrics + port: metrics-server + interval: 60s + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + {{- include "dynamic-environment-operator.selectorLabels" . | nindent 6 }} \ No newline at end of file diff --git a/helm/dynamic-environment/templates/webhook.yaml b/helm/dynamic-environment/templates/webhook.yaml new file mode 100644 index 0000000..3fe61ef --- /dev/null +++ b/helm/dynamic-environment/templates/webhook.yaml @@ -0,0 +1,74 @@ +{{ $tls := fromYaml ( include "dynamic-environment-operator.webhookCerts" . ) }} +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: +{{- if $.Values.enableCertManager }} + annotations: + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ template "dynamic-environment-operator.namePrefix" . }}-serving-cert +{{- end }} + name: {{ include "dynamic-environment-operator.namePrefix" . }}-webhook +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + caBundle: {{ if not $.Values.enableCertManager -}}{{ $tls.caCert }}{{- else -}}Cg=={{ end }} + service: + name: {{ template "dynamic-environment-operator.webhookService" . }} + namespace: {{ $.Release.Namespace }} + path: /validate-riskified-com-v1alpha1-dynamicenv + failurePolicy: Fail + name: vdynamicenv.kb.io + rules: + - apiGroups: + - riskified.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - dynamicenvs + sideEffects: None +--- +{{- if not $.Values.enableCertManager }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "dynamic-environment-operator.webhookCertSecret" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "dynamic-environment-operator.labels" . | indent 4 }} +type: kubernetes.io/tls +data: + ca.crt: {{ $tls.caCert }} + tls.crt: {{ $tls.clientCert }} + tls.key: {{ $tls.clientKey }} +{{- else }} +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ template "dynamic-environment-operator.namePrefix" . }}-serving-cert + namespace: {{ .Release.Namespace }} + labels: +{{ include "dynamic-environment-operator.labels" . | indent 4 }} +spec: + dnsNames: + - {{ template "dynamic-environment-operator.webhookService" . }}.{{ .Release.Namespace }} + - {{ template "dynamic-environment-operator.webhookService" . }}.{{ .Release.Namespace }}.svc + - {{ template "dynamic-environment-operator.webhookService" . }}.{{ .Release.Namespace }}.svc.cluster.local + issuerRef: + kind: Issuer + name: {{ template "dynamic-environment-operator.namePrefix" . }}-selfsigned-issuer + secretName: {{ template "dynamic-environment-operator.webhookCertSecret" . }} +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: {{ template "dynamic-environment-operator.namePrefix" . }}-selfsigned-issuer + namespace: {{ .Release.Namespace }} + labels: +{{ include "dynamic-environment-operator.labels" . | indent 4 }} +spec: + selfSigned: {} +{{- end }} \ No newline at end of file diff --git a/helm/dynamic-environment/values.yaml b/helm/dynamic-environment/values.yaml new file mode 100644 index 0000000..1310a67 --- /dev/null +++ b/helm/dynamic-environment/values.yaml @@ -0,0 +1,116 @@ +# Default values for dynamic-environment-operator. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +nameOverride: "" +fullnameOverride: "" + +replicaCount: 1 + +updateStrategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + +# podDisruptionBudget specifies the disruption budget for the controller pods. +# Disruption budget will be configured only when the replicaCount is greater than 1 +podDisruptionBudget: {} +# maxUnavailable: 1 + +image: + repository: localhost:32000/controller + tag: latest + pullPolicy: IfNotPresent + +imagePullSecrets: [] +podLabels: {} +podAnnotations: {} + +# Invocation overrides +command: + # Specify a version label other than the default 'version' (to identify DestinationRule version) + defaultVersionLabel: "version" + # Specify a default version other than the default 'shared'. This version will be used when searching default + # DestinationRule. Could be specified per subset in the CR. + defaultVersion: "shared" + +# Labels to be deleted form deployments when duplicating (e.g. labels that connect deployment to argocd app): +labelsToRemove: [] + +rbac: + # Specifies whether rbac resources should be created + create: true + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: + +podSecurityContext: + runAsNonRoot: true + +securityContext: + allowPrivilegeEscalation: false + +# Specifies the dnsPolicy that should be used for pods in the deployment +# +# This may need to be used to be changed given certain conditions. For instance, if one uses the cilium CNI +# with certain settings, one may need to set `hostNetwork: true` and webhooks won't work unless `dnsPolicy` +# is set to `ClusterFirstWithHostNet`. See https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy +dnsPolicy: ClusterFirst + +# Liveness probe configuration for the controller +livenessProbe: + failureThreshold: 2 + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + timeoutSeconds: 20 + +# Readiness probe configuration for the controller +readinessProbe: + failureThreshold: 1 + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + timeoutSeconds: 10 + +resources: + limits: + cpu: 200m + memory: 100Mi + requests: + cpu: 100m + memory: 20Mi + +# Time period for the controller pod to do a graceful shutdown +terminationGracePeriodSeconds: 10 + +# The address the metric endpoint binds to. (default ":8080") +metricsBindAddr: "" + +# Enable cert-manager +enableCertManager: false + +# webhookTLS specifies TLS cert/key for the webhook +webhookTLS: + caCert: + cert: + key: + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +# priorityClassName specifies the PriorityClass to indicate the importance of controller pods +# ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#priorityclass +priorityClassName: "" \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..bea0caf --- /dev/null +++ b/main.go @@ -0,0 +1,162 @@ +/* +Copyright 2021. + +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 ( + "flag" + "fmt" + "github.com/riskified/dynamic-environment/pkg/metrics" + "github.com/riskified/dynamic-environment/pkg/names" + "os" + "strings" + + // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) + // to ensure that exec-entrypoint and run can make use of them. + istionetwork "istio.io/client-go/pkg/apis/networking/v1alpha3" + _ "k8s.io/client-go/plugin/pkg/client/auth" + + "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" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + riskifiedv1alpha1 "github.com/riskified/dynamic-environment/api/v1alpha1" + "github.com/riskified/dynamic-environment/controllers" + // "istio.io/client-go/pkg/apis/networking/v1alpha3" + //+kubebuilder:scaffold:imports +) + +var ( + scheme = runtime.NewScheme() + setupLog = ctrl.Log.WithName("setup") +) + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + utilruntime.Must(riskifiedv1alpha1.AddToScheme(scheme)) + utilruntime.Must(istionetwork.AddToScheme(scheme)) + + // if err := v1alpha3.SchemeBuilder.AddToScheme(scheme); err != nil { + // //log.Error(err, "") + // os.Exit(1) + // } + + //+kubebuilder:scaffold:scheme +} + +// A flag parser for list of strings +type arrayFlags []string + +func (i *arrayFlags) String() string { + if i == nil { + return "" + } + return strings.Join(*i, ",") +} + +func (i *arrayFlags) Set(value string) error { + values := strings.Split(value, ",") + for _, s := range values { + if s == "" { + return fmt.Errorf("comma-separated list contains empty value") + } + } + *i = append(*i, values...) + return nil +} + +func main() { + var metricsAddr string + var enableLeaderElection bool + var probeAddr string + var versionLabel string + var defaultVersion string + var labelsToRemove arrayFlags + 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.") + flag.BoolVar(&enableLeaderElection, "leader-elect", false, + "Enable leader election for controller manager. "+ + "Enabling this will ensure there is only one active controller manager.") + flag.StringVar(&versionLabel, "version-label", names.DefaultVersionLabel, + "This is the name of the label used to differentiate versions (default vs overriding).") + flag.StringVar(&defaultVersion, "default-version", names.DefaultVersion, + "The global default version - this version is the one that gets the default route. Could be overridden per subset.") + flag.Var(&labelsToRemove, "remove-labels", "A comma separated list of labels to remove when duplicating deployment.") + opts := zap.Options{ + Development: true, + } + opts.BindFlags(flag.CommandLine) + flag.Parse() + + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: scheme, + MetricsBindAddress: metricsAddr, + Port: 9443, + HealthProbeBindAddress: probeAddr, + LeaderElection: enableLeaderElection, + LeaderElectionID: "dynamic-environment-lock", + }) + if err != nil { + setupLog.Error(err, "unable to start manager") + os.Exit(1) + } + + if err = (&controllers.DynamicEnvReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + VersionLabel: versionLabel, + DefaultVersion: defaultVersion, + LabelsToRemove: labelsToRemove, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "DynamicEnv") + os.Exit(1) + } + // prevents the webhooks from being started when the ENABLE_WEBHOOKS flag is set to false (for + // running locally) + if os.Getenv("ENABLE_WEBHOOKS") != "false" { + if err = (&riskifiedv1alpha1.DynamicEnv{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "DynamicEnv") + os.Exit(1) + } + } + // metrics + if err = (&metrics.DynamicEnvCollector{ + Client: mgr.GetClient(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "Unable to add metrics collector") + } + //+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) + } +} diff --git a/pkg/handlers/common_utils_private_test.go b/pkg/handlers/common_utils_private_test.go new file mode 100644 index 0000000..9baa0b3 --- /dev/null +++ b/pkg/handlers/common_utils_private_test.go @@ -0,0 +1,23 @@ +package handlers + +import ( + "context" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// A hack for implementing a k8s client inside a test function. +// see: https://stackoverflow.com/questions/31362044/anonymous-interface-implementation-in-golang +type MockClient struct { + client.Client + getMethod func(context.Context, types.NamespacedName, client.Object, ...client.GetOption) error + listMethod func(context.Context, client.ObjectList, ...client.ListOption) error +} + +func (m MockClient) Get(c context.Context, ns types.NamespacedName, o client.Object, _ ...client.GetOption) error { + return m.getMethod(c, ns, o) +} + +func (m MockClient) List(c context.Context, l client.ObjectList, o ...client.ListOption) error { + return m.listMethod(c, l, o...) +} diff --git a/pkg/handlers/deployment_handler.go b/pkg/handlers/deployment_handler.go new file mode 100644 index 0000000..1a6bcb3 --- /dev/null +++ b/pkg/handlers/deployment_handler.go @@ -0,0 +1,297 @@ +package handlers + +import ( + "context" + "fmt" + + "github.com/riskified/dynamic-environment/extensions" + + "github.com/go-logr/logr" + "github.com/mitchellh/hashstructure/v2" + riskifiedv1alpha1 "github.com/riskified/dynamic-environment/api/v1alpha1" + "github.com/riskified/dynamic-environment/pkg/helpers" + "github.com/riskified/dynamic-environment/pkg/names" + "github.com/riskified/dynamic-environment/pkg/watches" + appsv1 "k8s.io/api/apps/v1" + v1 "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" +) + +// A handler for managing Deployments manipulations. +type DeploymentHandler struct { + client.Client + // The unique name of the deployment we need to handle + UniqueName string + // The unique version of the deployment we need to handle + UniqueVersion string + // THe owner of the deployment we need to handle (e.g. to configure watches) + Owner types.NamespacedName + // The deployment we should use as base + BaseDeployment *appsv1.Deployment + // Is it a consumer or subset + DeploymentType riskifiedv1alpha1.SubsetOrConsumer + // A list of labels to be removed from the overriding deployment + LabelsToRemove []string + // The version label to use + VersionLabel string + // Status handler (to be able to update status) + StatusHandler *DynamicEnvStatusHandler + // The DynamicEnv matchers + Matches []riskifiedv1alpha1.IstioMatch + // An indicator whether the current deployment is updating (as opposed to initializing). + Updating bool + // The subset that contains the data of our deployment + Subset riskifiedv1alpha1.Subset + Log logr.Logger + Ctx context.Context +} + +// Handles creation and manipulation of related Deployments. +func (h *DeploymentHandler) Handle() error { + subset := h.Subset + sDeployment := &appsv1.Deployment{} + searchName := types.NamespacedName{Name: h.UniqueName, Namespace: subset.Namespace} + err := h.Get(h.Ctx, searchName, sDeployment) + if err != nil && errors.IsNotFound(err) { + if err := h.setStatus(riskifiedv1alpha1.Initializing); err != nil { + return fmt.Errorf("failed to update status (prior to adding deployment: %s): %w", h.UniqueName, err) + } + sDeployment, err2 := h.createOverridingDeployment() + if err2 != nil { + return err2 + } + h.Log.Info("Deploying newly created overriding deployment", "namespace", subset.Namespace, "name", subset.Name) + err2 = h.Create(h.Ctx, sDeployment) + if err2 != nil { + h.Log.Error(err, "Error deploying", "namespace", subset.Namespace, "name", subset.Name) + return err2 + } + hash, err := hashstructure.Hash(h.Subset, hashstructure.FormatV2, nil) + if err != nil { + return fmt.Errorf("calculating hash for %q: %w", h.UniqueName, err) + } + if err := h.StatusHandler.ApplyHash(h.UniqueName, hash, h.DeploymentType); err != nil { + return fmt.Errorf("setting subset hash on deployment creation %q: %w", h.UniqueName, err) + } + + return nil + } else if err != nil { + h.Log.Error(err, "Failed to get matching deployment for subset") + return err + } + return h.UpdateIfRequired() +} + +func (h *DeploymentHandler) GetStatus() (riskifiedv1alpha1.ResourceStatus, error) { + + genStatus := func(s riskifiedv1alpha1.LifeCycleStatus) riskifiedv1alpha1.ResourceStatus { + return riskifiedv1alpha1.ResourceStatus{ + Name: h.UniqueName, + Namespace: h.Subset.Namespace, + Status: s, + } + } + + deployment := &appsv1.Deployment{} + searchName := types.NamespacedName{Name: h.UniqueName, Namespace: h.Subset.Namespace} + if err := h.Get(h.Ctx, searchName, deployment); err != nil { + if errors.IsNotFound(err) { + return genStatus(riskifiedv1alpha1.Missing), nil + } else { + h.Log.Error(err, "error searching for deployment in getStatus", "name", h.UniqueName, "namespace", h.Subset.Namespace) + e := fmt.Errorf("error searching for deployment: %w", err) + return riskifiedv1alpha1.ResourceStatus{}, e + } + } + // TODO: This fails in testing - try to make it work. + // h.Log.V(1).Info("status found for deployment", "deployment", searchName, "status", deployment.Status) + + found, processing := getMostRecentConditionForType(appsv1.DeploymentProgressing, deployment.Status.Conditions) + if deployment.Status.AvailableReplicas > 0 { + if found && processing.Status == v1.ConditionTrue && processing.Reason != "NewReplicaSetAvailable" { + var status riskifiedv1alpha1.LifeCycleStatus + if h.Updating { + status = riskifiedv1alpha1.Updating + } else { + status = riskifiedv1alpha1.Initializing + } + return genStatus(status), nil + } + return genStatus(riskifiedv1alpha1.Running), nil + } else { + if found && processing.Status == v1.ConditionTrue { + return genStatus(riskifiedv1alpha1.Initializing), nil + } + if found && processing.Status == v1.ConditionFalse { + return genStatus(riskifiedv1alpha1.Failed), nil + } + } + return genStatus(riskifiedv1alpha1.Unknown), nil +} + +func (h *DeploymentHandler) ApplyStatus(rs riskifiedv1alpha1.ResourceStatus) error { + return h.StatusHandler.AddDeploymentStatusEntry(h.UniqueName, rs, h.DeploymentType) +} + +func (h *DeploymentHandler) GetSubset() string { + return h.UniqueName +} + +func (h *DeploymentHandler) UpdateIfRequired() error { + h.Log.V(1).Info("Checking whether it's required to update subset", "subset namespace", h.Subset.Namespace, "subset name", h.Subset.Name) + // Trusting the webhook validator we assume the following: + // - Subset name/namespace can not be changed + // - Container and initContainer overrides must have the same names + s := h.Subset + var existingHash uint64 + if h.DeploymentType == riskifiedv1alpha1.CONSUMER { + existingHash = h.StatusHandler.GetHashForConsumer(h.UniqueName) + } else { + existingHash = h.StatusHandler.GetHashForSubset(h.UniqueName) + } + hash, err := hashstructure.Hash(h.Subset, hashstructure.FormatV2, nil) + if err != nil { + return fmt.Errorf("could not calculate hash of subset '%s/%s': %w", s.Namespace, s.Name, err) + } + if existingHash != hash { + h.Updating = true + h.Log.V(1).Info("Deployment update required", "subset", h.UniqueName) + newDeployment, err := h.createOverridingDeployment() + if err != nil { + return err + } + if err := h.setStatus(riskifiedv1alpha1.Updating); err != nil { + return fmt.Errorf("failed to update status (prior to update deployment: %s): %w", h.UniqueName, err) + } + if err := h.Update(h.Ctx, newDeployment); err != nil { + h.Log.Error(err, "Updating Subset", "subset full name", h.UniqueName) + return fmt.Errorf("error updating subset %s: %w", h.UniqueName, err) + } + if err := h.StatusHandler.ApplyHash(h.UniqueName, hash, h.DeploymentType); err != nil { + return fmt.Errorf("setting subset hash for '%s': %w", h.UniqueName, err) + } + } + return nil +} + +func (h *DeploymentHandler) createOverridingDeployment() (*appsv1.Deployment, error) { + oldMeta := h.BaseDeployment.ObjectMeta.DeepCopy() + newSpec := h.BaseDeployment.Spec.DeepCopy() + oldMeta.Labels[h.VersionLabel] = h.UniqueVersion + oldMeta.Labels[names.DynamicEnvLabel] = "true" + for _, l := range h.LabelsToRemove { + delete(oldMeta.Labels, l) + } + newSpec.Selector.MatchLabels[h.VersionLabel] = h.UniqueVersion + var replicas int32 = 1 + if h.Subset.Replicas != nil { + replicas = *h.Subset.Replicas + } + *newSpec.Replicas = replicas + template := newSpec.Template + template.ObjectMeta.Labels[h.VersionLabel] = h.UniqueVersion + template.ObjectMeta.Labels[names.DynamicEnvLabel] = "true" + for k, v := range h.Subset.PodLabels { + template.ObjectMeta.Labels[k] = v + } + + // Main container overrides + for _, c := range h.Subset.Containers { + container, idx, err := createContainerTemplate(template.Spec.Containers, c) + if err != nil { + newError := fmt.Errorf("processing containers in subset %s: %w", h.Subset.Name, err) + return &appsv1.Deployment{}, newError + } + template.Spec.Containers[idx] = container + } + + // Init container overrides + for _, ic := range h.Subset.InitContainers { + initContainer, idx, err := createContainerTemplate(template.Spec.InitContainers, ic) + if err != nil { + newError := fmt.Errorf("processing init-containers in subset %s: %w", h.Subset.Name, err) + return &appsv1.Deployment{}, newError + } + template.Spec.InitContainers[idx] = initContainer + } + + dep := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: h.UniqueName, + Namespace: h.Subset.Namespace, + Labels: oldMeta.Labels, + }, + Spec: *newSpec, + } + + depData := extensions.DeploymentExtensionData{ + BaseDeployment: h.BaseDeployment, + Matches: h.Matches, + Subset: h.Subset, + } + + if err := extensions.ExtendOverridingDeployment(dep, depData); err != nil { + return dep, err + } + + watches.AddToAnnotation(h.Owner, dep) + return dep, nil +} + +func (h *DeploymentHandler) setStatus(status riskifiedv1alpha1.LifeCycleStatus) error { + currentState := riskifiedv1alpha1.ResourceStatus{ + Name: h.UniqueName, + Namespace: h.Subset.Namespace, + Status: status, + } + if err := h.StatusHandler.AddDeploymentStatusEntry(h.UniqueName, currentState, h.DeploymentType); err != nil { + return err + } + return nil +} + +func createContainerTemplate(containers []v1.Container, containerOverrides riskifiedv1alpha1.ContainerOverrides) (v1.Container, int, error) { + idx, err := findContainerIndex(&containers, containerOverrides.ContainerName) + if err != nil { + return v1.Container{}, 0, err + } + container := containers[idx] + if containerOverrides.Image != "" { + container.Image = containerOverrides.Image + } + if len(containerOverrides.Command) > 0 { + container.Command = containerOverrides.Command + } + container.Env = helpers.MergeEnvVars(container.Env, containerOverrides.Env) + return container, idx, nil +} + +// Returns the container with the specified name. Fails if not found +func findContainerIndex(containers *[]v1.Container, defaultName string) (int, error) { + if len(*containers) < 1 { + // Todo: How do we need to treat this situation? Is it even possible to have empty container list? + //goland:noinspection GoErrorStringFormat + return 0, fmt.Errorf("how did we get empty container list?") + } + + for idx, c := range *containers { + if c.Name == defaultName { + return idx, nil + } + } + return 0, fmt.Errorf("container name %s does'nt exist", defaultName) +} + +func getMostRecentConditionForType(typ appsv1.DeploymentConditionType, conditions []appsv1.DeploymentCondition) (found bool, c appsv1.DeploymentCondition) { + var result appsv1.DeploymentCondition + for _, c := range conditions { + if c.Type == typ { + found = true + result = c + } + } + return found, result +} diff --git a/pkg/handlers/deployment_handler_private_test.go b/pkg/handlers/deployment_handler_private_test.go new file mode 100644 index 0000000..dd9268b --- /dev/null +++ b/pkg/handlers/deployment_handler_private_test.go @@ -0,0 +1,200 @@ +package handlers + +// IMPORTANT: While it's generally bad practice to test private functions, I do it here in order to +// avoid overly specified tests from the outside. If these test fails (e.g. we changed +// implementation) just delete the failing tests (of-course, make sure you test the new +// functionality :) ). + +import ( + "encoding/json" + "fmt" + "github.com/go-logr/logr" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + riskifiedv1alpha1 "github.com/riskified/dynamic-environment/api/v1alpha1" + v1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/types" +) + +var _ = Describe("Accepting alternative version label", func() { + It("Handles custom version label correctly", func() { + uniqueVersion := "unique-version" + versionLabel := "version-label" + deploymentData := `{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "details", + "namespace": "simple-test", + "labels": { + "app": "details", + "version-label": "shared", + "version": "shared" + } + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app": "details", + "version-label": "shared", + "version": "shared" + } + }, + "template": { + "metadata": { + "labels": { + "app": "details", + "version-label": "shared", + "version": "shared" + } + }, + "spec": { + "serviceAccountName": "bookinfo-details", + "containers": [ + { + "name": "details", + "image": "docker.io/istio/examples-bookinfo-details-v1:1.16.2", + "imagePullPolicy": "IfNotPresent", + "ports": [ + { + "containerPort": 9080 + } + ], + "securityContext": { + "runAsUser": 1000 + } + } + ] + } + } + } + }` + deployment := v1.Deployment{} + if err := json.Unmarshal([]byte(deploymentData), &deployment); err != nil { + Fail(fmt.Sprintf("Could not parse deployment json: %s", err)) + } + h := DeploymentHandler{ + Client: nil, + UniqueName: "unique", + UniqueVersion: uniqueVersion, + Owner: types.NamespacedName{}, + BaseDeployment: &deployment, + DeploymentType: 0, + VersionLabel: versionLabel, + StatusHandler: nil, + Matches: nil, + Updating: false, + Subset: riskifiedv1alpha1.Subset{}, + Log: logr.Logger{}, + Ctx: nil, + } + result, err := h.createOverridingDeployment() + Expect(err).To(BeNil()) + Expect(result.ObjectMeta.Labels[versionLabel]).To(Equal(uniqueVersion)) + }) +}) + +var _ = Describe("Removing apecific labels from deployment", func() { + + deploymentData := `{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "details", + "namespace": "simple-test", + "labels": { + "app": "details", + "version-label": "shared", + "version": "shared", + "argocd.argoproj.io/instance": "value", + "another/value-to-remove": "other-value" + } + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app": "details", + "version-label": "shared", + "version": "shared" + } + }, + "template": { + "metadata": { + "labels": { + "app": "details", + "version-label": "shared", + "version": "shared" + } + }, + "spec": { + "serviceAccountName": "bookinfo-details", + "containers": [ + { + "name": "details", + "image": "docker.io/istio/examples-bookinfo-details-v1:1.16.2", + "imagePullPolicy": "IfNotPresent", + "ports": [ + { + "containerPort": 9080 + } + ], + "securityContext": { + "runAsUser": 1000 + } + } + ] + } + } + } + }` + deployment := v1.Deployment{} + if err := json.Unmarshal([]byte(deploymentData), &deployment); err != nil { + Fail(fmt.Sprintf("Could not parse deployment json: %s", err)) + } + + getDefaultHanlder := func() DeploymentHandler { + return DeploymentHandler{ + Client: nil, + UniqueName: "unique", + UniqueVersion: "unique-version", + Owner: types.NamespacedName{}, + BaseDeployment: &deployment, + DeploymentType: 0, + LabelsToRemove: []string{}, + VersionLabel: "version-label", + StatusHandler: nil, + Matches: nil, + Updating: false, + Subset: riskifiedv1alpha1.Subset{}, + Log: logr.Logger{}, + Ctx: nil, + } + } + + Context("With no global labels to remove", func() { + It("duplicates without removing labels", func() { + handler := getDefaultHanlder() + result, err := handler.createOverridingDeployment() + Expect(err).To(BeNil()) + Expect(result.ObjectMeta.Labels).To(HaveKey("argocd.argoproj.io/instance")) + Expect(result.ObjectMeta.Labels).To(HaveKey("another/value-to-remove")) + }) + }) + + Context("With globally configured labels to remove", func() { + It("remove the configured labels", func() { + handler := getDefaultHanlder() + handler.LabelsToRemove = []string{ + "argocd.argoproj.io/instance", + "another/value-to-remove", + "yet-another/label-to-remove", + } + result, err := handler.createOverridingDeployment() + Expect(err).To(BeNil()) + Expect(result.ObjectMeta.Labels).To(Not(HaveKey("argocd.argoproj.io/instance"))) + Expect(result.ObjectMeta.Labels).To(Not(HaveKey("another/value-to-remove"))) + }) + }) +}) diff --git a/pkg/handlers/deployment_handler_test.go b/pkg/handlers/deployment_handler_test.go new file mode 100644 index 0000000..a525972 --- /dev/null +++ b/pkg/handlers/deployment_handler_test.go @@ -0,0 +1,195 @@ +package handlers_test + +import ( + "context" + "fmt" + "github.com/riskified/dynamic-environment/pkg/names" + "io" + "os" + ctrl "sigs.k8s.io/controller-runtime" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + riskifiedv1alpha1 "github.com/riskified/dynamic-environment/api/v1alpha1" + "github.com/riskified/dynamic-environment/pkg/handlers" + appsv1 "k8s.io/api/apps/v1" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" +) + +func mkDynamicEnvFromYamlFile(fileName string) (de riskifiedv1alpha1.DynamicEnv, err error) { + sourceFile, err := os.Open(fileName) + if err != nil { + return de, fmt.Errorf("error opening fixture: %w", err) + } + data, err := io.ReadAll(sourceFile) + if err != nil { + return de, fmt.Errorf("error reading data from file: %w", err) + } + if err := yaml.UnmarshalStrict(data, &de); err != nil { + return de, fmt.Errorf("error strict unmarshaling fixture: %w", err) + } + return de, nil +} + +var _ = Describe("DeploymentHandler", func() { + Context("GetStatus", func() { + + mkExpected := func(h handlers.DeploymentHandler, s riskifiedv1alpha1.LifeCycleStatus) riskifiedv1alpha1.ResourceStatus { + return riskifiedv1alpha1.ResourceStatus{ + Name: h.UniqueName, + Namespace: h.Subset.Namespace, + Status: s, + } + } + + mkDeploymentHandler := func(name, ns string, c client.Client) handlers.DeploymentHandler { + return handlers.DeploymentHandler{ + Client: c, + UniqueName: name, + Subset: riskifiedv1alpha1.Subset{ + Namespace: ns, + }, + VersionLabel: names.DefaultVersionLabel, + } + } + + Context("When Deployment does not exist", func() { + It("Returns missing life cycle status", func() { + mc := struct{ MockClient }{} + mc.getMethod = func(context.Context, types.NamespacedName, client.Object, ...client.GetOption) error { + return errors.NewNotFound(schema.GroupResource{}, "error") + } + handler := mkDeploymentHandler("unique", "my-namespace", mc) + expected := mkExpected(handler, riskifiedv1alpha1.Missing) + result, _ := handler.GetStatus() + Expect(result).To(Equal(expected)) + }) + + }) + + Context("When Deployment is available", func() { + It("reports status as running if processing and reason is NewReplicaSetAvailable", func() { + mc := struct{ MockClient }{} + mc.getMethod = func(_ context.Context, _ types.NamespacedName, o client.Object, _ ...client.GetOption) error { + t := o.(*appsv1.Deployment) + t.Status.AvailableReplicas = 1 + t.Status.Conditions = []appsv1.DeploymentCondition{ + { + Type: appsv1.DeploymentProgressing, + Status: v1.ConditionTrue, + Reason: "NewReplicaSetAvailable", + }, + } + return nil + } + handler := mkDeploymentHandler("unique", "my-namespace", mc) + expected := mkExpected(handler, riskifiedv1alpha1.Running) + result, _ := handler.GetStatus() + Expect(result).To(Equal(expected)) + }) + + It("reports status as initializing if processing=true and reason is not NewReplicaSetAvailable", func() { + mc := struct{ MockClient }{} + mc.getMethod = func(_ context.Context, _ types.NamespacedName, o client.Object, _ ...client.GetOption) error { + t := o.(*appsv1.Deployment) + t.Status.AvailableReplicas = 1 + t.Status.Conditions = []appsv1.DeploymentCondition{ + { + Type: appsv1.DeploymentProgressing, + Status: v1.ConditionTrue, + }, + } + return nil + } + handler := mkDeploymentHandler("unique", "my-namespace", mc) + expected := mkExpected(handler, riskifiedv1alpha1.Initializing) + result, _ := handler.GetStatus() + Expect(result).To(Equal(expected)) + }) + }) + + Context("When Deployment is not available", func() { + It("reports status as initializing if Processing=True", func() { + mc := struct{ MockClient }{} + mc.getMethod = func(_ context.Context, _ types.NamespacedName, o client.Object, _ ...client.GetOption) error { + t := o.(*appsv1.Deployment) + t.Status.Conditions = []appsv1.DeploymentCondition{ + { + Type: appsv1.DeploymentProgressing, + Status: v1.ConditionTrue, + Reason: "NewReplicaSetCreated", + Message: "Created new replica Set ...", + }, + { + Type: appsv1.DeploymentAvailable, + Status: v1.ConditionFalse, + Reason: "MinimumReplicasUnavailable", + Message: "Deployment does not have minimum ...", + }, + } + return nil + } + handler := mkDeploymentHandler("unavailable1", "my-namespace", mc) + expected := mkExpected(handler, riskifiedv1alpha1.Initializing) + result, _ := handler.GetStatus() + Expect(result).To(Equal(expected)) + }) + + It("reports status as failure if Processing=False", func() { + mc := struct{ MockClient }{} + mc.getMethod = func(_ context.Context, _ types.NamespacedName, o client.Object, _ ...client.GetOption) error { + t := o.(*appsv1.Deployment) + t.Status.Conditions = []appsv1.DeploymentCondition{ + { + Type: appsv1.DeploymentProgressing, + Status: v1.ConditionFalse, + }, + } + return nil + } + handler := mkDeploymentHandler("unique", "my-namespace", mc) + expected := mkExpected(handler, riskifiedv1alpha1.Failed) + result, _ := handler.GetStatus() + Expect(result).To(Equal(expected)) + }) + + // TODO: Do we need to test of ReplicaFailure condition? + }) + }) + + Context("UpdateIfRequired", func() { + + mkDeploymentHandler := func(uniqueName string, de riskifiedv1alpha1.DynamicEnv, c client.Client) handlers.DeploymentHandler { + return handlers.DeploymentHandler{ + Client: c, + UniqueName: uniqueName, + VersionLabel: names.DefaultVersionLabel, + StatusHandler: &handlers.DynamicEnvStatusHandler{ + Client: c, + Ctx: nil, + DynamicEnv: &de, + }, + Log: ctrl.Log, + } + } + + It("Matches correctly hashes of unmodified subsets", func() { + de, err := mkDynamicEnvFromYamlFile("fixtures/simple-dynamicenv.yaml") + if err != nil { + Fail(err.Error()) + } + mc := struct{ MockClient }{} + handler := mkDeploymentHandler("details-default-dynamicenv-sample", de, mc) + subset := de.Spec.Subsets[0].DeepCopy() + handler.Subset = *subset + errorResult := handler.UpdateIfRequired() + Expect(errorResult).To(BeNil()) + Expect(handler.Updating).To(BeFalse()) + }) + }) +}) diff --git a/pkg/handlers/destinationrule_handler.go b/pkg/handlers/destinationrule_handler.go new file mode 100644 index 0000000..c5740ee --- /dev/null +++ b/pkg/handlers/destinationrule_handler.go @@ -0,0 +1,211 @@ +package handlers + +import ( + "context" + goerrors "errors" + "fmt" + + "github.com/go-logr/logr" + riskifiedv1alpha1 "github.com/riskified/dynamic-environment/api/v1alpha1" + "github.com/riskified/dynamic-environment/pkg/helpers" + "github.com/riskified/dynamic-environment/pkg/watches" + istioapi "istio.io/api/networking/v1alpha3" + istionetwork "istio.io/client-go/pkg/apis/networking/v1alpha3" + "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" +) + +// A handler for managing DestinationRule manipulations. +type DestinationRuleHandler struct { + client.Client + // The unique name of the target DestinationRule + UniqueName string + // The unique version of the target DestinationRule + UniqueVersion string + // The namespace of the target DestinationRule + Namespace string + // The version label + VersionLabel string + // The version that gets the default route + DefaultVersion string + // Status handler (to be able to update status) + StatusHandler *DynamicEnvStatusHandler + // The host name of the service that points to the Deployment specified in + // the subset. + ServiceHosts []string + // The name/nmespace of the DynamicEnv that launches this DestinationRule + Owner types.NamespacedName + Log logr.Logger + Ctx context.Context + + ignoredMissing []string + activeHosts []string +} + +// Handles creation and manipulation of related DestinationRules. +func (h *DestinationRuleHandler) Handle() error { + for _, serviceHost := range h.ServiceHosts { + found := &istionetwork.DestinationRule{} + drName := h.calculateDRName(serviceHost) + if err := h.Get(h.Ctx, types.NamespacedName{Name: drName, Namespace: h.Namespace}, found); err != nil { + if errors.IsNotFound(err) { + if err := h.createMissingDestinationRule(drName, serviceHost); err != nil { + return err + } + continue + } + + return fmt.Errorf("error locating existing destination rule by name (%s): %w", serviceHost, err) + } + h.activeHosts = append(h.activeHosts, serviceHost) + } + + if len(h.activeHosts) == 0 { + return fmt.Errorf("no base destination rules were found for subset: %s", h.UniqueName) + } + + return nil +} + +// GetStatus here can only return missing or running is there is no real status +// for DestinationRule, just whether it exists or missing. +func (h *DestinationRuleHandler) GetStatus() (statuses []riskifiedv1alpha1.ResourceStatus, err error) { + + genStatus := func(name string, s riskifiedv1alpha1.LifeCycleStatus) riskifiedv1alpha1.ResourceStatus { + return riskifiedv1alpha1.ResourceStatus{ + Name: name, + Namespace: h.Namespace, + Status: s, + } + } + + for _, sh := range h.ServiceHosts { + found := &istionetwork.DestinationRule{} + drName := h.calculateDRName(sh) + if err := h.Get(h.Ctx, types.NamespacedName{Name: drName, Namespace: h.Namespace}, found); err != nil { + if errors.IsNotFound(err) { + if helpers.StringSliceContains(sh, h.ignoredMissing) { + statuses = append(statuses, genStatus(drName, riskifiedv1alpha1.IgnoredMissingDR)) + continue + } + statuses = append(statuses, genStatus(drName, riskifiedv1alpha1.Missing)) + continue + } + return statuses, fmt.Errorf("error locating existing destination rule by name (%s): %w", drName, err) + } + statuses = append(statuses, genStatus(drName, riskifiedv1alpha1.Running)) + } + + return statuses, nil +} + +func (h *DestinationRuleHandler) ApplyStatus(statuses []riskifiedv1alpha1.ResourceStatus) error { + for _, rs := range statuses { + if err := h.StatusHandler.AddDestinationRuleStatusEntry(h.UniqueName, rs); err != nil { + return err + } + } + return nil +} + +func (h *DestinationRuleHandler) GetSubset() string { + return h.UniqueName +} + +func (h *DestinationRuleHandler) GetHosts() []string { + return h.activeHosts +} + +func (h *DestinationRuleHandler) createMissingDestinationRule(destinationRuleName, serviceHost string) error { + if err := h.setStatus(h.UniqueName, destinationRuleName, riskifiedv1alpha1.Initializing); err != nil { + return fmt.Errorf("failed to update status (prior to launching destination rule: %s): %w", serviceHost, err) + } + if err := h.createOverridingDestinationRule(destinationRuleName, serviceHost); err != nil { + if goerrors.As(err, &IgnoredMissing{}) { + h.ignoredMissing = append(h.ignoredMissing, serviceHost) + h.Log.Info("Added hostname to list of ignored missing", "hostname", serviceHost) + } else { + return fmt.Errorf("creating destination rule for '%s': %w", serviceHost, err) + } + } else { + h.activeHosts = append(h.activeHosts, serviceHost) + } + return nil +} + +func (h *DestinationRuleHandler) createOverridingDestinationRule(drName, serviceHost string) error { + newDestinationRule, err := h.generateOverridingDestinationRule(serviceHost) + if err != nil { + return fmt.Errorf("creating overriding destination rule: %w", err) + } + h.Log.Info("Deploying newly created destination rule", "destination rule name", h.UniqueName, "service-host", drName) + watches.AddToAnnotation(h.Owner, newDestinationRule) + if err = h.Create(h.Ctx, newDestinationRule); err != nil { + return fmt.Errorf("error deploying new destination rule version=%q service-host=%q: %w", h.UniqueName, drName, err) + } + return nil +} + +func (h *DestinationRuleHandler) generateOverridingDestinationRule(serviceHost string) (*istionetwork.DestinationRule, error) { + originalDestinationRule, err := h.locateDestinationRuleByHostname(serviceHost) + if err != nil { + return nil, fmt.Errorf("locating default destination rule for '%s': %w", h.ServiceHosts, err) + } + subset := &istioapi.Subset{ + Labels: map[string]string{h.VersionLabel: h.UniqueVersion}, + Name: h.UniqueVersion, + } + newDestinationRule := &istionetwork.DestinationRule{ + ObjectMeta: metav1.ObjectMeta{ + Name: h.calculateDRName(serviceHost), + Namespace: h.Namespace, + Labels: map[string]string{ + h.VersionLabel: h.UniqueVersion, + }, + }, + Spec: istioapi.DestinationRule{ + Host: originalDestinationRule.Spec.Host, + Subsets: []*istioapi.Subset{ + subset, + }, + }, + } + return newDestinationRule, nil +} + +func (h *DestinationRuleHandler) locateDestinationRuleByHostname(hostName string) (*istionetwork.DestinationRule, error) { + destinationRules := &istionetwork.DestinationRuleList{} + if err := h.List(h.Ctx, destinationRules, client.InNamespace(h.Namespace)); err != nil { + return nil, fmt.Errorf("error listing existing destination rules: %w", err) + } + for _, dr := range destinationRules.Items { + if helpers.MatchNamespacedHost(hostName, h.Namespace, dr.Spec.Host, dr.Namespace) { + for _, s := range dr.Spec.Subsets { + if s.Labels[h.VersionLabel] == h.DefaultVersion { + return dr, nil + } + } + } + } + h.Log.Info("Couldn't find DestinationRule per hostname with default version", "default-version", + h.VersionLabel, "namespace", h.Namespace, "hostname", hostName) + return nil, IgnoredMissing{} +} + +func (h *DestinationRuleHandler) setStatus(subset, drName string, status riskifiedv1alpha1.LifeCycleStatus) error { + currentState := riskifiedv1alpha1.ResourceStatus{ + Name: drName, + Namespace: h.Namespace, + Status: status, + } + if err := h.StatusHandler.AddDestinationRuleStatusEntry(subset, currentState); err != nil { + return err + } + return nil +} + +func (h *DestinationRuleHandler) calculateDRName(serviceHost string) string { + return h.UniqueName + "-" + serviceHost +} diff --git a/pkg/handlers/destinationrule_handler_private_test.go b/pkg/handlers/destinationrule_handler_private_test.go new file mode 100644 index 0000000..3876b66 --- /dev/null +++ b/pkg/handlers/destinationrule_handler_private_test.go @@ -0,0 +1,65 @@ +package handlers + +// IMPORTANT: While it's generally bad practice to test private functions, I do it here in order to +// avoid overly specified tests from the outside. If these test fails (e.g. we changed +// implementation) just delete the failing tests (of-course, make sure you test the new +// functionality :) ). + +import ( + "context" + "github.com/go-logr/logr" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + istioapi "istio.io/api/networking/v1alpha3" + istionetwork "istio.io/client-go/pkg/apis/networking/v1alpha3" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var _ = Describe("Accepting alternative version label and version", func() { + It("Handles custom version label correctly", func() { + versionLabel := "version-label" + version := "custom-version-value" + mc := struct{ MockClient }{} + serviceName := "service-name" + mc.listMethod = func(_ context.Context, drs client.ObjectList, _ ...client.ListOption) error { + drs.(*istionetwork.DestinationRuleList).Items = []*istionetwork.DestinationRule{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "service", + Namespace: "namespace", + }, + Spec: istioapi.DestinationRule{ + Host: serviceName, + Subsets: []*istioapi.Subset{ + { + Name: versionLabel, + Labels: map[string]string{ + versionLabel: version, + }, + }, + }, + }, + }, + } + return nil + } + h := DestinationRuleHandler{ + Client: mc, + UniqueName: "unique-name", + UniqueVersion: "unique-version", + Namespace: "namespace", + VersionLabel: versionLabel, + DefaultVersion: version, + StatusHandler: nil, + ServiceHosts: []string{serviceName}, + Owner: types.NamespacedName{}, + Log: logr.Logger{}, + Ctx: nil, + } + dr, err := h.generateOverridingDestinationRule(serviceName) + Expect(err).To(BeNil()) + Expect(dr).NotTo(BeNil()) + }) +}) diff --git a/pkg/handlers/destinationrule_handler_test.go b/pkg/handlers/destinationrule_handler_test.go new file mode 100644 index 0000000..07ee9e9 --- /dev/null +++ b/pkg/handlers/destinationrule_handler_test.go @@ -0,0 +1,125 @@ +package handlers_test + +import ( + "context" + "fmt" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + riskifiedv1alpha1 "github.com/riskified/dynamic-environment/api/v1alpha1" + "github.com/riskified/dynamic-environment/pkg/handlers" + "io" + istionetwork "istio.io/client-go/pkg/apis/networking/v1alpha3" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/yaml" + "os" + ctrl "sigs.k8s.io/controller-runtime" + + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func destinationRuleFromYaml(fileName string) (dr *istionetwork.DestinationRule, err error) { + sourceFile, err := os.Open(fileName) + if err != nil { + return dr, fmt.Errorf("loading fixture file %q: %w", fileName, err) + } + data, err := io.ReadAll(sourceFile) + if err != nil { + return dr, fmt.Errorf("error reading data from file: %w", err) + } + if err := yaml.UnmarshalStrict(data, &dr); err != nil { + return dr, fmt.Errorf("error strict unmarshaling fixture: %w", err) + } + return dr, nil +} + +var _ = Describe("DestinationRuleHandler", func() { + Context("GetStatus", func() { + Context("Not ignored", func() { + It("returns 'missing' if destination rule not found", func() { + mc := struct{ MockClient }{} + mc.getMethod = func(context.Context, types.NamespacedName, client.Object, ...client.GetOption) error { + return errors.NewNotFound(schema.GroupResource{}, "error") + } + handler := handlers.DestinationRuleHandler{ + Client: mc, + UniqueName: "unique", + Namespace: "ns", + ServiceHosts: []string{"service"}, + } + expected := []riskifiedv1alpha1.ResourceStatus{ + { + Name: "unique-service", + Namespace: "ns", + Status: riskifiedv1alpha1.Missing, + }, + } + result, err := handler.GetStatus() + Expect(err).To(BeNil()) + Expect(result).To(Equal(expected)) + }) + }) + }) + + Context("Handle", func() { + Context("missing base destination rules", func() { + It("returns without error if at least one base destination rule is found", func() { + mc := struct{ MockClient }{} + mc.listMethod = func(_ context.Context, o client.ObjectList, _ ...client.ListOption) error { + drList := o.(*istionetwork.DestinationRuleList) + dr, err := destinationRuleFromYaml("fixtures/destination-rule-with-unrelated-hostname.yaml") + Expect(err).To(BeNil()) + drList.Items = []*istionetwork.DestinationRule{dr} + return nil + } + mc.getMethod = func(context.Context, types.NamespacedName, client.Object, ...client.GetOption) error { + return errors.NewNotFound(schema.GroupResource{}, "error") + } + handler := handlers.DestinationRuleHandler{ + Client: mc, + UniqueName: "unique", + Namespace: "ns", + ServiceHosts: []string{"details", "service2"}, + StatusHandler: &handlers.DynamicEnvStatusHandler{ + Client: mc, + Ctx: context.Background(), + DynamicEnv: &riskifiedv1alpha1.DynamicEnv{}, + }, + Log: ctrl.Log, + } + err := handler.Handle() + Expect(err).To(BeNil()) + }) + + It("returns error if destination rules for all hosts were missing", func() { + mc := struct{ MockClient }{} + mc.listMethod = func(_ context.Context, o client.ObjectList, _ ...client.ListOption) error { + drList := o.(*istionetwork.DestinationRuleList) + dr, err := destinationRuleFromYaml("fixtures/destination-rule-with-unrelated-hostname.yaml") + Expect(err).To(BeNil()) + drList.Items = []*istionetwork.DestinationRule{dr} + return nil + } + mc.getMethod = func(context.Context, types.NamespacedName, client.Object, ...client.GetOption) error { + return errors.NewNotFound(schema.GroupResource{}, "error") + } + handler := handlers.DestinationRuleHandler{ + Client: mc, + UniqueName: "unique", + Namespace: "ns", + ServiceHosts: []string{"service1", "service2"}, + StatusHandler: &handlers.DynamicEnvStatusHandler{ + Client: mc, + Ctx: context.Background(), + DynamicEnv: &riskifiedv1alpha1.DynamicEnv{}, + }, + Log: ctrl.Log, + } + err := handler.Handle() + Expect(err).NotTo(BeNil()) + Expect(err.Error()).To(ContainSubstring("no base destination rules")) + }) + }) + }) +}) diff --git a/pkg/handlers/dynamicenv_status_handler.go b/pkg/handlers/dynamicenv_status_handler.go new file mode 100644 index 0000000..07d3fd0 --- /dev/null +++ b/pkg/handlers/dynamicenv_status_handler.go @@ -0,0 +1,239 @@ +package handlers + +import ( + "context" + + riskifiedv1alpha1 "github.com/riskified/dynamic-environment/api/v1alpha1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// A handler for managing `DynamicEnv` Status. +type DynamicEnvStatusHandler struct { + client.Client + Ctx context.Context + DynamicEnv *riskifiedv1alpha1.DynamicEnv +} + +// Adds (or updates) a status entry to the *Deployments* status section (if not +// exists). +func (h *DynamicEnvStatusHandler) AddDeploymentStatusEntry(subset string, newStatus riskifiedv1alpha1.ResourceStatus, tpe riskifiedv1alpha1.SubsetOrConsumer) error { + if tpe == riskifiedv1alpha1.CONSUMER { + return h.addConsumerDeploymentStatusEntry(subset, newStatus) + } + return h.addSubsetDeploymentStatusEntry(subset, newStatus) +} + +// Add a status entry to the *DestinationRules* status section (if not exists). +func (h *DynamicEnvStatusHandler) AddDestinationRuleStatusEntry(subset string, newStatus riskifiedv1alpha1.ResourceStatus) error { + currentStatus := h.safeGetSubsetsStatus(subset) + modified, newStatuses := SyncStatusResources(newStatus, currentStatus.DestinationRules) + if modified { + currentStatus.DestinationRules = newStatuses + h.DynamicEnv.Status.SubsetsStatus[subset] = currentStatus + return h.Status().Update(h.Ctx, h.DynamicEnv) + } + return nil +} + +// Add a status entry to the *VirtualServices* status section (if not exists). +func (h *DynamicEnvStatusHandler) AddVirtualServiceStatusEntry(subset string, newStatus riskifiedv1alpha1.ResourceStatus) error { + currentStatus := h.safeGetSubsetsStatus(subset) + modified, newStatuses := SyncStatusResources(newStatus, currentStatus.VirtualServices) + if modified { + currentStatus.VirtualServices = newStatuses + h.DynamicEnv.Status.SubsetsStatus[subset] = currentStatus + return h.Status().Update(h.Ctx, h.DynamicEnv) + } + return nil +} + +func (h *DynamicEnvStatusHandler) AddGlobalVirtualServiceError(subset, msg string) error { + currentStatus := h.safeGetSubsetsStatus(subset) + statusErrors := SafeGetSubsetErrors(currentStatus) + currentErrors := statusErrors.VirtualServices + currentErrors = SyncGlobalErrors(msg, currentErrors) + statusErrors.VirtualServices = currentErrors + currentStatus.Errors = &statusErrors + h.DynamicEnv.Status.SubsetsStatus[subset] = currentStatus + return h.Status().Update(h.Ctx, h.DynamicEnv) +} + +func (h *DynamicEnvStatusHandler) SetGlobalState(state riskifiedv1alpha1.GlobalReadyStatus, totalCount int, notReadyCount int) error { + h.DynamicEnv.Status.State = state + h.DynamicEnv.Status.TotalCount = totalCount + h.DynamicEnv.Status.TotalReady = totalCount - notReadyCount + return h.Status().Update(h.Ctx, h.DynamicEnv) +} + +func (h *DynamicEnvStatusHandler) addSubsetDeploymentStatusEntry(subset string, newStatus riskifiedv1alpha1.ResourceStatus) error { + currentStatus := h.safeGetSubsetsStatus(subset) + if !currentStatus.Deployment.IsEqual(newStatus) { + currentStatus.Deployment = newStatus + h.DynamicEnv.Status.SubsetsStatus[subset] = currentStatus + return h.Status().Update(h.Ctx, h.DynamicEnv) + } + return nil +} + +func (h *DynamicEnvStatusHandler) addConsumerDeploymentStatusEntry(subset string, newStatus riskifiedv1alpha1.ResourceStatus) error { + currentStatus := h.safeGetConsumersStatus(subset) + if !currentStatus.IsEqual(newStatus) { + currentStatus.Name = newStatus.Name + currentStatus.Namespace = newStatus.Namespace + currentStatus.Status = newStatus.Status + h.DynamicEnv.Status.ConsumersStatus[subset] = currentStatus + return h.Status().Update(h.Ctx, h.DynamicEnv) + } + return nil +} + +func (h *DynamicEnvStatusHandler) safeGetSubsetsStatus(subset string) riskifiedv1alpha1.SubsetStatus { + if h.DynamicEnv.Status.SubsetsStatus == nil { + h.DynamicEnv.Status.SubsetsStatus = make(map[string]riskifiedv1alpha1.SubsetStatus) + } + return h.DynamicEnv.Status.SubsetsStatus[subset] +} + +func (h *DynamicEnvStatusHandler) safeGetConsumersStatus(subset string) riskifiedv1alpha1.ConsumerStatus { + if h.DynamicEnv.Status.ConsumersStatus == nil { + h.DynamicEnv.Status.ConsumersStatus = make(map[string]riskifiedv1alpha1.ConsumerStatus) + } + return h.DynamicEnv.Status.ConsumersStatus[subset] +} + +func SafeGetSubsetErrors(status riskifiedv1alpha1.SubsetStatus) riskifiedv1alpha1.SubsetErrors { + if status.Errors != nil { + return *status.Errors + } + return riskifiedv1alpha1.SubsetErrors{} +} + +func (h *DynamicEnvStatusHandler) SyncSubsetMessagesToStatus(messages map[string]riskifiedv1alpha1.SubsetMessages) { + for sbst, msgs := range messages { + statuses := h.DynamicEnv.Status.SubsetsStatus + if statuses == nil { + statuses = make(map[string]riskifiedv1alpha1.SubsetStatus) + } + cs := statuses[sbst] + errors := SafeGetSubsetErrors(cs) + for _, msg := range msgs.Deployment { + errors.Deployment = SyncGlobalErrors(msg, errors.Deployment) + } + for _, msg := range msgs.DestinationRule { + errors.DestinationRule = SyncGlobalErrors(msg, errors.DestinationRule) + } + for _, msg := range msgs.VirtualService { + errors.VirtualServices = SyncGlobalErrors(msg, errors.VirtualServices) + } + for _, msg := range msgs.GlobalErrors { + errors.Subset = SyncGlobalErrors(msg, errors.Subset) + } + cs.Errors = &errors + statuses[sbst] = cs + h.DynamicEnv.Status.SubsetsStatus = statuses + } +} + +func (h *DynamicEnvStatusHandler) SyncConsumerMessagesToStatus(messages map[string][]string) { + for subject, msgs := range messages { + statuses := h.DynamicEnv.Status.ConsumersStatus + if statuses == nil { + statuses = make(map[string]riskifiedv1alpha1.ConsumerStatus) + } + cs := statuses[subject] + for _, msg := range msgs { + cs.Errors = SyncGlobalErrors(msg, cs.Errors) + } + statuses[subject] = cs + h.DynamicEnv.Status.ConsumersStatus = statuses + } +} + +func (h *DynamicEnvStatusHandler) GetHashForSubset(name string) uint64 { + subsetStatus, ok := h.DynamicEnv.Status.SubsetsStatus[name] + if !ok { + return 0 + } + return uint64(subsetStatus.Hash) +} + +func (h *DynamicEnvStatusHandler) GetHashForConsumer(name string) uint64 { + consumerStatus, ok := h.DynamicEnv.Status.ConsumersStatus[name] + if !ok { + return 0 + } + return uint64(consumerStatus.Hash) +} + +func (h *DynamicEnvStatusHandler) ApplyHash(name string, hash uint64, tpe riskifiedv1alpha1.SubsetOrConsumer) error { + if tpe == riskifiedv1alpha1.CONSUMER { + return h.setHashForConsumer(name, hash) + } else { + return h.setHashForSubset(name, hash) + } +} + +func SyncStatusResources( + s riskifiedv1alpha1.ResourceStatus, + currentStatuses []riskifiedv1alpha1.ResourceStatus, +) (modified bool, result []riskifiedv1alpha1.ResourceStatus) { + exists := false + for _, resource := range currentStatuses { + newStatus := resource + if resource.Name == s.Name && resource.Namespace == s.Namespace { + exists = true + if resource.Status != s.Status { + modified = true + newStatus = s + } + } + result = append(result, newStatus) + } + if !exists { + result = append(result, s) + modified = true + } + return modified, result +} + +func SyncGlobalErrors(msg string, errors []riskifiedv1alpha1.StatusError) []riskifiedv1alpha1.StatusError { + var result []riskifiedv1alpha1.StatusError + if len(errors) == 0 { + result = append(result, riskifiedv1alpha1.NewStatusError(msg)) + } else { + found := false + for _, e := range errors { + if e.Error == msg { + found = true + e.UpdateTime() + } + result = append(result, e) + } + if !found { + result = append(result, riskifiedv1alpha1.NewStatusError(msg)) + } + } + return result +} +func (h *DynamicEnvStatusHandler) setHashForSubset(name string, hash uint64) error { + subsetStatus, ok := h.DynamicEnv.Status.SubsetsStatus[name] + if !ok { + subsetStatus = riskifiedv1alpha1.SubsetStatus{} + } + subsetStatus.Hash = int64(hash) + h.DynamicEnv.Status.SubsetsStatus[name] = subsetStatus + return h.Status().Update(h.Ctx, h.DynamicEnv) +} + +func (h *DynamicEnvStatusHandler) setHashForConsumer(name string, hash uint64) error { + if h.DynamicEnv.Status.ConsumersStatus == nil { + h.DynamicEnv.Status.ConsumersStatus = make(map[string]riskifiedv1alpha1.ConsumerStatus) + } + consumerStatus, ok := h.DynamicEnv.Status.ConsumersStatus[name] + if !ok { + consumerStatus = riskifiedv1alpha1.ConsumerStatus{} + } + consumerStatus.Hash = int64(hash) + h.DynamicEnv.Status.ConsumersStatus[name] = consumerStatus + return h.Status().Update(h.Ctx, h.DynamicEnv) +} diff --git a/pkg/handlers/fixtures/destination-rule-with-unrelated-hostname.yaml b/pkg/handlers/fixtures/destination-rule-with-unrelated-hostname.yaml new file mode 100644 index 0000000..d6aecca --- /dev/null +++ b/pkg/handlers/fixtures/destination-rule-with-unrelated-hostname.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: details + namespace: ns +spec: + host: details + subsets: + - name: shared + labels: + version: shared +... \ No newline at end of file diff --git a/pkg/handlers/fixtures/simple-dynamicenv.yaml b/pkg/handlers/fixtures/simple-dynamicenv.yaml new file mode 100644 index 0000000..d79fcc7 --- /dev/null +++ b/pkg/handlers/fixtures/simple-dynamicenv.yaml @@ -0,0 +1,22 @@ +apiVersion: riskified.com/v1alpha1 +kind: DynamicEnv +metadata: + name: dynamicenv-sample +spec: + istioMatches: + - headers: + end-user: + exact: jason + subsets: + - name: "details" + namespace: "services" + containers: + - containerName: details + image: docker.io/istio/examples-bookinfo-details-v2:1.16.2 + env: + - name: TOPIC_NAME + value: test +status: + subsetsStatus: + details-default-dynamicenv-sample: + hash: 1157382594159137535 diff --git a/pkg/handlers/handler.go b/pkg/handlers/handler.go new file mode 100644 index 0000000..4d422ab --- /dev/null +++ b/pkg/handlers/handler.go @@ -0,0 +1,40 @@ +package handlers + +import ( + riskifiedv1alpha1 "github.com/riskified/dynamic-environment/api/v1alpha1" +) + +// Common functionality for SRHandler and MRHandler +type BaseHandler interface { + // An entry point to the handler. Takes care of initializing / updating the resource. + Handle() error + // Get the subset unique name for this handler + GetSubset() string +} + +// A SingleResourceHandler for any of the resources we manage in DynamicEnv controller. +type SRHandler interface { + BaseHandler + // Computes what the current status of the resource should be. It does not + // update the status, just computes what the current status should be. + GetStatus() (riskifiedv1alpha1.ResourceStatus, error) + // Apply the provided status to the DynamicEnvironment. + ApplyStatus(riskifiedv1alpha1.ResourceStatus) error +} + +// MultiResourceHandler is a spacial kind of Handler as it may affect several resources. +type MRHandler interface { + BaseHandler + // Computes the status of all the services which are affected by this handler. It doesn't change + // anything, just returns the found statuses. + GetStatus() ([]riskifiedv1alpha1.ResourceStatus, error) + // Apply the provided status to the DynamicEnvironment. + ApplyStatus([]riskifiedv1alpha1.ResourceStatus) error + /// GetHosts returns non-missing hosts. + GetHosts() []string +} + +// IgnoredMissing should indicate an acceptable missing resource (e.g. missing DR per hostname) +type IgnoredMissing struct{} + +func (im IgnoredMissing) Error() string { return "Ignored Missing Resource" } diff --git a/pkg/handlers/handlers_suite_test.go b/pkg/handlers/handlers_suite_test.go new file mode 100644 index 0000000..bd6531d --- /dev/null +++ b/pkg/handlers/handlers_suite_test.go @@ -0,0 +1,49 @@ +package handlers_test + +import ( + "context" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Handlers Suite") +} + +// A hack for implementing a k8s client inside a test function. +// see: https://stackoverflow.com/questions/31362044/anonymous-interface-implementation-in-golang +type MockClient struct { + client.Client + getMethod func(context.Context, types.NamespacedName, client.Object, ...client.GetOption) error + listMethod func(context.Context, client.ObjectList, ...client.ListOption) error +} + +func (m MockClient) Get(c context.Context, ns types.NamespacedName, o client.Object, _ ...client.GetOption) error { + return m.getMethod(c, ns, o) +} + +func (m MockClient) List(c context.Context, l client.ObjectList, o ...client.ListOption) error { + return m.listMethod(c, l, o...) +} + +func (m MockClient) Create(_ context.Context, _ client.Object, _ ...client.CreateOption) error { + return nil +} + +func (m MockClient) Status() client.SubResourceWriter { + return MockStatus{} +} + +type MockStatus struct { + client.SubResourceWriter +} + +func (_ MockStatus) Update(_ context.Context, _ client.Object, _ ...client.SubResourceUpdateOption) error { + return nil +} diff --git a/pkg/handlers/status_handler_test.go b/pkg/handlers/status_handler_test.go new file mode 100644 index 0000000..24d957b --- /dev/null +++ b/pkg/handlers/status_handler_test.go @@ -0,0 +1,359 @@ +package handlers_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "time" + + riskifiedv1alpha1 "github.com/riskified/dynamic-environment/api/v1alpha1" + "github.com/riskified/dynamic-environment/pkg/handlers" +) + +var _ = Describe("SyncStatusResources", func() { + defaultResources := []riskifiedv1alpha1.ResourceStatus{ + { + Name: "event1", + Namespace: "ns", + Status: riskifiedv1alpha1.Unknown, + }, + { + Name: "event2", + Namespace: "ns", + Status: riskifiedv1alpha1.Unknown, + }, + } + + DescribeTable( + "correctly handles status sync", + func( + s riskifiedv1alpha1.ResourceStatus, + resources []riskifiedv1alpha1.ResourceStatus, + modified bool, + expected []riskifiedv1alpha1.ResourceStatus, + ) { + m, result := handlers.SyncStatusResources(s, resources) + Expect(result).To(Equal(expected)) + Expect(m).To(Equal(modified)) + }, + Entry( + "when status already synced", + riskifiedv1alpha1.ResourceStatus{ + Name: "event1", + Namespace: "ns", + Status: riskifiedv1alpha1.Unknown, + }, + defaultResources, + false, + defaultResources, + ), + Entry( + "when provided resource exists by name but status is different", + riskifiedv1alpha1.ResourceStatus{ + Name: "event1", + Namespace: "ns", + Status: riskifiedv1alpha1.Initializing, + }, + defaultResources, + true, + []riskifiedv1alpha1.ResourceStatus{ + { + Name: "event1", + Namespace: "ns", + Status: riskifiedv1alpha1.Initializing, + }, + { + Name: "event2", + Namespace: "ns", + Status: riskifiedv1alpha1.Unknown, + }, + }, + ), + Entry( + "when provided status does not exists", + riskifiedv1alpha1.ResourceStatus{ + Name: "event2", + Namespace: "ns", + Status: riskifiedv1alpha1.Running, + }, + []riskifiedv1alpha1.ResourceStatus{ + { + Name: "event1", + Namespace: "ns", + Status: riskifiedv1alpha1.Initializing, + }, + }, + true, + []riskifiedv1alpha1.ResourceStatus{ + { + Name: "event1", + Namespace: "ns", + Status: riskifiedv1alpha1.Initializing, + }, + { + Name: "event2", + Namespace: "ns", + Status: riskifiedv1alpha1.Running, + }, + }, + ), + Entry( + "when current resources is empty", + riskifiedv1alpha1.ResourceStatus{ + Name: "event1", + Namespace: "ns", + Status: riskifiedv1alpha1.Initializing, + }, + nil, + true, + []riskifiedv1alpha1.ResourceStatus{ + { + Name: "event1", + Namespace: "ns", + Status: riskifiedv1alpha1.Initializing, + }, + }, + ), + ) + // do something +}) + +var _ = Describe("SyncGlobalErrors", func() { + + findErrorIndex := func(msg string, errors []riskifiedv1alpha1.StatusError) int { + for i, e := range errors { + if e.Error == msg { + return i + } + } + panic("error not found in error list") + } + + Context("Empty global error list", func() { + It("Adds provided error", func() { + now := metav1.Now() + time.Sleep(200 * time.Microsecond) // force after + var currentErrors []riskifiedv1alpha1.StatusError + msg := "This is an error" + result := handlers.SyncGlobalErrors(msg, currentErrors) + Expect(result).To(HaveLen(1)) + e1 := result[0] + Expect(e1.Error).Should(Equal(msg)) + Expect(e1.LastOccurrence.After(now.Time)).To(BeTrue(), "last occurrence should be after 'now'") + }) + }) + + Context("Existing global errors", func() { + Context("Existing single matching error", func() { + It("Updates the last occurrence", func() { + msg := "This is an error" + currentErrorTime := metav1.NewTime(time.Now().Add(-time.Minute * 10)) + current := []riskifiedv1alpha1.StatusError{ + { + Error: msg, + LastOccurrence: currentErrorTime, + }, + } + result := handlers.SyncGlobalErrors(msg, current) + Expect(result).To(HaveLen(1)) + e1 := result[0] + Expect(e1.Error).Should(Equal(msg)) + Expect(e1.LastOccurrence.After(currentErrorTime.Time)).To(BeTrue(), "After update error time should be after original") + }) + }) + + Context("Existing multiple errors including matching error", func() { + It("Updates the last occurrence of the matching error", func() { + msg := "This is an error" + currentErrorTime := metav1.NewTime(time.Now().Add(-time.Minute * 10)) + current := []riskifiedv1alpha1.StatusError{ + riskifiedv1alpha1.NewStatusError("Different error 1"), + { + Error: msg, + LastOccurrence: currentErrorTime, + }, + riskifiedv1alpha1.NewStatusError("Different error 2"), + } + result := handlers.SyncGlobalErrors(msg, current) + Expect(result).To(HaveLen(3)) + idx := findErrorIndex(msg, result) + err := result[idx] + Expect(err.LastOccurrence.After(currentErrorTime.Time)).To(BeTrue(), "After update error time should be after original") + }) + }) + + Context("Existing Errors without matching error", func() { + It("Adds the provided error", func() { + msg := "This is an error" + current := []riskifiedv1alpha1.StatusError{ + riskifiedv1alpha1.NewStatusError("Different error 1"), + riskifiedv1alpha1.NewStatusError("Different error 2"), + } + result := handlers.SyncGlobalErrors(msg, current) + Expect(result).To(HaveLen(3)) + _ = findErrorIndex(msg, result) + // If last call did not panic than we're ok. + + }) + }) + }) +}) + +var _ = Describe("SyncSubsetMessagesToStatus", func() { + st1msgDeploy := "A deployment message on subset1" + st1msgVS := "A VS message on subset1" + st1msgDR := "A DR message on subset1" + st1msgErr := "A global error on subset1" + st2msgDeploy := "A deployment message on subset2" + st2msgVS := "A VS message on subset2" + st2msgDR := "A DR message on subset2" + st2msgErr := "A global error on subset2" + nonEmptyStatus := map[string]riskifiedv1alpha1.SubsetStatus{ + "subset1": { + Errors: &riskifiedv1alpha1.SubsetErrors{ + Deployment: []riskifiedv1alpha1.StatusError{ + { + Error: "A deployment error on subset1", + }, + }, + DestinationRule: []riskifiedv1alpha1.StatusError{ + { + Error: "A DR error on subset1", + }, + }, + VirtualServices: []riskifiedv1alpha1.StatusError{ + { + Error: "A VS error on subset 1", + }, + }, + Subset: []riskifiedv1alpha1.StatusError{ + { + Error: "A subset error on subset 1", + }, + }, + }, + }, + "subset2": { + Errors: &riskifiedv1alpha1.SubsetErrors{ + Deployment: []riskifiedv1alpha1.StatusError{ + { + Error: "A deployment error on subset2", + }, + }, + DestinationRule: []riskifiedv1alpha1.StatusError{ + { + Error: "A DR error on subset2", + }, + }, + VirtualServices: []riskifiedv1alpha1.StatusError{ + { + Error: "A VS error on subset 2", + }, + }, + Subset: []riskifiedv1alpha1.StatusError{ + { + Error: "A subset error on subset 2", + }, + }, + }, + }, + } + nonEmptyMessages := map[string]riskifiedv1alpha1.SubsetMessages{ + "subset1": { + Deployment: []string{ + st1msgDeploy, + }, + VirtualService: []string{ + st1msgVS, + }, + DestinationRule: []string{ + st1msgDR, + }, + GlobalErrors: []string{ + st1msgErr, + }, + }, + "subset2": { + Deployment: []string{ + st2msgDeploy, + }, + VirtualService: []string{ + st2msgVS, + }, + DestinationRule: []string{ + st2msgDR, + }, + GlobalErrors: []string{ + st2msgErr, + }, + }, + } + + mkStatusHandler := func() *handlers.DynamicEnvStatusHandler { + return &handlers.DynamicEnvStatusHandler{ + DynamicEnv: &riskifiedv1alpha1.DynamicEnv{ + Status: riskifiedv1alpha1.DynamicEnvStatus{ + SubsetsStatus: make(map[string]riskifiedv1alpha1.SubsetStatus), + }, + }, + } + } + + statusErrors2Messages := func(errors []riskifiedv1alpha1.StatusError) (result []string) { + for _, err := range errors { + result = append(result, err.Error) + } + return result + } + + Context("With empty status", func() { + It("should include all messages", func() { + h := mkStatusHandler() + h.SyncSubsetMessagesToStatus(nonEmptyMessages) + result := h.DynamicEnv.Status.SubsetsStatus + Expect(result).To(HaveLen(2)) + errors1 := handlers.SafeGetSubsetErrors(result["subset1"]) + errors2 := handlers.SafeGetSubsetErrors(result["subset2"]) + Expect(errors1.Deployment[0].Error).To(Equal(st1msgDeploy)) + Expect(errors1.DestinationRule[0].Error).To(Equal(st1msgDR)) + Expect(errors1.VirtualServices[0].Error).To(Equal(st1msgVS)) + Expect(errors1.Subset[0].Error).To(Equal(st1msgErr)) + Expect(errors2.Deployment[0].Error).To(Equal(st2msgDeploy)) + Expect(errors2.DestinationRule[0].Error).To(Equal(st2msgDR)) + Expect(errors2.VirtualServices[0].Error).To(Equal(st2msgVS)) + Expect(errors2.Subset[0].Error).To(Equal(st2msgErr)) + }) + }) + + Context("With no messages", func() { + It("should leave status the same", func() { + h := mkStatusHandler() + h.DynamicEnv.Status.SubsetsStatus = nonEmptyStatus + h.SyncSubsetMessagesToStatus(make(map[string]riskifiedv1alpha1.SubsetMessages)) + result := h.DynamicEnv.Status.SubsetsStatus + Expect(result).To(Equal(nonEmptyStatus)) + }) + }) + + Context("With both status and messages", func() { + It("should merge messages", func() { + h := mkStatusHandler() + h.DynamicEnv.Status.SubsetsStatus = nonEmptyStatus + h.SyncSubsetMessagesToStatus(nonEmptyMessages) + subset1 := h.DynamicEnv.Status.SubsetsStatus["subset1"] + Expect(subset1.Errors).ToNot(BeNil(), "expected error messages, received nil") + Expect(subset1.Errors.Deployment).To(HaveLen(2)) + Expect(subset1.Errors.Deployment).To(ContainElement(nonEmptyStatus["subset1"].Errors.Deployment[0])) + Expect(statusErrors2Messages(subset1.Errors.Deployment)).To(ContainElement(st1msgDeploy)) + Expect(subset1.Errors.DestinationRule).To(HaveLen(2)) + Expect(subset1.Errors.DestinationRule).To(ContainElement(nonEmptyStatus["subset1"].Errors.DestinationRule[0])) + Expect(statusErrors2Messages(subset1.Errors.DestinationRule)).To(ContainElement(st1msgDR)) + Expect(subset1.Errors.VirtualServices).To(HaveLen(2)) + Expect(subset1.Errors.VirtualServices).To(ContainElement(nonEmptyStatus["subset1"].Errors.VirtualServices[0])) + Expect(statusErrors2Messages(subset1.Errors.VirtualServices)).To(ContainElement(st1msgVS)) + Expect(subset1.Errors.Subset).To(HaveLen(2)) + Expect(subset1.Errors.Subset).To(ContainElement(nonEmptyStatus["subset1"].Errors.Subset[0])) + Expect(statusErrors2Messages(subset1.Errors.Subset)).To(ContainElement(st1msgErr)) + }) + }) +}) diff --git a/pkg/handlers/virtualservice_handler.go b/pkg/handlers/virtualservice_handler.go new file mode 100644 index 0000000..64ab938 --- /dev/null +++ b/pkg/handlers/virtualservice_handler.go @@ -0,0 +1,331 @@ +package handlers + +import ( + "context" + "fmt" + "strings" + + "k8s.io/apimachinery/pkg/api/errors" + + "github.com/go-logr/logr" + riskifiedv1alpha1 "github.com/riskified/dynamic-environment/api/v1alpha1" + "github.com/riskified/dynamic-environment/pkg/helpers" + "github.com/riskified/dynamic-environment/pkg/watches" + istioapi "istio.io/api/networking/v1alpha3" + istionetwork "istio.io/client-go/pkg/apis/networking/v1alpha3" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// A handler for managing VirtualService manipulations. +type VirtualServiceHandler struct { + client.Client + UniqueName string + UniqueVersion string + Namespace string + RoutePrefix string + ServiceHosts []string + DefaultVersion string + DynamicEnv *riskifiedv1alpha1.DynamicEnv + StatusHandler *DynamicEnvStatusHandler + Log logr.Logger + Ctx context.Context + + activeHosts []string +} + +// Fetches related Virtual Services and manipulates them accordingly. +func (h *VirtualServiceHandler) Handle() error { + for _, serviceHost := range h.ServiceHosts { + services, err := h.locateVirtualServicesByServiceHost(serviceHost) + if err != nil { + return err + } + var atLeastOneVSPerHostnameExists = false + for _, service := range services { + if err := h.updateVirtualService(serviceHost, service); err != nil { + h.Log.Info("error updating virtual service", "service-host", serviceHost, "error", err.Error()) + continue + } + atLeastOneVSPerHostnameExists = true + } + if atLeastOneVSPerHostnameExists { + h.activeHosts = append(h.activeHosts, serviceHost) + } + } + + if len(h.activeHosts) == 0 { + return fmt.Errorf("could not find even one virtual service that handles subset %q", h.GetSubset()) + } + return nil +} + +func (h *VirtualServiceHandler) GetStatus() ([]riskifiedv1alpha1.ResourceStatus, error) { + var services []*istionetwork.VirtualService + var statuses []riskifiedv1alpha1.ResourceStatus + for _, serviceHost := range h.ServiceHosts { + result, err := h.locateVirtualServicesByServiceHost(serviceHost) + if err != nil { + h.Log.Error(err, "While running GetStatus in VirtualServiceHandler") + return []riskifiedv1alpha1.ResourceStatus{}, err + } + services = append(services, result...) + } + for _, service := range services { + s := h.getStatusForService(service) + statuses = append(statuses, s) + } + + return statuses, nil +} + +func (h *VirtualServiceHandler) ApplyStatus(statuses []riskifiedv1alpha1.ResourceStatus) error { + for _, rs := range statuses { + if err := h.StatusHandler.AddVirtualServiceStatusEntry(h.UniqueName, rs); err != nil { + return err + } + } + return nil +} + +func (h *VirtualServiceHandler) GetSubset() string { + return h.UniqueName +} + +func (h *VirtualServiceHandler) GetHosts() []string { + return h.activeHosts +} + +func (h *VirtualServiceHandler) locateVirtualServicesByServiceHost(serviceHost string) ([]*istionetwork.VirtualService, error) { + virtualServices := istionetwork.VirtualServiceList{} + var result []*istionetwork.VirtualService + if err := h.List(h.Ctx, &virtualServices); err != nil { + return nil, fmt.Errorf("error locating virtual services for app host '%s': %w", h.ServiceHosts, err) + } + for idx, service := range virtualServices.Items { + for _, host := range service.Spec.Hosts { + if helpers.MatchNamespacedHost(serviceHost, h.Namespace, host, service.Namespace) { + useSelf, delegated := h.resolveVirtualServices(serviceHost, service) + if useSelf { + result = append(result, virtualServices.Items[idx]) + } + for _, d := range delegated { + found, err := h.extractServiceFromDelegate(d, virtualServices) + if err != nil { + return result, err + } + if found != nil { + result = append(result, found) + } + } + } + } + } + h.Log.Info(fmt.Sprintf("Found %d virtual services matching '%s'", len(result), h.ServiceHosts)) + return result, nil +} + +// Parses the provided virtual service and return a list of delegates (if exists) and itself if +// contains non-delegate HTTPRoutes. +func (h *VirtualServiceHandler) resolveVirtualServices(serviceHost string, service *istionetwork.VirtualService) ( + useSelf bool, delegated []*istioapi.Delegate, +) { + for _, r := range service.Spec.Http { + if r.Delegate != nil { + delegated = append(delegated, r.Delegate) + } + if h.hasMatchingHostAndSubset(serviceHost, r.Route, service.Namespace) { + useSelf = true + } + } + return useSelf, delegated +} + +func (h *VirtualServiceHandler) extractServiceFromDelegate(delegate *istioapi.Delegate, services istionetwork.VirtualServiceList) (*istionetwork.VirtualService, error) { + for _, s := range services.Items { + if delegate.Name == s.Name && delegate.Namespace == s.Namespace { + return s, nil + } + } + msg := fmt.Sprintf("Wierd, Couldn't find a service with name: %s, namespace: %s, in the service list", delegate.Name, delegate.Namespace) + if err := h.StatusHandler.AddGlobalVirtualServiceError(h.UniqueName, msg); err != nil { + h.Log.Error(err, "failed to write the following message to status: "+msg) + } + h.Log.Info(msg) + s := &istionetwork.VirtualService{} + searchName := types.NamespacedName{Name: delegate.Name, Namespace: delegate.Namespace} + if err := h.Client.Get(h.Ctx, searchName, s); err != nil { + if errors.IsNotFound(err) { + msg := fmt.Sprintf("Delegate (%s/%s) not found", delegate.Namespace, delegate.Name) + h.Log.V(0).Info(msg) + if err = h.StatusHandler.AddGlobalVirtualServiceError(h.UniqueName, msg); err != nil { + h.Log.Error(err, "Error writing virtual service status regarding delegate", "delegate", delegate) + } + return nil, nil + } else { + return nil, fmt.Errorf("error while loading delegate '%s/%s': %w", delegate.Namespace, delegate.Name, err) + } + } + return s, nil +} + +func (h *VirtualServiceHandler) updateVirtualService(serviceHost string, service *istionetwork.VirtualService) error { + h.Log.Info("Updating virtual service to route to version", "virtual-service", service.Name, "version", h.UniqueVersion) + owner := types.NamespacedName{Name: h.DynamicEnv.Name, Namespace: h.DynamicEnv.Namespace} + prefix := h.RoutePrefix + + // TODO: Might not be relevant in multiple services + if containsDynamicEnvRoutes(service.Spec.Http, prefix) { + h.Log.Info("Skipping virtual service that already contains dynamic-environment routes", "virtual-service", service.Name) + return nil + } + + // Add this service to status + newStatus := riskifiedv1alpha1.ResourceStatus{Name: service.Name, Namespace: service.Namespace} + if err := h.StatusHandler.AddVirtualServiceStatusEntry(h.UniqueName, newStatus); err != nil { + h.Log.Error(err, "error updating state for virtual service") + return err + } + + var newRoutes []*istioapi.HTTPRoute + for _, r := range service.Spec.Http { + if strings.HasPrefix(r.Name, prefix) { + // We never use our old definitions, we're always acting from scratch + // TODO: Not relevant in multiple services + continue + } + if h.hasMatchingHostAndSubset(serviceHost, r.Route, service.Namespace) { + for _, match := range h.DynamicEnv.Spec.IstioMatches { + ourSubsetRoute := r.DeepCopy() + if err := h.updateRouteForSubset(serviceHost, ourSubsetRoute, match, service.Namespace); err != nil { + h.Log.Error(err, "Adopting rule for dynamic environment") + return err + } + newRoutes = append(newRoutes, ourSubsetRoute) + } + } + newRoutes = append(newRoutes, r) + } + + service.Spec.Http = newRoutes + watches.AddToAnnotation(owner, service) + if err := h.Update(h.Ctx, service); err != nil { + h.Log.Error(err, "Error updating VirtualService with our updated rules") + return err + } + + return nil +} + +func (h *VirtualServiceHandler) getStatusForService(vs *istionetwork.VirtualService) riskifiedv1alpha1.ResourceStatus { + genStatus := func(s riskifiedv1alpha1.LifeCycleStatus) riskifiedv1alpha1.ResourceStatus { + return riskifiedv1alpha1.ResourceStatus{ + Name: vs.Name, + Namespace: vs.Namespace, + Status: s, + } + } + + // TODO: is this even possible? we validate existing routes when selecting virtual services... + if containsDynamicEnvRoutes(vs.Spec.Http, h.RoutePrefix) { + return genStatus(riskifiedv1alpha1.Running) + } else { + return genStatus(riskifiedv1alpha1.IgnoredMissingVS) + } +} + +// Note: the `inNamespace` parameter is the namespace to search for if the destination is not fully +// qualified (e.g.the namespace which contains the virtual service we're updating). +func (h *VirtualServiceHandler) updateRouteForSubset(serviceHost string, route *istioapi.HTTPRoute, match riskifiedv1alpha1.IstioMatch, inNamespace string) error { + newDestinations := h.filterDestinationByHostAndSubset(serviceHost, route.Route, inNamespace) + if len(newDestinations) == 0 { + return IgnoredMissing{} + } + for _, d := range newDestinations { + d.Destination.Subset = h.UniqueVersion + d.Weight = 0 + if d.Headers == nil { + d.Headers = &istioapi.Headers{} + } + safeApplyResponseHeaders(d.Headers, "x-dynamic-env", h.UniqueName) + } + route.Route = newDestinations + + if len(route.Match) == 0 { + // if we're handling default route than match is empty, however we need at + // least one match for merging out rules into + route.Match = []*istioapi.HTTPMatchRequest{{}} + } + for _, m := range route.Match { + if len(match.Headers) > 0 && m.Headers == nil { + m.Headers = make(map[string]*istioapi.StringMatch) + } + if len(match.SourceLabels) > 0 && m.SourceLabels == nil { + m.SourceLabels = make(map[string]string) + } + for k, v := range match.Headers { + if v.Exact != "" { + m.Headers[k] = &istioapi.StringMatch{MatchType: &istioapi.StringMatch_Exact{Exact: v.Exact}} + } else if v.Prefix != "" { + m.Headers[k] = &istioapi.StringMatch{MatchType: &istioapi.StringMatch_Prefix{Prefix: v.Prefix}} + } else { + m.Headers[k] = &istioapi.StringMatch{MatchType: &istioapi.StringMatch_Regex{Regex: v.Regex}} + } + } + for k, v := range match.SourceLabels { + m.SourceLabels[k] = v + } + } + newRuleHash := helpers.AsSha256(route) + route.Name = fmt.Sprintf("%s-%s", h.RoutePrefix, helpers.Shorten(newRuleHash, 10)) + + return nil +} + +func containsDynamicEnvRoutes(routes []*istioapi.HTTPRoute, prefix string) bool { + for _, r := range routes { + if strings.HasPrefix(r.Name, prefix) { + return true + } + } + return false +} + +// A helper to check if any of the provided routes has the serviceHost and namespace configured in +// our handler. The `inNamespace` parameter is the namespace to search if the `host` parameter is +// not fully qualified (e.g. the namespace of the virtual service). +func (h *VirtualServiceHandler) hasMatchingHostAndSubset(serviceHost string, routes []*istioapi.HTTPRouteDestination, inNamespace string) bool { + for _, route := range routes { + dest := route.Destination + if helpers.MatchNamespacedHost(serviceHost, h.Namespace, dest.Host, inNamespace) && dest.Subset == h.DefaultVersion { + return true + } + } + return false +} + +// Note: the `inNamespace` parameter is the namespace to search for if the destination is not fully +// qualified (e.g.the namespace which contains the virtual service we're processing). +func (h *VirtualServiceHandler) filterDestinationByHostAndSubset(serviceHost string, destinations []*istioapi.HTTPRouteDestination, inNamespace string) []*istioapi.HTTPRouteDestination { + var newDestinations []*istioapi.HTTPRouteDestination + + for _, d := range destinations { + if helpers.MatchNamespacedHost(serviceHost, h.Namespace, d.Destination.Host, inNamespace) && d.Destination.Subset == h.DefaultVersion { + newDestinations = append(newDestinations, d) + } + } + + return newDestinations +} + +func safeApplyResponseHeaders(headers *istioapi.Headers, key, value string) { + if headers.Response == nil { + headers.Response = &istioapi.Headers_HeaderOperations{} + } + currentResponseSet := headers.Response.GetAdd() + if currentResponseSet == nil { + currentResponseSet = make(map[string]string) + } + currentResponseSet[key] = value + headers.Response.Add = currentResponseSet +} diff --git a/pkg/handlers/virtualservice_handler_private_test.go b/pkg/handlers/virtualservice_handler_private_test.go new file mode 100644 index 0000000..d3bbe24 --- /dev/null +++ b/pkg/handlers/virtualservice_handler_private_test.go @@ -0,0 +1,210 @@ +package handlers + +// IMPORTANT: While it's generally bad practice to test private functions, I do it here in order to +// avoid overly specified tests from the outside. If these test fails (e.g. we changed +// implementation) just delete the failing tests (of-course, make sure you test the new +// functionality :) ). + +import ( + "encoding/json" + "fmt" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + istionetwork "istio.io/client-go/pkg/apis/networking/v1alpha3" + ctrl "sigs.k8s.io/controller-runtime" + + istioapi "istio.io/api/networking/v1alpha3" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var _ = Describe("VirtualService with Delegates", func() { + + const HOST1 = "my-host" + //const HOST2 = "my-other-host" + + mkVirtualServiceHandler := func(name, ns string, c client.Client) VirtualServiceHandler { + return VirtualServiceHandler{ + Client: c, + UniqueName: name, + Namespace: ns, + DefaultVersion: "shared", + Log: ctrl.Log, + } + } + + DescribeTable( + "Resolving VirtualServices and Delegates", + func( + serviceHost string, + data string, + expectedDelegates []*istioapi.Delegate, + expectedSelf bool, + ) { + mc := struct{ client.Client }{} + handler := mkVirtualServiceHandler("handler", "my-ns", &mc) + handler.ServiceHosts = []string{serviceHost} + vs := istionetwork.VirtualService{} + if err := json.Unmarshal([]byte(data), &vs); err != nil { + Fail(fmt.Sprintf("Error unmarshaling data: %s", err)) + } + s, ds := handler.resolveVirtualServices(serviceHost, &vs) + Expect(ds).To(HaveLen(len(expectedDelegates))) + for idx, delegate := range expectedDelegates { + Expect(delegate.Name).To(Equal(ds[idx].Name), "delegate name of index %d (%s) not equal to expected (%s)", idx, ds[idx].Name, delegate.Name) + Expect(delegate.Namespace).To(Equal(ds[idx].Namespace), "delegate ns of index %d (%s) not equal to expected (%s)", idx, ds[idx].Namespace, delegate.Namespace) + } + Expect(s).To(Equal(expectedSelf)) + }, + Entry( + "virtual service without delegates", + HOST1, + `{ + "apiVersion": "networking.istio.io/v1alpha3", + "kind": "VirtualService", + "metadata": { + "name": "my-name", + "namespace": "my-ns" + }, + "spec": { + "hosts": [ + "my-host" + ], + "http": [ + { + "route": [ + { + "destination": { + "host": "my-host", + "subset": "shared" + } + } + ] + } + ] + } + }`, + nil, + true, + ), + Entry( + "virtual service with only delegates", + HOST1, + `{ + "apiVersion": "networking.istio.io/v1alpha3", + "kind": "VirtualService", + "metadata": { + "name": "my-name", + "namespace": "my-ns" + }, + "spec": { + "hosts": [ + "my-host", + "my-other-host" + ], + "http": [ + { + "match": [ + { + "uri": null, + "prefix": "/reviews" + } + ], + "delegate": { + "name": "vs1", + "namespace": "my-ns" + } + }, + { + "match": [ + { + "uri": null, + "prefix": "/details" + } + ], + "delegate": { + "name": "vs2", + "namespace": "my-ns" + } + } + ] + } + }`, + []*istioapi.Delegate{ + { + Name: "vs1", + Namespace: "my-ns", + }, + { + Name: "vs2", + Namespace: "my-ns", + }, + }, + false, + ), + Entry( + "virtual service with both delegates and local routes", + HOST1, + `{ + "apiVersion": "networking.istio.io/v1alpha3", + "kind": "VirtualService", + "metadata": { + "name": "my-name", + "namespace": "my-ns" + }, + "spec": { + "hosts": [ + "my-host", + "my-other-host" + ], + "http": [ + { + "match": [ + { + "uri": null, + "prefix": "/reviews" + } + ], + "delegate": { + "name": "vs1", + "namespace": "my-ns" + } + }, + { + "match": [ + { + "uri": null, + "prefix": "/details" + } + ], + "delegate": { + "name": "vs2", + "namespace": "my-ns" + } + }, + { + "route": [ + { + "destination": { + "host": "my-host", + "subset": "shared" + } + } + ] + } + ] + } + }`, + []*istioapi.Delegate{ + { + Name: "vs1", + Namespace: "my-ns", + }, + { + Name: "vs2", + Namespace: "my-ns", + }, + }, + true, + ), + ) +}) diff --git a/pkg/handlers/virtualservice_handler_test.go b/pkg/handlers/virtualservice_handler_test.go new file mode 100644 index 0000000..d328e45 --- /dev/null +++ b/pkg/handlers/virtualservice_handler_test.go @@ -0,0 +1,131 @@ +package handlers_test + +import ( + "context" + + ctrl "sigs.k8s.io/controller-runtime" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + riskifiedv1alpha1 "github.com/riskified/dynamic-environment/api/v1alpha1" + "github.com/riskified/dynamic-environment/pkg/handlers" + "github.com/riskified/dynamic-environment/pkg/helpers" + "istio.io/api/networking/v1alpha3" + istionetwork "istio.io/client-go/pkg/apis/networking/v1alpha3" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var _ = Describe("VirtualServiceHandler", func() { + Context("GetStatus", func() { + mkVirtualServiceHandler := func(name, ns, version, prefix string, c client.Client) handlers.VirtualServiceHandler { + return handlers.VirtualServiceHandler{ + Client: c, + UniqueName: name, + Namespace: ns, + UniqueVersion: version, + DefaultVersion: "shared", + RoutePrefix: prefix, + Log: ctrl.Log, + } + } + + It("returns empty result if no virtual service found", func() { + mc := struct{ MockClient }{} + mc.listMethod = func(context.Context, client.ObjectList, ...client.ListOption) error { + return nil + } + handler := mkVirtualServiceHandler("unique", "ns", "_version", "_prefix", &mc) + var expected []riskifiedv1alpha1.ResourceStatus + result, err := handler.GetStatus() + Expect(err).To(BeNil()) + Expect(result).To(Equal(expected)) + }) + + It("returns 'ignored-missing-virtual-service' for virtual services that does not contain our routes", func() { + serviceName := "my-service-host" + mc := struct{ MockClient }{} + mc.listMethod = func(_ context.Context, o client.ObjectList, _ ...client.ListOption) error { + serviceResult := istionetwork.VirtualService{} + serviceResult.Name = "service" + serviceResult.Namespace = "ns" + serviceResult.Spec.Hosts = []string{serviceName} + serviceResult.Spec.Http = []*v1alpha3.HTTPRoute{ + { + Route: []*v1alpha3.HTTPRouteDestination{ + { + Destination: &v1alpha3.Destination{ + Host: serviceName, + Subset: "shared", + }, + }, + }, + }, + } + services := o.(*istionetwork.VirtualServiceList) + services.Items = []*istionetwork.VirtualService{ + &serviceResult, + } + return nil + } + version := "unique-version" + prefix := helpers.CalculateVirtualServicePrefix(version, "subset") + handler := mkVirtualServiceHandler("unique", "ns", version, prefix, mc) + handler.ServiceHosts = []string{"my-service-host"} + handler.UniqueVersion = "unique-version" + expected := []riskifiedv1alpha1.ResourceStatus{ + { + Name: "service", + Namespace: "ns", + Status: riskifiedv1alpha1.IgnoredMissingVS, + }, + } + result, err := handler.GetStatus() + Expect(err).To(BeNil()) + Expect(result).To(Equal(expected)) + }) + + It("returns 'running' for every service that contains our routing", func() { + serviceName := "my-service-host" + uniqueVersion := "unique-version" + prefix := helpers.CalculateVirtualServicePrefix(uniqueVersion, "subset") + mc := struct{ MockClient }{} + mc.listMethod = func(_ context.Context, o client.ObjectList, _ ...client.ListOption) error { + serviceResult := istionetwork.VirtualService{} + serviceResult.Name = "service" + serviceResult.Namespace = "ns" + serviceResult.Spec.Hosts = []string{serviceName} + serviceResult.Spec.Http = []*v1alpha3.HTTPRoute{ + { + Name: prefix + "abscdsdlkfj", + Route: []*v1alpha3.HTTPRouteDestination{ + { + Destination: &v1alpha3.Destination{ + Host: serviceName, + Subset: "shared", + }, + }, + }, + }, + } + services := o.(*istionetwork.VirtualServiceList) + services.Items = []*istionetwork.VirtualService{ + &serviceResult, + } + return nil + } + handler := mkVirtualServiceHandler("unique", "ns", uniqueVersion, prefix, &mc) + handler.ServiceHosts = []string{"my-service-host"} + handler.UniqueVersion = uniqueVersion + expected := []riskifiedv1alpha1.ResourceStatus{ + { + Name: "service", + Namespace: "ns", + Status: riskifiedv1alpha1.Running, + }, + } + result, err := handler.GetStatus() + Expect(err).To(BeNil()) + Expect(result).To(Equal(expected)) + }) + }) +}) diff --git a/pkg/helpers/helpers.go b/pkg/helpers/helpers.go new file mode 100644 index 0000000..d1e1bd8 --- /dev/null +++ b/pkg/helpers/helpers.go @@ -0,0 +1,112 @@ +package helpers + +import ( + "crypto/sha256" + "fmt" + "strings" + + riskifiedv1alpha1 "github.com/riskified/dynamic-environment/api/v1alpha1" + ctrl "sigs.k8s.io/controller-runtime" +) + +const ( + KeyValueHeaderSeparator = ":" + KeyValueHeaderConcatinator = "|" +) + +func IsStringSliceEqual(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + +func StringSliceContains(s string, strings []string) bool { + for _, item := range strings { + if item == s { + return true + } + } + return false +} + +func RemoveItemFromStringSlice(s string, slc []string) []string { + var result []string + for _, item := range slc { + if s != item { + result = append(result, item) + } + } + return result +} + +// Generates (somewhat) unique hash of a struct (or anything else). Depends on +// the object's `String` method. Copied from blog post: +// https://blog.8bitzen.com/posts/22-08-2019-how-to-hash-a-struct-in-go +func AsSha256(o interface{}) string { + h := sha256.New() + h.Write([]byte(fmt.Sprintf("%v", o))) + + return fmt.Sprintf("%x", h.Sum(nil)) +} + +// Shortens the provided string to the desired length +func Shorten(msg string, length int) string { + if len(msg) <= length { + return msg + } else { + return msg[0:length] + } +} + +func UniqueDynamicEnvName(name, namespace string) string { + if len(name) > 40 { + name = name[:40] + } + if len(namespace) > 20 { + namespace = namespace[:20] + } + return fmt.Sprintf("%s-%s", namespace, name) +} + +// This is a temporary hack so we should not care about edge cases: +// +//goland:noinspection ALL +func SerializeIstioMatchExactHeaders(headers map[string]riskifiedv1alpha1.StringMatch) string { + var serialized strings.Builder + for k, v := range headers { + if len(v.Exact) > 0 { + if serialized.Len() > 0 { + fmt.Fprint(&serialized, KeyValueHeaderConcatinator) + } + fmt.Fprintf(&serialized, "%s%s%s", k, KeyValueHeaderSeparator, v.Exact) + } else { + ctrl.Log.V(1).Info("Ignoring non-exact header while serializing match for envoy filters", "ignored-key", k) + } + } + return serialized.String() +} + +func HeadersContainsExactStringMatch(headers map[string]riskifiedv1alpha1.StringMatch) bool { + for _, h := range headers { + if len(h.Exact) > 0 { + return true + } + } + return false +} + +// Validate intersection between two slices is not empty/ +func CommonValueExists(l1, l2 []string) bool { + for _, s := range l2 { + if StringSliceContains(s, l1) { + return true + } + } + return false +} diff --git a/pkg/helpers/helpers_test.go b/pkg/helpers/helpers_test.go new file mode 100644 index 0000000..206648a --- /dev/null +++ b/pkg/helpers/helpers_test.go @@ -0,0 +1,52 @@ +package helpers + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + riskifiedv1alpha1 "github.com/riskified/dynamic-environment/api/v1alpha1" +) + +func TestHelpers(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Helpers Tests") +} + +var _ = Describe("Helpers Tests", func() { + Context("SerializationMatchExactHeaders", func() { + + var ( + multiple = map[string]riskifiedv1alpha1.StringMatch{ + "first-header": {Exact: "value"}, + "anotherheader": {Exact: "another-value"}, + } + empty = map[string]riskifiedv1alpha1.StringMatch{} + mixed = map[string]riskifiedv1alpha1.StringMatch{ + "first-header": {Exact: "value"}, + "anotherheader": {Prefix: "another-value"}, + "withregexp": {Prefix: "a-z"}, + } + ) + + It("Concatenates multiple exact headers", func() { + got := SerializeIstioMatchExactHeaders(multiple) + possibleResults := []string{ + "first-header:value|anotherheader:another-value", + "anotherheader:another-value|first-header:value", + } + Expect(possibleResults).To(ContainElement(got)) + }) + + It("Returns empty string if empty headers provided", func() { + got := SerializeIstioMatchExactHeaders(empty) + Expect(got).To(BeEmpty()) + }) + + It("Only serializes exact match and ignores other headers", func() { + got := SerializeIstioMatchExactHeaders(mixed) + Expect(got).To(Equal("first-header:value")) + }) + }) +}) diff --git a/pkg/helpers/k8s_helpers.go b/pkg/helpers/k8s_helpers.go new file mode 100644 index 0000000..9c0d296 --- /dev/null +++ b/pkg/helpers/k8s_helpers.go @@ -0,0 +1,63 @@ +package helpers + +import ( + "fmt" + + "github.com/riskified/dynamic-environment/pkg/names" + v1 "k8s.io/api/core/v1" +) + +// MatchNamespacedHost compares the provided `hostname` and `namespace` to the provided `matchHost`. +// If `matchHost` is *not* fully qualified, it uses the `inNamespace` parameter to match against the +// searched namespace. +func MatchNamespacedHost(hostname, namespace, matchedHost, inNamespace string) bool { + shortNameEqual := hostname == matchedHost && namespace == inNamespace + fqdnEqual := fmt.Sprint(hostname, ".", namespace, ".svc.cluster.local") == matchedHost + return shortNameEqual || fqdnEqual +} + +func MergeEnvVars(current []v1.EnvVar, overrides []v1.EnvVar) []v1.EnvVar { + var newEnv []v1.EnvVar + for _, env := range current { + exists, overridingEnv := fetchEnvVar(overrides, env.Name) + if exists { + newEnv = append(newEnv, overridingEnv) + } else { + newEnv = append(newEnv, *env.DeepCopy()) + } + } + for _, env := range overrides { + if !EnvVarContains(newEnv, env) { + newEnv = append(newEnv, *env.DeepCopy()) + } + } + return newEnv +} + +func EnvVarContains(envs []v1.EnvVar, env v1.EnvVar) bool { + for _, e := range envs { + if e == env { + return true + } + } + return false +} + +func fetchEnvVar(envs []v1.EnvVar, name string) (exists bool, env v1.EnvVar) { + for _, env := range envs { + if env.Name == name { + return true, *env.DeepCopy() + } + } + return false, v1.EnvVar{} +} + +// Calculates the prefix for naming virtual service route name prefix. Note that empty subset +// produces more inclusive prefix (e.g. everything belongs to this single dynamicenv). +func CalculateVirtualServicePrefix(version, subset string) string { + subsetPart := "" + if len(subset) > 0 { + subsetPart = subset + } + return fmt.Sprintf("%s-%s-%s", names.VirtualServiceRoutePrefix, version, subsetPart) +} diff --git a/pkg/helpers/k8s_helpers_test.go b/pkg/helpers/k8s_helpers_test.go new file mode 100644 index 0000000..bdefb3e --- /dev/null +++ b/pkg/helpers/k8s_helpers_test.go @@ -0,0 +1,48 @@ +package helpers_test + +import ( + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/riskified/dynamic-environment/pkg/helpers" + v1 "k8s.io/api/core/v1" +) + +var _ = Describe("Dynamic Environment Controller", func() { + + Context("Kubernetes Helpers", func() { + Context("MergeEnvVars", func() { + + var ( + notOverridden = v1.EnvVar{Name: "ENV_FIVE", Value: "original_value5"} + overrides = []v1.EnvVar{ + {Name: "ENV_ONE", Value: "override_value1"}, + {Name: "ENV_TWO", Value: "override_value2"}, + {Name: "ENV_THREE", Value: "override_value3"}, + {Name: "ENV_FOUR", Value: "override_value4"}, + } + current = []v1.EnvVar{ + {Name: "ENV_ONE", Value: "original_value1"}, + {Name: "ENV_TWO", Value: "original_value2"}, + {Name: "ENV_THREE", Value: "original_value3"}, + notOverridden, + } + ) + + It("overrides with supplied environment variables", func() { + r := helpers.MergeEnvVars(current, overrides) + Expect(r).To(HaveLen(5), "Seems that not all environment variables were merged") + for _, item := range overrides { + Expect(helpers.EnvVarContains(r, item)).To(BeTrue(), fmt.Sprintf("override %v does not exist", item)) + } + }) + + It("retains values that are not overridden", func() { + r := helpers.MergeEnvVars(current, overrides) + Expect(r).To(HaveLen(5), "Seems that not all environment variables were merged") + Expect(helpers.EnvVarContains(r, notOverridden)).To(BeTrue(), "should keep original value") + }) + }) + }) +}) diff --git a/pkg/metrics/collector.go b/pkg/metrics/collector.go new file mode 100644 index 0000000..48679e5 --- /dev/null +++ b/pkg/metrics/collector.go @@ -0,0 +1,77 @@ +package metrics + +import ( + "context" + + "github.com/prometheus/client_golang/prometheus" + riskifiedv1alpha1 "github.com/riskified/dynamic-environment/api/v1alpha1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + k8smetrics "sigs.k8s.io/controller-runtime/pkg/metrics" +) + +var ( + log = ctrl.Log.WithName("collector") + runningEnvironmentsDesc = prometheus.NewDesc( + "dynamic_environments", + "Number of deployed dynamic environments per namespace", + []string{"namespace"}, + nil, + ) + degradedEnvironmentsDesc = prometheus.NewDesc( + "degraded_dynamic_environments", + "Number of dynamic environments in degraded state per namespace", + []string{"namespace", "name"}, + nil, + ) +) + +type DynamicEnvCollector struct { + client.Client +} + +func (c *DynamicEnvCollector) Describe(descs chan<- *prometheus.Desc) { + log.Info("Called Describe ...") + descs <- runningEnvironmentsDesc + descs <- degradedEnvironmentsDesc +} + +func (c *DynamicEnvCollector) Collect(metrics chan<- prometheus.Metric) { + log.Info("called Collect ...") + desPerNamespace := make(map[string]float64) + ctx := context.Background() + des := &riskifiedv1alpha1.DynamicEnvList{} + if err := c.List(ctx, des, client.InNamespace("")); err != nil { + log.Error(err, "Error listing dynamic environments") + return + } + + for _, de := range des.Items { + desPerNamespace[de.Namespace] = desPerNamespace[de.Namespace] + 1 + if de.Status.State == riskifiedv1alpha1.Degraded { + metrics <- prometheus.MustNewConstMetric(degradedEnvironmentsDesc, prometheus.GaugeValue, 1, de.Namespace, de.Name) + } + } + for ns, val := range desPerNamespace { + metrics <- prometheus.MustNewConstMetric(runningEnvironmentsDesc, prometheus.GaugeValue, val, ns) + } +} + +func (c *DynamicEnvCollector) Start(ctx context.Context) error { + log.Info("Starting collector") + if err := k8smetrics.Registry.Register(c); err != nil { + return err + } + + // Block until the context is done. + <-ctx.Done() + k8smetrics.Registry.Unregister(c) + + log.Info("Ending collector") + return nil +} + +func (c *DynamicEnvCollector) SetupWithManager(mgr manager.Manager) error { + return mgr.Add(c) +} diff --git a/pkg/names/constants.go b/pkg/names/constants.go new file mode 100644 index 0000000..8ba9b55 --- /dev/null +++ b/pkg/names/constants.go @@ -0,0 +1,18 @@ +package names + +const ( + DynamicEnvLabel = "dynamic-env" + DefaultVersionLabel = "version" + DefaultVersion = "shared" + DeleteDeployments = "DeleteDeployments" + DeleteDestinationRules = "DeleteDestinationRules" + CleanupVirtualServices = "CleanupVirtualServices" + VirtualServiceRoutePrefix = "dynamic-environment" + MainServiceLabelKey = "purpose" + MainServiceLabelValue = "main" + DynamicEnvHeadersLabelKey = "dynamic-env-headers" + DynamicEnvHeadersLabelValue = "true" + IstioSideCarName = "istio-proxy" + IstioSideCarImage = "auto" + IstioSideCarHeaderEnvName = "EXACT_HEADERS_SERIALIZED" +) diff --git a/pkg/watches/enqueue_annotations.go b/pkg/watches/enqueue_annotations.go new file mode 100644 index 0000000..3c952af --- /dev/null +++ b/pkg/watches/enqueue_annotations.go @@ -0,0 +1,109 @@ +package watches + +import ( + "fmt" + "strings" + + "github.com/riskified/dynamic-environment/pkg/helpers" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/workqueue" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +const ( + // NamespacedNameAnnotation is an annotation which indicates who the dynamic environment owner of this resource is. + // The format is `/` with comma-separated values if there is more than one dynamic environment + NamespacedNameAnnotation = "riskified.com/dynamic-environment" +) + +type EnqueueRequestForAnnotation struct{} + +var _ handler.EventHandler = &EnqueueRequestForAnnotation{} + +// Create is called in response to an add event. +func (e *EnqueueRequestForAnnotation) Create(evt event.CreateEvent, q workqueue.RateLimitingInterface) { + addToQueue(evt.Object, q) +} + +// Update is called in response to an update event. +func (e *EnqueueRequestForAnnotation) Update(evt event.UpdateEvent, q workqueue.RateLimitingInterface) { + addToQueue(evt.ObjectNew, q) + addToQueue(evt.ObjectOld, q) +} + +// Delete is called in response to a delete event. +func (e *EnqueueRequestForAnnotation) Delete(evt event.DeleteEvent, q workqueue.RateLimitingInterface) { + addToQueue(evt.Object, q) +} + +// GenericFunc is called in response to a generic event. +func (e *EnqueueRequestForAnnotation) Generic(evt event.GenericEvent, q workqueue.RateLimitingInterface) { + addToQueue(evt.Object, q) +} + +// addToQueue converts annotations defined for NamespacedNameAnnotation as comma-separated list and add them to queue. +func addToQueue(object client.Object, q workqueue.RateLimitingInterface) { + annotations := object.GetAnnotations() + if annotations != nil { + dynamicEnvs := strings.Split(annotations[NamespacedNameAnnotation], ",") + for _, env := range dynamicEnvs { + if env != "" { + values := strings.SplitN(env, "/", 2) + q.Add(reconcile.Request{NamespacedName: types.NamespacedName{ + Name: values[1], + Namespace: values[0], + }}) + } + } + } +} + +// AddToAnnotation appends the current Dynamic environment to `NamespacedNameAnnotation` +func AddToAnnotation(owner types.NamespacedName, object client.Object) { + annotations := object.GetAnnotations() + if annotations == nil { + annotations = map[string]string{} + } + + existingDynamicEnvs := strings.Split(annotations[NamespacedNameAnnotation], ",") + currentDynamicEnv := fmt.Sprintf("%s/%s", owner.Namespace, owner.Name) + + if len(existingDynamicEnvs) == 1 && existingDynamicEnvs[0] == "" { + existingDynamicEnvs[0] = currentDynamicEnv + } else if !helpers.StringSliceContains(currentDynamicEnv, existingDynamicEnvs) { + existingDynamicEnvs = append(existingDynamicEnvs, currentDynamicEnv) + } + + annotations[NamespacedNameAnnotation] = strings.Join(existingDynamicEnvs, ",") + object.SetAnnotations(annotations) +} + +// RemoveFromAnnotation removes current Dynamic environment from `NamespacedNameAnnotation` +func RemoveFromAnnotation(owner types.NamespacedName, object client.Object) { + // Todo: Do we want to delete the annotation entirely if empty? + annotations := object.GetAnnotations() + if annotations == nil { + annotations = map[string]string{} + } + + existingDynamicEnvs := strings.Split(annotations[NamespacedNameAnnotation], ",") + currentDynamicEnv := fmt.Sprintf("%s/%s", owner.Namespace, owner.Name) + existingDynamicEnvs = helpers.RemoveItemFromStringSlice(currentDynamicEnv, existingDynamicEnvs) + + annotations[NamespacedNameAnnotation] = strings.Join(existingDynamicEnvs, ",") + object.SetAnnotations(annotations) +} + +// ContainsAnnotations checks whether the requested annotation already exists. +func ContainsAnnotation(searchItem types.NamespacedName, object client.Object) bool { + annotations := object.GetAnnotations() + if annotations == nil { + return false + } + existingAnnotations := strings.Split(annotations[NamespacedNameAnnotation], ",") + searchFor := fmt.Sprintf("%s/%s", searchItem.Namespace, searchItem.Name) + return helpers.StringSliceContains(searchFor, existingAnnotations) +}