From 420ecb121191d742926af6080a988acbbe672b9c Mon Sep 17 00:00:00 2001 From: Martin Weindel Date: Mon, 13 Jan 2025 10:42:25 +0100 Subject: [PATCH] add e2e test for deployment on Garden runtime cluster --- .gitignore | 1 + Makefile | 27 ++- .../templates/_helpers.tpl | 10 +- .../templates/crds.yaml | 6 + .../templates/deployment.yaml | 8 +- .../values.yaml | 1 + .../app/app.go | 4 +- example/controller-registration.yaml | 2 +- .../extension.extension.gardener.cloud.yaml | 8 + example/shoot-cert-service/extension.yaml | 39 ++++ example/shoot-cert-service/kustomization.yaml | 6 + go.mod | 71 +++--- go.sum | 136 +++++------ hack/check-skaffold-deps.sh | 26 +++ hack/test-e2e-provider-local.sh | 60 +++++ hack/update-skaffold-deps.sh | 11 + imagevector/images.yaml | 2 +- pkg/apis/config/types.go | 4 +- .../v1alpha1/zz_generated.conversion.go | 3 +- pkg/apis/config/zz_generated.deepcopy.go | 4 +- pkg/cmd/options.go | 4 +- pkg/controller/healthcheck/add.go | 5 +- .../runtimecluster/certificate/add.go | 2 +- .../runtimecluster/certificate/reconciler.go | 2 +- .../runtimecluster/garden/reconciler.go | 10 +- pkg/controller/shootcertservice/actuator.go | 69 +++++- pkg/controller/shootcertservice/add.go | 2 +- skaffold.yaml | 68 ++++++ test/e2e/garden/common.go | 219 ++++++++++++++++++ test/e2e/garden/create_delete.go | 127 ++++++++++ test/e2e/garden/e2e_suite_test.go | 17 ++ 31 files changed, 824 insertions(+), 130 deletions(-) create mode 100644 example/shoot-cert-service/extension.extension.gardener.cloud.yaml create mode 100644 example/shoot-cert-service/extension.yaml create mode 100644 example/shoot-cert-service/kustomization.yaml create mode 100755 hack/check-skaffold-deps.sh create mode 100755 hack/test-e2e-provider-local.sh create mode 100755 hack/update-skaffold-deps.sh create mode 100644 skaffold.yaml create mode 100644 test/e2e/garden/common.go create mode 100644 test/e2e/garden/create_delete.go create mode 100644 test/e2e/garden/e2e_suite_test.go diff --git a/.gitignore b/.gitignore index f90c6538..eb27d6a9 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /local **/dev /hack/tools/bin +/gardener *.coverprofile *.html diff --git a/Makefile b/Makefile index e580813a..b13db50a 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ REPO_ROOT := $(shell dirname $(realpath $(lastword $(MAKEFILE_ HACK_DIR := $(REPO_ROOT)/hack VERSION := $(shell cat "$(REPO_ROOT)/VERSION") EFFECTIVE_VERSION := $(VERSION)-$(shell git rev-parse HEAD) +BUILD_DATE := $(shell date '+%Y-%m-%dT%H:%M:%S%z' | sed 's/\([0-9][0-9]\)$$/:\1/g') LD_FLAGS := "-w $(shell bash $(GARDENER_HACK_DIR)/get-build-ld-flags.sh k8s.io/component-base $(REPO_ROOT)/VERSION $(EXTENSION_PREFIX))" LEADER_ELECTION := false IGNORE_OPERATION_ANNOTATION := true @@ -82,8 +83,9 @@ check-generate: .PHONY: check check: $(GOIMPORTS) $(GOLANGCI_LINT) $(HELM) - @bash $(GARDENER_HACK_DIR)/check.sh --golangci-lint-config=./.golangci.yaml ./cmd/... ./pkg/... ./test/... - @bash $(GARDENER_HACK_DIR)/check-charts.sh ./charts + #@bash $(GARDENER_HACK_DIR)/check.sh --golangci-lint-config=./.golangci.yaml ./cmd/... ./pkg/... ./test/... + #@bash $(GARDENER_HACK_DIR)/check-charts.sh ./charts + @GARDENER_HACK_DIR=$(GARDENER_HACK_DIR) $(REPO_ROOT)/hack/check-skaffold-deps.sh .PHONY: generate generate: $(CONTROLLER_GEN) $(GEN_CRD_API_REFERENCE_DOCS) $(HELM) $(MOCKGEN) $(YQ) $(VGOPATH) @@ -120,3 +122,24 @@ verify: check format test sast .PHONY: verify-extended verify-extended: check-generate check format test-cov test-clean sast-report + +.PHONY: test-e2e-local +test-e2e-local: $(KIND) $(YQ) $(GINKGO) + @$(REPO_ROOT)/hack/test-e2e-provider-local.sh --procs=3 + +.PHONY: extension-up +extension-up: export EXTENSION_VERSION = $(VERSION) +extension-up: export SKAFFOLD_DEFAULT_REPO = garden.local.gardener.cloud:5001 +extension-up: export SKAFFOLD_PUSH = true +extension-up: export SOURCE_DATE_EPOCH = $(shell date -d $(BUILD_DATE) +%s) +extension-up: export LD_FLAGS = $(shell bash $(GARDENER_HACK_DIR)/get-build-ld-flags.sh k8s.io/component-base $(REPO_ROOT)/VERSION gardener-extension-shoot-cert-service $(BUILD_DATE)) +extension-up: export EXTENSION_GARDENER_HACK_DIR = $(GARDENER_HACK_DIR) +extension-up: $(SKAFFOLD) $(HELM) $(KUBECTL) + $(SKAFFOLD) run --cache-artifacts=true + +extension-down: $(KUBECTL) + kubectl get extop extension-shoot-cert-service -oyaml | yq eval '.spec.deployment.extension.values.gardenerCertificates.seed.enabled = false' | kubectl replace -f - + kubectl delete mutatingwebhookconfigurations.admissionregistration.k8s.io gardener-extension-shoot-cert-service --ignore-not-found + kubectl -n garden annotate extension.extensions.gardener.cloud shoot-cert-service confirmation.gardener.cloud/deletion=true --overwrite || echo "ignored" + kubectl -n garden delete extension.extensions.gardener.cloud shoot-cert-service --ignore-not-found + $(SKAFFOLD) delete diff --git a/charts/gardener-extension-shoot-cert-service/templates/_helpers.tpl b/charts/gardener-extension-shoot-cert-service/templates/_helpers.tpl index 18a57d27..6d3e4008 100644 --- a/charts/gardener-extension-shoot-cert-service/templates/_helpers.tpl +++ b/charts/gardener-extension-shoot-cert-service/templates/_helpers.tpl @@ -67,10 +67,14 @@ ca: {{- end }} {{- define "image" -}} - {{- if hasPrefix "sha256:" .Values.image.tag }} - {{- printf "%s@%s" .Values.image.repository .Values.image.tag }} + {{- if .Values.skaffoldImage }} + {{- .Values.skaffoldImage }} {{- else }} - {{- printf "%s:%s" .Values.image.repository .Values.image.tag }} + {{- if hasPrefix "sha256:" .Values.image.tag }} + {{- printf "%s@%s" .Values.image.repository .Values.image.tag }} + {{- else }} + {{- printf "%s:%s" .Values.image.repository .Values.image.tag }} + {{- end }} {{- end }} {{- end }} diff --git a/charts/gardener-extension-shoot-cert-service/templates/crds.yaml b/charts/gardener-extension-shoot-cert-service/templates/crds.yaml index ca126a2b..da642305 100644 --- a/charts/gardener-extension-shoot-cert-service/templates/crds.yaml +++ b/charts/gardener-extension-shoot-cert-service/templates/crds.yaml @@ -3,6 +3,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: issuers.cert.gardener.cloud + annotations: + resources.gardener.cloud/keep-object: "true" labels: app.kubernetes.io/name: gardener-extension-shoot-cert-service app.kubernetes.io/instance: {{ .Release.Name }} @@ -215,6 +217,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: certificates.cert.gardener.cloud + annotations: + resources.gardener.cloud/keep-object: "true" labels: shoot.gardener.cloud/no-cleanup: "true" app.kubernetes.io/name: gardener-extension-shoot-cert-service @@ -616,6 +620,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: certificaterevocations.cert.gardener.cloud + annotations: + resources.gardener.cloud/keep-object: "true" labels: shoot.gardener.cloud/no-cleanup: "true" app.kubernetes.io/name: gardener-extension-shoot-cert-service diff --git a/charts/gardener-extension-shoot-cert-service/templates/deployment.yaml b/charts/gardener-extension-shoot-cert-service/templates/deployment.yaml index 8bc521d4..501dccae 100644 --- a/charts/gardener-extension-shoot-cert-service/templates/deployment.yaml +++ b/charts/gardener-extension-shoot-cert-service/templates/deployment.yaml @@ -33,6 +33,7 @@ spec: app.kubernetes.io/instance: {{ .Release.Name }} networking.gardener.cloud/to-runtime-apiserver: allowed networking.gardener.cloud/to-dns: allowed + networking.resources.gardener.cloud/to-all-istio-ingresses-istio-ingressgateway-tcp-9443: allowed spec: {{- if .Values.gardener.runtimeCluster.enabled }} priorityClassName: {{ .Values.gardener.runtimeCluster.priorityClassName }} @@ -44,8 +45,7 @@ spec: - name: gardener-extension-shoot-cert-service image: {{ include "image" . }} imagePullPolicy: {{ .Values.image.pullPolicy }} - command: - - /gardener-extension-shoot-cert-service + args: - --config=/etc/cert-service/config.yaml - --max-concurrent-reconciles={{ .Values.controllers.concurrentSyncs }} - --healthcheck-max-concurrent-reconciles={{ .Values.controllers.healthcheck.concurrentSyncs }} @@ -77,8 +77,12 @@ spec: value: "true" {{- end }} {{- if .Values.gardenerCertificates.seed.enabled }} + - name: SEED_NAME + value: {{ .Values.gardener.seed.name }} - name: SEED_INGRESS_DNS_DOMAIN value: {{ .Values.gardener.seed.ingressDomain }} + - name: SEED_DNS_SECRET_ROLE + value: {{ .Values.gardenerCertificates.seed.dnsSecretRole }} {{- end }} {{- if .Values.imageVectorOverwrite }} - name: IMAGEVECTOR_OVERWRITE diff --git a/charts/gardener-extension-shoot-cert-service/values.yaml b/charts/gardener-extension-shoot-cert-service/values.yaml index 23a4444e..51ab531c 100644 --- a/charts/gardener-extension-shoot-cert-service/values.yaml +++ b/charts/gardener-extension-shoot-cert-service/values.yaml @@ -89,6 +89,7 @@ disableControllers: [] gardenerCertificates: seed: enabled: false # if true, a special cert-controller-manager is deployed to provide the TLS certificate for the seed ingress domain + # dnsSecretRole: internal-domain # the "gardener.cloud/role" label to look up the DNS secret in the seed namespace on the virtual garden to be used for DNS challenges runtimeCluster: enabled: false # if true, a special cert-controller-manager is deployed to provide the TLS certificate for the virtual cluster kube-apiserver and garden ingress domain virtualKubeAPIServerIncludePrimaryDomain: false # if false, the first domain name of the virtual cluster kube-apiserver is excluded from TLS SNI config diff --git a/cmd/gardener-extension-shoot-cert-service/app/app.go b/cmd/gardener-extension-shoot-cert-service/app/app.go index c4be15de..1e8a00e1 100644 --- a/cmd/gardener-extension-shoot-cert-service/app/app.go +++ b/cmd/gardener-extension-shoot-cert-service/app/app.go @@ -15,7 +15,7 @@ import ( operatorv1alpha1 "github.com/gardener/gardener/pkg/apis/operator/v1alpha1" "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" - componentbaseconfig "k8s.io/component-base/config" + componentbaseconfigv1alpha1 "k8s.io/component-base/config/v1alpha1" "k8s.io/component-base/version/verflag" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/manager" @@ -58,7 +58,7 @@ func NewServiceControllerCommand() *cobra.Command { func (o *Options) run(ctx context.Context) error { // TODO: Make these flags configurable via command line parameters or component config file. - util.ApplyClientConnectionConfigurationToRESTConfig(&componentbaseconfig.ClientConnectionConfiguration{ + util.ApplyClientConnectionConfigurationToRESTConfig(&componentbaseconfigv1alpha1.ClientConnectionConfiguration{ QPS: 100.0, Burst: 130, }, o.restOptions.Completed().Config) diff --git a/example/controller-registration.yaml b/example/controller-registration.yaml index 46708cf0..093e7d06 100644 --- a/example/controller-registration.yaml +++ b/example/controller-registration.yaml @@ -4,7 +4,7 @@ kind: ControllerDeployment metadata: name: extension-shoot-cert-service helm: - rawChart: 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 + rawChart: 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 values: image: tag: v1.48.0-dev diff --git a/example/shoot-cert-service/extension.extension.gardener.cloud.yaml b/example/shoot-cert-service/extension.extension.gardener.cloud.yaml new file mode 100644 index 00000000..83f76063 --- /dev/null +++ b/example/shoot-cert-service/extension.extension.gardener.cloud.yaml @@ -0,0 +1,8 @@ +apiVersion: extensions.gardener.cloud/v1alpha1 +kind: Extension +metadata: + name: shoot-cert-service + namespace: garden +spec: + class: garden + type: shoot-cert-service diff --git a/example/shoot-cert-service/extension.yaml b/example/shoot-cert-service/extension.yaml new file mode 100644 index 00000000..c16a9b6a --- /dev/null +++ b/example/shoot-cert-service/extension.yaml @@ -0,0 +1,39 @@ +apiVersion: operator.gardener.cloud/v1alpha1 +kind: Extension +metadata: + name: extension-shoot-cert-service + annotations: + security.gardener.cloud/pod-security-enforce: baseline +spec: + resources: + - kind: Extension + globallyEnabled: true + type: shoot-cert-service + workerlessSupported: true + deployment: + extension: + runtimeClusterValues: + gardenerCertificates: + runtimeCluster: + enabled: true + certificateConfig: + defaultIssuer: + name: garden + acme: + server: https://acme-staging-v02.api.letsencrypt.org/directory + email: some.user@gardener.cloud + values: + gardenerCertificates: + seed: + enabled: true + dnsSecretRole: internal-domain + certificateConfig: + defaultIssuer: + name: garden + acme: + server: https://acme-staging-v02.api.letsencrypt.org/directory + email: some.user@gardener.cloud + helm: + ociRepository: + ref: local-skaffold/gardener-extension-shoot-cert-service/charts/extension:v0.0.0 + policy: Always \ No newline at end of file diff --git a/example/shoot-cert-service/kustomization.yaml b/example/shoot-cert-service/kustomization.yaml new file mode 100644 index 00000000..d7b703fc --- /dev/null +++ b/example/shoot-cert-service/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: +- extension.yaml +- extension.extension.gardener.cloud.yaml \ No newline at end of file diff --git a/go.mod b/go.mod index 8df0a4cd..f322fba2 100644 --- a/go.mod +++ b/go.mod @@ -6,25 +6,28 @@ toolchain go1.23.1 require ( github.com/ahmetb/gen-crd-api-reference-docs v0.3.0 - github.com/gardener/cert-management v0.17.1 - github.com/gardener/gardener v1.110.0 + github.com/gardener/cert-management v0.17.2 + github.com/gardener/gardener v1.110.2 github.com/go-logr/logr v1.4.2 github.com/onsi/ginkgo/v2 v2.22.0 github.com/onsi/gomega v1.36.0 github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 go.uber.org/mock v0.5.0 - golang.org/x/tools v0.28.0 + golang.org/x/tools v0.29.0 gomodules.xyz/jsonpatch/v2 v2.4.0 - k8s.io/api v0.31.3 - k8s.io/apimachinery v0.31.3 - k8s.io/client-go v0.31.3 - k8s.io/code-generator v0.31.3 - k8s.io/component-base v0.31.3 + k8s.io/api v0.31.4 + k8s.io/apimachinery v0.31.4 + k8s.io/client-go v0.31.4 + k8s.io/code-generator v0.31.4 + k8s.io/component-base v0.31.4 k8s.io/utils v0.0.0-20241210054802-24370beab758 - sigs.k8s.io/controller-runtime v0.19.3 + sigs.k8s.io/controller-runtime v0.19.4 ) +// TODO(MartinWeindel) remove if gardener v1.111.0 is released +replace github.com/gardener/gardener => github.com/gardener/gardener v1.73.1-0.20250112160428-50828b680ca0 + require ( dario.cat/mergo v1.0.1 // indirect github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect @@ -45,13 +48,13 @@ require ( github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/fluent/fluent-operator/v2 v2.9.0 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/gardener/controller-manager-library v0.2.1-0.20241104074533-80cbeddadabc // indirect + github.com/gardener/controller-manager-library v0.2.1-0.20241212154005-7de194a006b6 // indirect github.com/gardener/etcd-druid v0.25.0 // indirect - github.com/gardener/external-dns-management v0.22.1 // indirect + github.com/gardener/external-dns-management v0.22.2 // indirect github.com/gardener/machine-controller-manager v0.55.1 // indirect - github.com/go-acme/lego/v4 v4.20.4 // indirect + github.com/go-acme/lego/v4 v4.21.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.6 // indirect github.com/go-jose/go-jose/v4 v4.0.4 // indirect github.com/go-ldap/ldap/v3 v3.4.8 // indirect @@ -81,7 +84,7 @@ require ( github.com/ironcore-dev/vgopath v0.1.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/kubernetes-csi/external-snapshotter/client/v4 v4.2.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -111,7 +114,7 @@ require ( github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/afero v1.12.0 // indirect github.com/spf13/cast v1.7.0 // indirect github.com/spf13/viper v1.19.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect @@ -121,41 +124,41 @@ require ( github.com/xeipuuv/gojsonschema v1.2.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.31.0 // indirect - golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/net v0.33.0 // indirect - golang.org/x/oauth2 v0.24.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/oauth2 v0.25.0 // indirect golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/term v0.27.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect - golang.org/x/time v0.8.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 // indirect - google.golang.org/protobuf v1.35.2 // indirect + golang.org/x/time v0.9.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/protobuf v1.36.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - helm.sh/helm/v3 v3.16.3 // indirect - istio.io/api v1.23.3 // indirect + helm.sh/helm/v3 v3.16.4 // indirect + istio.io/api v1.23.4 // indirect istio.io/client-go v1.23.3 // indirect - k8s.io/apiextensions-apiserver v0.31.3 // indirect - k8s.io/apiserver v0.31.3 // indirect + k8s.io/apiextensions-apiserver v0.31.4 // indirect + k8s.io/apiserver v0.31.4 // indirect k8s.io/autoscaler/vertical-pod-autoscaler v1.2.1 // indirect - k8s.io/cluster-bootstrap v0.31.3 // indirect - k8s.io/component-helpers v0.31.3 // indirect + k8s.io/cluster-bootstrap v0.31.4 // indirect + k8s.io/component-helpers v0.31.4 // indirect k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 // indirect k8s.io/gengo/v2 v2.0.0-20240826214909-a7b603a56eb7 // indirect k8s.io/klog v1.0.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-aggregator v0.31.3 // indirect + k8s.io/kube-aggregator v0.31.4 // indirect k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38 // indirect - k8s.io/kubelet v0.31.3 // indirect - k8s.io/metrics v0.31.3 // indirect + k8s.io/kubelet v0.31.4 // indirect + k8s.io/metrics v0.31.4 // indirect sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20231015215740-bf15e44028f9 // indirect sigs.k8s.io/controller-tools v0.16.5 // indirect - sigs.k8s.io/gateway-api v1.2.0 // indirect + sigs.k8s.io/gateway-api v1.2.1 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.3 // indirect sigs.k8s.io/yaml v1.4.0 // indirect diff --git a/go.sum b/go.sum index c8f64833..df25a60c 100644 --- a/go.sum +++ b/go.sum @@ -97,25 +97,25 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/gardener/cert-management v0.17.1 h1:vawZGN+rsCRMviacnnMSWELbuIJsNXHaqaLbZ4hYADw= -github.com/gardener/cert-management v0.17.1/go.mod h1:cwSsyN935017HojKVuWqw2TBhiaxSisX132D9Tn+n9I= -github.com/gardener/controller-manager-library v0.2.1-0.20241104074533-80cbeddadabc h1:o4r1a4B2HKwkglGpO74FJ9XnrGK8NAqynTpvKTfapyI= -github.com/gardener/controller-manager-library v0.2.1-0.20241104074533-80cbeddadabc/go.mod h1:fyLOrcaKtGno4McZKW21b6QtwNghCF0IemTLKcwKZlM= +github.com/gardener/cert-management v0.17.2 h1:yM+iG2KNS5uzwzAqbvbpPdjpZ/YRotfK3bzHxDnelBY= +github.com/gardener/cert-management v0.17.2/go.mod h1:FvFlrSonrEHxPLkLF9o6aicSfFkJvcSBrVYmj5gpG2Y= +github.com/gardener/controller-manager-library v0.2.1-0.20241212154005-7de194a006b6 h1:/0hc8MLgN2MveT337C2bg1SNV5M/rkisIuxGwD09vnc= +github.com/gardener/controller-manager-library v0.2.1-0.20241212154005-7de194a006b6/go.mod h1:MY4/kbJSY5V+viMdHndNqwGzsKr1XvRwMmWViM8vj24= github.com/gardener/etcd-druid v0.25.0 h1:mR9/x5r27pO+I+XzpNcN2DDenam+7ITrhc7qKt9rbsI= github.com/gardener/etcd-druid v0.25.0/go.mod h1:6C0eyfdlw6CowLm/l4ZiKwrvkc+5NHrnc/rY2wCUwys= -github.com/gardener/external-dns-management v0.22.1 h1:WEwCDOersJ7ezeDJelbGVac1BTmEveJuds3JlJc84Xg= -github.com/gardener/external-dns-management v0.22.1/go.mod h1:2P7PamBPMKIOZMYRhl/VFhxZEBn4VUTdjESjKPxvOXA= -github.com/gardener/gardener v1.110.0 h1:Ix/NeYJyYIIDRHqO0126JYPGNVKy2kDEco7RyXuCYwo= -github.com/gardener/gardener v1.110.0/go.mod h1:Ge2wQMWm0NmQZP3L/WMejpfXsnGbfTFBEZud819P3vU= +github.com/gardener/external-dns-management v0.22.2 h1:caSPJBLFHv9Y95IAwk1HvarIUCjDccLcyuyjW1qqwhM= +github.com/gardener/external-dns-management v0.22.2/go.mod h1:adBY3qQ39Fvc2PvihP4xzEE5Y2//GuurMXQpKylMOJ4= +github.com/gardener/gardener v1.73.1-0.20250112160428-50828b680ca0 h1:m7UYHXtKt6RSlp5NVdHDQrKJlN4f36F/JZdyDMIp8tw= +github.com/gardener/gardener v1.73.1-0.20250112160428-50828b680ca0/go.mod h1:qXHddg2pf9KGTu9xlJiCVI9AWS0kQ0sqtPOXVZ+irow= github.com/gardener/machine-controller-manager v0.55.1 h1:d6mTnuYko+jWeIi7tAFWgWnL1nR5hGcI6pRCDcH0TGY= github.com/gardener/machine-controller-manager v0.55.1/go.mod h1:eCng7De6OE15rndmMm6Q1fwMQI39esASCd3WKZ/lLmY= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-acme/lego/v4 v4.20.4 h1:yCQGBX9jOfMbriEQUocdYm7EBapdTp8nLXYG8k6SqSU= -github.com/go-acme/lego/v4 v4.20.4/go.mod h1:foauPlhnhoq8WUphaWx5U04uDc+JGhk4ZZtPz/Vqsjg= +github.com/go-acme/lego/v4 v4.21.0 h1:arEW+8o5p7VI8Bk1kr/PDlgD1DrxtTH1gJ4b7mehL8o= +github.com/go-acme/lego/v4 v4.21.0/go.mod h1:HrSWzm3Ckj45Ie3i+p1zKVobbQoMOaGu9m4up0dUeDI= github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-asn1-ber/asn1-ber v1.5.6 h1:CYsqysemXfEaQbyrLJmdsCRuufHoLa3P/gGWGl5TDrM= github.com/go-asn1-ber/asn1-ber v1.5.6/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= @@ -259,8 +259,8 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= 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/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -365,8 +365,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= @@ -430,15 +430,15 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= 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-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0= -golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= 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= @@ -485,15 +485,15 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= 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.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= -golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= +golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 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= @@ -532,16 +532,16 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= 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= @@ -556,8 +556,8 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb 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.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= -golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -585,8 +585,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= -golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= +golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= +golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= 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= @@ -613,8 +613,8 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 h1:fVoAXEKA4+yufmbdVYv+SE73+cPZbbbe8paLsHfkK+U= -google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53/go.mod h1:riSXTwQ4+nqmPGtobMFyW5FqVAmIs0St6VPp4Ug7CE4= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= 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= @@ -630,8 +630,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 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.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= -google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 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= @@ -656,41 +656,41 @@ 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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -helm.sh/helm/v3 v3.16.3 h1:kb8bSxMeRJ+knsK/ovvlaVPfdis0X3/ZhYCSFRP+YmY= -helm.sh/helm/v3 v3.16.3/go.mod h1:zeVWGDR4JJgiRbT3AnNsjYaX8OTJlIE9zC+Q7F7iUSU= +helm.sh/helm/v3 v3.16.4 h1:rBn/h9MACw+QlhxQTjpl8Ifx+VTWaYsw3rguGBYBzr0= +helm.sh/helm/v3 v3.16.4/go.mod h1:k8QPotUt57wWbi90w3LNmg3/MWcLPigVv+0/X4B8BzA= 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= -istio.io/api v1.23.3 h1:+CP0AHz8/+WJ7ZKJLbilHEiqBCi5KLe1Yil9bJI39ow= -istio.io/api v1.23.3/go.mod h1:QPSTGXuIQdnZFEm3myf9NZ5uBMwCdJWUvfj9ZZ+2oBM= +istio.io/api v1.23.4 h1:fLlg+s4+tHiudb/EMbuQJATiIOgg3TEspEOA2pvnlWk= +istio.io/api v1.23.4/go.mod h1:QPSTGXuIQdnZFEm3myf9NZ5uBMwCdJWUvfj9ZZ+2oBM= istio.io/client-go v1.23.3 h1:rs+mO4A+NaXVcZgDO0RRZE7KRAlDooq2PSkxl7tevig= istio.io/client-go v1.23.3/go.mod h1:Lfa3anzx7/kCOpcAciR+JiRMj/SYuzDcbXQDjkThnLg= k8s.io/api v0.19.0/go.mod h1:I1K45XlvTrDjmj5LoM5LuP/KYrhWbjUKT/SoPG0qTjw= -k8s.io/api v0.31.3 h1:umzm5o8lFbdN/hIXbrK9oRpOproJO62CV1zqxXrLgk8= -k8s.io/api v0.31.3/go.mod h1:UJrkIp9pnMOI9K2nlL6vwpxRzzEX5sWgn8kGQe92kCE= -k8s.io/apiextensions-apiserver v0.31.3 h1:+GFGj2qFiU7rGCsA5o+p/rul1OQIq6oYpQw4+u+nciE= -k8s.io/apiextensions-apiserver v0.31.3/go.mod h1:2DSpFhUZZJmn/cr/RweH1cEVVbzFw9YBu4T+U3mf1e4= +k8s.io/api v0.31.4 h1:I2QNzitPVsPeLQvexMEsj945QumYraqv9m74isPDKhM= +k8s.io/api v0.31.4/go.mod h1:d+7vgXLvmcdT1BCo79VEgJxHHryww3V5np2OYTr6jdw= +k8s.io/apiextensions-apiserver v0.31.4 h1:FxbqzSvy92Ca9DIs5jqot883G0Ln/PGXfm/07t39LS0= +k8s.io/apiextensions-apiserver v0.31.4/go.mod h1:hIW9YU8UsqZqIWGG99/gsdIU0Ar45Qd3A12QOe/rvpg= k8s.io/apimachinery v0.19.0/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= -k8s.io/apimachinery v0.31.3 h1:6l0WhcYgasZ/wk9ktLq5vLaoXJJr5ts6lkaQzgeYPq4= -k8s.io/apimachinery v0.31.3/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= -k8s.io/apiserver v0.31.3 h1:+1oHTtCB+OheqFEz375D0IlzHZ5VeQKX1KGXnx+TTuY= -k8s.io/apiserver v0.31.3/go.mod h1:PrxVbebxrxQPFhJk4powDISIROkNMKHibTg9lTRQ0Qg= +k8s.io/apimachinery v0.31.4 h1:8xjE2C4CzhYVm9DGf60yohpNUh5AEBnPxCryPBECmlM= +k8s.io/apimachinery v0.31.4/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/apiserver v0.31.4 h1:JbtnTaXVYEAYIHJil6Wd74Wif9sd8jVcBw84kwEmp7o= +k8s.io/apiserver v0.31.4/go.mod h1:JJjoTjZ9PTMLdIFq7mmcJy2B9xLN3HeAUebW6xZyIP0= k8s.io/autoscaler/vertical-pod-autoscaler v1.2.1 h1:t5t0Rsn4b7iQfiVlGdWSEnEx8pjrSM96Sn4Dvo1QH/Q= k8s.io/autoscaler/vertical-pod-autoscaler v1.2.1/go.mod h1:9ywHbt0kTrLyeNGgTNm7WEns34PmBMEr+9bDKTxW6wQ= k8s.io/client-go v0.19.0/go.mod h1:H9E/VT95blcFQnlyShFgnFT9ZnJOAceiUHM3MlRC+mU= -k8s.io/client-go v0.31.3 h1:CAlZuM+PH2cm+86LOBemaJI/lQ5linJ6UFxKX/SoG+4= -k8s.io/client-go v0.31.3/go.mod h1:2CgjPUTpv3fE5dNygAr2NcM8nhHzXvxB8KL5gYc3kJs= -k8s.io/cluster-bootstrap v0.31.3 h1:O1Yxk1bLaxZvmQCXLaJjj5iJD+lVMfJdRUuKgbUHPlA= -k8s.io/cluster-bootstrap v0.31.3/go.mod h1:TI6TCsQQB4FfcryWgNO3SLXSKWBqHjx4DfyqSFwixj8= +k8s.io/client-go v0.31.4 h1:t4QEXt4jgHIkKKlx06+W3+1JOwAFU/2OPiOo7H92eRQ= +k8s.io/client-go v0.31.4/go.mod h1:kvuMro4sFYIa8sulL5Gi5GFqUPvfH2O/dXuKstbaaeg= +k8s.io/cluster-bootstrap v0.31.4 h1:/jLYowVtnU3OCkEUOsiqWduBsHJyz8CrW3aHjyB5t/8= +k8s.io/cluster-bootstrap v0.31.4/go.mod h1:J36a0uLKbTAYcJuf4k0oxcGvtA5iGGJFYqmnH+aaVTM= k8s.io/code-generator v0.19.0/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk= -k8s.io/code-generator v0.31.3 h1:Pj0fYOBms+ZrsulLi4DMsCEx1jG8fWKRLy44onHsLBI= -k8s.io/code-generator v0.31.3/go.mod h1:/umCIlT84g1+Yu5ZXtP1KGSRTnGiIzzX5AzUAxsNlts= -k8s.io/component-base v0.31.3 h1:DMCXXVx546Rfvhj+3cOm2EUxhS+EyztH423j+8sOwhQ= -k8s.io/component-base v0.31.3/go.mod h1:xME6BHfUOafRgT0rGVBGl7TuSg8Z9/deT7qq6w7qjIU= -k8s.io/component-helpers v0.31.3 h1:0zGPD2PrekhFWgmz85XxlMEl7dfhlKC1tERZDe3onQc= -k8s.io/component-helpers v0.31.3/go.mod h1:HZ1HZx2TKXM7xSUV2cR9L5yDoyZPhhHQNaE3BPBLPUQ= +k8s.io/code-generator v0.31.4 h1:Vu+8fKz+239rKiVDHFVHgjQ162cg5iUQPtTyQbwXeQw= +k8s.io/code-generator v0.31.4/go.mod h1:yMDt13Kn7m4MMZ4LxB1KBzdZjEyxzdT4b4qXq+lnI90= +k8s.io/component-base v0.31.4 h1:wCquJh4ul9O8nNBSB8N/o8+gbfu3BVQkVw9jAUY/Qtw= +k8s.io/component-base v0.31.4/go.mod h1:G4dgtf5BccwiDT9DdejK0qM6zTK0jwDGEKnCmb9+u/s= +k8s.io/component-helpers v0.31.4 h1:pqokuXozyWVrVBMmx0AMcKqNWqXhR00OZvpAE5hG5NM= +k8s.io/component-helpers v0.31.4/go.mod h1:Ddq5GYRK/1uNoPNgJh9N5osPutvBweQEcIG6b8kcvgQ= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20201203183100-97869a43a9d9/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= @@ -705,27 +705,27 @@ k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-aggregator v0.31.3 h1:DqHPdTglJHgOfB884AaroyxrML/aL82ASYOh65m7MSk= -k8s.io/kube-aggregator v0.31.3/go.mod h1:Kx59Xjnf0SnY47qf9Or++4y3XCHQ3kR0xk1Di6KFiFU= +k8s.io/kube-aggregator v0.31.4 h1:4hWVeNo4vLWstckMCo223cb9j7cCt7KD6b+RhQ8hTNE= +k8s.io/kube-aggregator v0.31.4/go.mod h1:R1wXjopE/VgW947R1axTzwEmyuatUp/a2lKn/ZGo2yo= k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38 h1:1dWzkmJrrprYvjGwh9kEUxmcUV/CtNU8QM7h1FLWQOo= k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38/go.mod h1:coRQXBK9NxO98XUv3ZD6AK3xzHCxV6+b7lrquKwaKzA= -k8s.io/kubelet v0.31.3 h1:DIXRAmvVGp42mV2vpA1GCLU6oO8who0/vp3Oq6kSpbI= -k8s.io/kubelet v0.31.3/go.mod h1:KSdbEfNy5VzqUlAHlytA/fH12s+sE1u8fb/8JY9sL/8= -k8s.io/metrics v0.31.3 h1:DkT9I3gFlb2/z+/4BMY7WrQ/PnbukuV4Yli82v/KBCM= -k8s.io/metrics v0.31.3/go.mod h1:2w9gpd8z+13oJmaPR6p3kDyrDqnxSyoKpnOw2qLIdhI= +k8s.io/kubelet v0.31.4 h1:6TokbMv+HnFG7Oe9tVS/J0VPGdC4GnsQZXuZoo7Ixi8= +k8s.io/kubelet v0.31.4/go.mod h1:8ZM5LZyANoVxUtmayUxD/nsl+6GjREo7kSanv8AoL4U= +k8s.io/metrics v0.31.4 h1:u6uCAUk+aYTf2RG1TCkjYJeGRlkTk24hIJammD7Fg/4= +k8s.io/metrics v0.31.4/go.mod h1:3S5m9eXJGhgEqH45t6f5pq7dbqpTbgcJvMfk9iEWlFM= k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -sigs.k8s.io/controller-runtime v0.19.3 h1:XO2GvC9OPftRst6xWCpTgBZO04S2cbp0Qqkj8bX1sPw= -sigs.k8s.io/controller-runtime v0.19.3/go.mod h1:j4j87DqtsThvwTv5/Tc5NFRyyF/RF0ip4+62tbTSIUM= +sigs.k8s.io/controller-runtime v0.19.4 h1:SUmheabttt0nx8uJtoII4oIP27BVVvAKFvdvGFwV/Qo= +sigs.k8s.io/controller-runtime v0.19.4/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20231015215740-bf15e44028f9 h1:O27fSMHw4u0h+Rj8bNzcZk5jY0iZCO0J8/mCpigpnbw= sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20231015215740-bf15e44028f9/go.mod h1:TF/lVLWS+JNNaVqJuDDictY2hZSXSsIHCx4FClMvqFg= sigs.k8s.io/controller-tools v0.16.5 h1:5k9FNRqziBPwqr17AMEPPV/En39ZBplLAdOwwQHruP4= sigs.k8s.io/controller-tools v0.16.5/go.mod h1:8vztuRVzs8IuuJqKqbXCSlXcw+lkAv/M2sTpg55qjMY= -sigs.k8s.io/gateway-api v1.2.0 h1:LrToiFwtqKTKZcZtoQPTuo3FxhrrhTgzQG0Te+YGSo8= -sigs.k8s.io/gateway-api v1.2.0/go.mod h1:EpNfEXNjiYfUJypf0eZ0P5iXA9ekSGWaS1WgPaM42X0= +sigs.k8s.io/gateway-api v1.2.1 h1:fZZ/+RyRb+Y5tGkwxFKuYuSRQHu9dZtbjenblleOLHM= +sigs.k8s.io/gateway-api v1.2.1/go.mod h1:EpNfEXNjiYfUJypf0eZ0P5iXA9ekSGWaS1WgPaM42X0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= diff --git a/hack/check-skaffold-deps.sh b/hack/check-skaffold-deps.sh new file mode 100755 index 00000000..ab839d7c --- /dev/null +++ b/hack/check-skaffold-deps.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# +# SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Gardener contributors +# +# SPDX-License-Identifier: Apache-2.0 + +set -e + +operation="${1:-check}" + +echo "> ${operation} Skaffold Dependencies" + +success=true + +function run() { + if ! bash "$GARDENER_HACK_DIR"/check-skaffold-deps-for-binary.sh "$operation" --skaffold-file "$1" --binary "$2" --skaffold-config "$3"; then + success=false + fi +} + +# skaffold.yaml +run "skaffold.yaml" "gardener-extension-shoot-cert-service" "shoot-cert-service" + +if ! $success ; then + exit 1 +fi diff --git a/hack/test-e2e-provider-local.sh b/hack/test-e2e-provider-local.sh new file mode 100755 index 00000000..71a60df3 --- /dev/null +++ b/hack/test-e2e-provider-local.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +# SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Gardener contributors +# +# SPDX-License-Identifier: Apache-2.0 + +set -o nounset +set -o pipefail +set -o errexit + +repo_root="$(readlink -f $(dirname ${0})/..)" + +if [[ ! -d "$repo_root/gardener" ]]; then + git clone https://github.com/gardener/gardener.git +fi + +gardener_version=$(go list -m -f '{{.Version}}' github.com/gardener/gardener) +gardener_version='50828b680ca0' # TODO(martinweindel): remove this line as soon as the gardener version is updated +cd "$repo_root/gardener" +git checkout "$gardener_version" +source "$repo_root/gardener/hack/ci-common.sh" + +echo ">>>>>>>>>>>>>>>>>>>> kind-operator-up" +make kind-operator-up +trap '{ + cd "$repo_root/gardener" + export_artifacts "gardener-local" + make kind-operator-down +}' EXIT +export KUBECONFIG=$repo_root/gardener/example/provider-local/seed-operator/base/kubeconfig +echo "<<<<<<<<<<<<<<<<<<<< kind-operator-up done" + +echo ">>>>>>>>>>>>>>>>>>>> operator-up" +make operator-up +echo "<<<<<<<<<<<<<<<<<<<< operator-up done" + +echo ">>>>>>>>>>>>>>>>>>>> operator-seed-up" +make operator-seed-up +echo "<<<<<<<<<<<<<<<<<<<< operator-seed-up done" + +cd $repo_root + +echo ">>>>>>>>>>>>>>>>>>>> extension-up" +make extension-up +echo "<<<<<<<<<<<<<<<<<<<< extension-up done" + +export REPO_ROOT=$repo_root + +# reduce flakiness in contended pipelines +export GOMEGA_DEFAULT_EVENTUALLY_TIMEOUT=5s +export GOMEGA_DEFAULT_EVENTUALLY_POLLING_INTERVAL=200ms +# if we're running low on resources, it might take longer for tested code to do something "wrong" +# poll for 5s to make sure, we're not missing any wrong action +export GOMEGA_DEFAULT_CONSISTENTLY_DURATION=5s +export GOMEGA_DEFAULT_CONSISTENTLY_POLLING_INTERVAL=200ms + +ginkgo --timeout=1h --v --progress "$@" $repo_root/test/e2e/... + +cd "$repo_root/gardener" +make kind-operator-down \ No newline at end of file diff --git a/hack/update-skaffold-deps.sh b/hack/update-skaffold-deps.sh new file mode 100755 index 00000000..a2bd7b7b --- /dev/null +++ b/hack/update-skaffold-deps.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# +# SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Gardener contributors +# +# SPDX-License-Identifier: Apache-2.0 + +set -e + +repo_root="$(git rev-parse --show-toplevel)" +export GARDENER_HACK_DIR="$(go list -m -f "{{.Dir}}" github.com/gardener/gardener)/hack" +$repo_root/hack/check-skaffold-deps.sh update diff --git a/imagevector/images.yaml b/imagevector/images.yaml index 63e434fa..9a02c6ad 100644 --- a/imagevector/images.yaml +++ b/imagevector/images.yaml @@ -2,4 +2,4 @@ images: - name: cert-management sourceRepository: github.com/gardener/cert-management repository: europe-docker.pkg.dev/gardener-project/releases/cert-controller-manager - tag: "v0.17.1" + tag: "v0.17.2" diff --git a/pkg/apis/config/types.go b/pkg/apis/config/types.go index e477bca2..ca654293 100644 --- a/pkg/apis/config/types.go +++ b/pkg/apis/config/types.go @@ -5,7 +5,7 @@ package config import ( - apisconfig "github.com/gardener/gardener/extensions/pkg/apis/config" + extensionsconfigv1alpha1 "github.com/gardener/gardener/extensions/pkg/apis/config/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -28,7 +28,7 @@ type Configuration struct { // CA contains CA related configuration. CA *CA // HealthCheckConfig is the config for the health check controller. - HealthCheckConfig *apisconfig.HealthCheckConfig + HealthCheckConfig *extensionsconfigv1alpha1.HealthCheckConfig // PrivateKeyDefaults default algorithm and sizes for certificate private keys. PrivateKeyDefaults *PrivateKeyDefaults } diff --git a/pkg/apis/config/v1alpha1/zz_generated.conversion.go b/pkg/apis/config/v1alpha1/zz_generated.conversion.go index 7da7dffd..ce089104 100644 --- a/pkg/apis/config/v1alpha1/zz_generated.conversion.go +++ b/pkg/apis/config/v1alpha1/zz_generated.conversion.go @@ -13,7 +13,6 @@ import ( unsafe "unsafe" config "github.com/gardener/gardener-extension-shoot-cert-service/pkg/apis/config" - apisconfig "github.com/gardener/gardener/extensions/pkg/apis/config" configv1alpha1 "github.com/gardener/gardener/extensions/pkg/apis/config/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" @@ -143,7 +142,7 @@ func autoConvert_v1alpha1_Configuration_To_config_Configuration(in *Configuratio out.ShootIssuers = (*config.ShootIssuers)(unsafe.Pointer(in.ShootIssuers)) out.ACME = (*config.ACME)(unsafe.Pointer(in.ACME)) out.CA = (*config.CA)(unsafe.Pointer(in.CA)) - out.HealthCheckConfig = (*apisconfig.HealthCheckConfig)(unsafe.Pointer(in.HealthCheckConfig)) + out.HealthCheckConfig = (*configv1alpha1.HealthCheckConfig)(unsafe.Pointer(in.HealthCheckConfig)) out.PrivateKeyDefaults = (*config.PrivateKeyDefaults)(unsafe.Pointer(in.PrivateKeyDefaults)) return nil } diff --git a/pkg/apis/config/zz_generated.deepcopy.go b/pkg/apis/config/zz_generated.deepcopy.go index 1ccc036d..a0897a25 100644 --- a/pkg/apis/config/zz_generated.deepcopy.go +++ b/pkg/apis/config/zz_generated.deepcopy.go @@ -10,7 +10,7 @@ package config import ( - apisconfig "github.com/gardener/gardener/extensions/pkg/apis/config" + v1alpha1 "github.com/gardener/gardener/extensions/pkg/apis/config/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -108,7 +108,7 @@ func (in *Configuration) DeepCopyInto(out *Configuration) { } if in.HealthCheckConfig != nil { in, out := &in.HealthCheckConfig, &out.HealthCheckConfig - *out = new(apisconfig.HealthCheckConfig) + *out = new(v1alpha1.HealthCheckConfig) (*in).DeepCopyInto(*out) } if in.PrivateKeyDefaults != nil { diff --git a/pkg/cmd/options.go b/pkg/cmd/options.go index 8f9fa1f9..a961431f 100644 --- a/pkg/cmd/options.go +++ b/pkg/cmd/options.go @@ -8,7 +8,7 @@ import ( "errors" "os" - extensionsapisconfig "github.com/gardener/gardener/extensions/pkg/apis/config" + extensionsapisconfigv1alpha1 "github.com/gardener/gardener/extensions/pkg/apis/config/v1alpha1" "github.com/gardener/gardener/extensions/pkg/controller/cmd" extensionshealthcheckcontroller "github.com/gardener/gardener/extensions/pkg/controller/healthcheck" extensionsheartbeatcontroller "github.com/gardener/gardener/extensions/pkg/controller/heartbeat" @@ -106,7 +106,7 @@ func ControllerSwitches() *cmd.SwitchOptions { ) } -func (c *CertificateServiceConfig) ApplyHealthCheckConfig(config *extensionsapisconfig.HealthCheckConfig) { +func (c *CertificateServiceConfig) ApplyHealthCheckConfig(config *extensionsapisconfigv1alpha1.HealthCheckConfig) { if c.config.HealthCheckConfig != nil { *config = *c.config.HealthCheckConfig } diff --git a/pkg/controller/healthcheck/add.go b/pkg/controller/healthcheck/add.go index ee3bb226..2fb3f0ab 100644 --- a/pkg/controller/healthcheck/add.go +++ b/pkg/controller/healthcheck/add.go @@ -8,7 +8,7 @@ import ( "context" "time" - apisconfig "github.com/gardener/gardener/extensions/pkg/apis/config" + extensionsconfigv1alpha1 "github.com/gardener/gardener/extensions/pkg/apis/config/v1alpha1" extensionscontroller "github.com/gardener/gardener/extensions/pkg/controller" "github.com/gardener/gardener/extensions/pkg/controller/healthcheck" "github.com/gardener/gardener/extensions/pkg/controller/healthcheck/general" @@ -28,7 +28,7 @@ var ( defaultSyncPeriod = time.Second * 30 // DefaultAddOptions are the default DefaultAddArgs for AddToManager. DefaultAddOptions = healthcheck.DefaultAddArgs{ - HealthCheckConfig: apisconfig.HealthCheckConfig{SyncPeriod: metav1.Duration{Duration: defaultSyncPeriod}}, + HealthCheckConfig: extensionsconfigv1alpha1.HealthCheckConfig{SyncPeriod: metav1.Duration{Duration: defaultSyncPeriod}}, } ) @@ -45,7 +45,6 @@ func RegisterHealthChecks(ctx context.Context, mgr manager.Manager, opts healthc } return healthcheck.DefaultRegistration( - ctx, shootcertservice.Type, extensionsv1alpha1.SchemeGroupVersion.WithKind(extensionsv1alpha1.ExtensionResource), func() client.ObjectList { return &extensionsv1alpha1.ExtensionList{} }, diff --git a/pkg/controller/runtimecluster/certificate/add.go b/pkg/controller/runtimecluster/certificate/add.go index d52daabe..4d0bcfba 100644 --- a/pkg/controller/runtimecluster/certificate/add.go +++ b/pkg/controller/runtimecluster/certificate/add.go @@ -61,7 +61,7 @@ func (r *Reconciler) AddToManager(mgr manager.Manager) error { extensionspredicate.IsInGardenNamespacePredicate, predicate.NewPredicateFuncs(func(obj client.Object) bool { return obj != nil && - obj.GetLabels()[v1beta1constants.GardenRole] == v1beta1constants.GardenRoleControlPlaneWildcardCert && + obj.GetLabels()[v1beta1constants.GardenRole] == v1beta1constants.GardenRoleGardenWildcardCert && obj.GetLabels()[ExtensionClassLabel] == string(extensionsv1alpha1.ExtensionClassGarden) }), )). diff --git a/pkg/controller/runtimecluster/certificate/reconciler.go b/pkg/controller/runtimecluster/certificate/reconciler.go index 595bc779..d158054d 100644 --- a/pkg/controller/runtimecluster/certificate/reconciler.go +++ b/pkg/controller/runtimecluster/certificate/reconciler.go @@ -73,7 +73,7 @@ func (r *Reconciler) reconcile( reconcile.Result, error, ) { - if cert.Status.State != "Ready" { + if cert.Status.State != "Ready" && cert.Annotations["service.cert.extensions.gardener.cloud/test-simulate-ready"] != "true" { log.Info("Certificate is not ready yet") return reconcile.Result{}, nil } diff --git a/pkg/controller/runtimecluster/garden/reconciler.go b/pkg/controller/runtimecluster/garden/reconciler.go index 5f9a4419..b8de0b2d 100644 --- a/pkg/controller/runtimecluster/garden/reconciler.go +++ b/pkg/controller/runtimecluster/garden/reconciler.go @@ -10,6 +10,7 @@ import ( "strings" certv1alpha1 "github.com/gardener/cert-management/pkg/apis/cert/v1alpha1" + "github.com/gardener/cert-management/pkg/cert/source" v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants" extensionsv1alpha1 "github.com/gardener/gardener/pkg/apis/extensions/v1alpha1" operatorv1alpha1 "github.com/gardener/gardener/pkg/apis/operator/v1alpha1" @@ -111,7 +112,7 @@ func (r *Reconciler) reconcile( result, err := controllerutils.CreateOrGetAndMergePatch(ctx, r.RuntimeClientSet.Client(), cert, func() error { cert.Spec.DNSNames = dnsNames cert.Spec.SecretLabels = map[string]string{ - v1beta1constants.GardenRole: v1beta1constants.GardenRoleControlPlaneWildcardCert, + v1beta1constants.GardenRole: v1beta1constants.GardenRoleGardenWildcardCert, certificate.ManagedByLabel: ControllerName + "-controller", certificate.ExtensionClassLabel: string(extensionsv1alpha1.ExtensionClassGarden), } @@ -122,8 +123,13 @@ func (r *Reconciler) reconcile( if cert.Annotations == nil { cert.Annotations = map[string]string{} } - cert.Annotations["cert.gardener.cloud/class"] = "garden" + cert.Annotations[source.AnnotClass] = "garden" cert.Annotations[certificate.TLSCertAPIServerNamesAnnotation] = strings.Join(apiServerNames, ",") + if garden.Spec.DNS != nil { + cert.Annotations[source.AnnotDNSRecordProviderType] = garden.Spec.DNS.Providers[0].Type + cert.Annotations[source.AnnotDNSRecordSecretRef] = garden.Spec.DNS.Providers[0].SecretRef.Name + cert.Annotations[source.AnnotDNSRecordClass] = string(extensionsv1alpha1.ExtensionClassGarden) + } if cert.Labels == nil { cert.Labels = map[string]string{} } diff --git a/pkg/controller/shootcertservice/actuator.go b/pkg/controller/shootcertservice/actuator.go index 2fd9b3a0..29aaf048 100644 --- a/pkg/controller/shootcertservice/actuator.go +++ b/pkg/controller/shootcertservice/actuator.go @@ -13,6 +13,7 @@ import ( "time" certv1alpha1 "github.com/gardener/cert-management/pkg/apis/cert/v1alpha1" + "github.com/gardener/cert-management/pkg/cert/source" "github.com/gardener/gardener/extensions/pkg/controller" "github.com/gardener/gardener/extensions/pkg/controller/extension" "github.com/gardener/gardener/extensions/pkg/util" @@ -20,9 +21,11 @@ import ( v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants" extensionsv1alpha1 "github.com/gardener/gardener/pkg/apis/extensions/v1alpha1" "github.com/gardener/gardener/pkg/chartrenderer" + "github.com/gardener/gardener/pkg/client/kubernetes" "github.com/gardener/gardener/pkg/controllerutils" "github.com/gardener/gardener/pkg/extensions" "github.com/gardener/gardener/pkg/utils/chart" + gardenerutils "github.com/gardener/gardener/pkg/utils/gardener" gutil "github.com/gardener/gardener/pkg/utils/gardener" kutil "github.com/gardener/gardener/pkg/utils/kubernetes" "github.com/gardener/gardener/pkg/utils/managedresources" @@ -52,8 +55,13 @@ const ( // ActuatorName is the name of the Certificate Service actuator. ActuatorName = "shoot-cert-service-actuator" + // EnvSeedName is the environment variable for the seed name. + EnvSeedName = "SEED_NAME" // EnvSeedIngressDNSDomain is the environment variable for the seed ingress DNS domain. EnvSeedIngressDNSDomain = "SEED_INGRESS_DNS_DOMAIN" + // EnvSeedDNSDomainSecretRole is the environment variable for the seed DNS domain secret role. + // This role is used to look up the DNS secret in the seed namespace on the virtual garden used for DNS Challenges + EnvSeedDNSDomainSecretRole = "SEED_DNS_SECRET_ROLE" ) // NewActuator returns an actuator responsible for Extension resources. @@ -61,6 +69,7 @@ func NewActuator(mgr manager.Manager, config config.Configuration, extensionClas return &actuator{ client: mgr.GetClient(), config: mgr.GetConfig(), + scheme: mgr.GetScheme(), decoder: serializer.NewCodecFactory(mgr.GetScheme(), serializer.EnableStrict).UniversalDecoder(), logger: log.Log.WithName(ActuatorName), serviceConfig: config, @@ -71,6 +80,7 @@ func NewActuator(mgr manager.Manager, config config.Configuration, extensionClas type actuator struct { client client.Client config *rest.Config + scheme *runtime.Scheme decoder runtime.Decoder extensionClass extensionsv1alpha1.ExtensionClass @@ -542,7 +552,14 @@ func (a *actuator) ensureSeedIngressWildcardCert(ctx context.Context, log logr.L if cert.Annotations == nil { cert.Annotations = map[string]string{} } - cert.Annotations["cert.gardener.cloud/class"] = "seed" + cert.Annotations[source.AnnotClass] = "seed" + dnsSecretRole := os.Getenv(EnvSeedDNSDomainSecretRole) + providerType, secretName, err := a.ensureSecretNameForSeedIngressWildcardCertDNSChallenge(ctx, log, dnsSecretRole) + if err != nil { + return fmt.Errorf("failed to lookup secret name for seed ingress wildcard cert DNS challenge: %w", err) + } + cert.Annotations[source.AnnotDNSRecordProviderType] = providerType + cert.Annotations[source.AnnotDNSRecordSecretRef] = secretName if cert.Labels == nil { cert.Labels = map[string]string{} } @@ -567,6 +584,56 @@ func (a *actuator) ensureSeedIngressWildcardCert(ctx context.Context, log logr.L return nil } +func (a *actuator) ensureSecretNameForSeedIngressWildcardCertDNSChallenge(ctx context.Context, log logr.Logger, role string) (string, string, error) { + if role == "" { + // assuming not configured, as no DNSChallenges needed + return "", "", nil + } + + gardenClient, err := a.createGardenClient() + if err != nil { + return "", "", err + } + seedNamespace := gardenerutils.ComputeGardenNamespace(os.Getenv(EnvSeedName)) + secretList := &corev1.SecretList{} + if err := gardenClient.List(ctx, secretList, client.InNamespace(seedNamespace), client.MatchingLabels{v1beta1constants.GardenRole: role}); err != nil { + return "", "", fmt.Errorf("failed to list secrets in seed: %w", err) + } + if len(secretList.Items) == 0 { + return "", "", fmt.Errorf("no secret found in seed namespace %s with role %s", seedNamespace, role) + } + if len(secretList.Items) > 1 { + return "", "", fmt.Errorf("multiple secrets found in seed namespace %s with role %s", seedNamespace, role) + } + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dns-challenge-secret", + Namespace: os.Getenv("LEADER_ELECTION_NAMESPACE"), + }, + } + secretName := secret.Namespace + "/" + secret.Name + result, err := controllerutils.GetAndCreateOrMergePatch(ctx, a.client, secret, func() error { + secret.Data = secretList.Items[0].Data + return nil + }) + if err != nil { + return "", "", fmt.Errorf("failed to create or update secret %s: %w", client.ObjectKeyFromObject(secret), err) + } + log.Info("DNS challenge secret processed", "result", result, "name", secretName) + providerType := secretList.Items[0].Annotations[gutil.DNSProvider] + return providerType, secretName, nil +} + +func (a *actuator) createGardenClient() (client.Client, error) { + restConfig, err := kubernetes.RESTConfigFromKubeconfigFile(gutil.PathGenericGardenKubeconfig, kubernetes.AuthTokenFile) + if err != nil { + return nil, fmt.Errorf("failed to read garden kubeconfig: %w", err) + } + return client.New(restConfig, client.Options{ + Scheme: a.scheme, + }) +} + func mergeServers(serversList ...string) string { existing := map[string]struct{}{} merged := []string{} diff --git a/pkg/controller/shootcertservice/add.go b/pkg/controller/shootcertservice/add.go index 7d307308..c1460a6d 100644 --- a/pkg/controller/shootcertservice/add.go +++ b/pkg/controller/shootcertservice/add.go @@ -75,7 +75,7 @@ func AddToManagerWithOptions(ctx context.Context, mgr manager.Manager, opts AddO // Trigger reconciliation for existing extensions in the deployment namespace on election. go triggerReconcileSpecialExtensionOnElection(ctx, mgr, opts.ExtensionClass) - return extension.Add(ctx, mgr, extension.AddArgs{ + return extension.Add(mgr, extension.AddArgs{ Actuator: NewActuator(mgr, opts.ServiceConfig.Configuration, opts.ExtensionClass), ControllerOptions: opts.ControllerOptions, Name: ControllerName, diff --git a/skaffold.yaml b/skaffold.yaml new file mode 100644 index 00000000..0c776d09 --- /dev/null +++ b/skaffold.yaml @@ -0,0 +1,68 @@ +apiVersion: skaffold/v4beta7 +kind: Config +metadata: + name: shoot-cert-service +build: + insecureRegistries: + - garden.local.gardener.cloud:5001 + tagPolicy: + customTemplate: + template: "{{.version}}-{{.sha}}" + components: + - name: version + envTemplate: + template: "{{.EXTENSION_VERSION}}" + - name: sha + gitCommit: + variant: AbbrevCommitSha + artifacts: + - image: local-skaffold/gardener-extension-shoot-cert-service + ko: + dependencies: + paths: + - charts + - charts/internal + - cmd/gardener-extension-shoot-cert-service + - cmd/gardener-extension-shoot-cert-service/app + - imagevector + - imagevector/images.yaml + - pkg/apis/config + - pkg/apis/config/v1alpha1 + - pkg/apis/config/validation + - pkg/apis/service + - pkg/apis/service/install + - pkg/apis/service/v1alpha1 + - pkg/apis/service/validation + - pkg/cmd + - pkg/controller/config + - pkg/controller/healthcheck + - pkg/controller/runtimecluster/certificate + - pkg/controller/runtimecluster/garden + - pkg/controller/shootcertservice + - pkg/webhook/sniconfig + - VERSION + ldflags: + - '{{.LD_FLAGS}}' + main: ./cmd/gardener-extension-shoot-cert-service + - image: local-skaffold/gardener-extension-shoot-cert-service/charts/extension + custom: + buildCommand: | + bash {{.EXTENSION_GARDENER_HACK_DIR}}/push-helm.sh charts/gardener-extension-shoot-cert-service .skaffoldImage + dependencies: + paths: + - charts/gardener-extension-shoot-cert-service + requires: + - image: local-skaffold/gardener-extension-shoot-cert-service + alias: IMG +resourceSelector: + allow: + # instruct skaffold to inject the built image reference into the image fields in our Extension object + - groupKind: Extension.operator.gardener.cloud + image: + - .spec.deployment.extension.helm.ociRepository.ref +manifests: + kustomize: + paths: + - example/shoot-cert-service +deploy: + kubectl: {} diff --git a/test/e2e/garden/common.go b/test/e2e/garden/common.go new file mode 100644 index 00000000..fa856701 --- /dev/null +++ b/test/e2e/garden/common.go @@ -0,0 +1,219 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package garden + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "os" + "os/exec" + "strings" + "time" + + certv1alpha1 "github.com/gardener/cert-management/pkg/apis/cert/v1alpha1" + gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1" + v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants" + extensionsv1alpha1 "github.com/gardener/gardener/pkg/apis/extensions/v1alpha1" + operatorv1alpha1 "github.com/gardener/gardener/pkg/apis/operator/v1alpha1" + "github.com/gardener/gardener/pkg/client/kubernetes" + "github.com/gardener/gardener/pkg/controllerutils" + "github.com/gardener/gardener/pkg/logger" + . "github.com/gardener/gardener/pkg/utils/test" + . "github.com/gardener/gardener/pkg/utils/test/matchers" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gstruct" + gomegatypes "github.com/onsi/gomega/types" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + kubernetesscheme "k8s.io/client-go/kubernetes/scheme" + componentbaseconfigv1alpha1 "k8s.io/component-base/config/v1alpha1" + "sigs.k8s.io/controller-runtime/pkg/client" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +const namespace = "garden" + +var ( + parentCtx context.Context + runtimeClient client.Client +) + +var _ = BeforeSuite(func() { + Expect(os.Getenv("KUBECONFIG")).NotTo(BeEmpty(), "KUBECONFIG must be set") + Expect(os.Getenv("REPO_ROOT")).NotTo(BeEmpty(), "REPO_ROOT must be set") + + logf.SetLogger(logger.MustNewZapLogger(logger.InfoLevel, logger.FormatJSON, zap.WriteTo(GinkgoWriter))) + + restConfig, err := kubernetes.RESTConfigFromClientConnectionConfiguration(&componentbaseconfigv1alpha1.ClientConnectionConfiguration{Kubeconfig: os.Getenv("KUBECONFIG")}, nil, kubernetes.AuthTokenFile, kubernetes.AuthClientCertificate) + Expect(err).NotTo(HaveOccurred()) + + scheme := runtime.NewScheme() + Expect(kubernetesscheme.AddToScheme(scheme)).To(Succeed()) + Expect(operatorv1alpha1.AddToScheme(scheme)).To(Succeed()) + Expect(extensionsv1alpha1.AddToScheme(scheme)).To(Succeed()) + Expect(certv1alpha1.AddToScheme(scheme)).To(Succeed()) + runtimeClient, err = client.New(restConfig, client.Options{Scheme: scheme}) + Expect(err).NotTo(HaveOccurred()) +}) + +var _ = BeforeEach(func() { + parentCtx = context.Background() +}) + +func waitForGardenToBeReconciled(ctx context.Context, garden *operatorv1alpha1.Garden) { + CEventually(ctx, func(g Gomega) gardencorev1beta1.LastOperationState { + g.Expect(runtimeClient.Get(ctx, client.ObjectKeyFromObject(garden), garden)).To(Succeed()) + if garden.Status.LastOperation == nil || garden.Status.ObservedGeneration != garden.Generation { + return "" + } + return garden.Status.LastOperation.State + }).WithPolling(2 * time.Second).Should(Equal(gardencorev1beta1.LastOperationStateSucceeded)) +} + +func waitForOperatorExtensionToBeReconciled( + ctx context.Context, + extension *operatorv1alpha1.Extension, + expectedRuntimeStatus, expectedVirtualStatus gardencorev1beta1.ConditionStatus, +) { + CEventually(ctx, func(g Gomega) []gardencorev1beta1.Condition { + g.Expect(runtimeClient.Get(ctx, client.ObjectKeyFromObject(extension), extension)).To(Succeed()) + if extension.Status.ObservedGeneration != extension.Generation { + return nil + } + + return extension.Status.Conditions + }).WithPolling(2 * time.Second).Should(ConsistOf(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(operatorv1alpha1.ExtensionInstalled), + "Status": Equal(gardencorev1beta1.ConditionTrue), + }), MatchFields(IgnoreExtras, Fields{ + "Type": Equal(operatorv1alpha1.ExtensionRequiredRuntime), + "Status": Equal(expectedRuntimeStatus), + }), MatchFields(IgnoreExtras, Fields{ + "Type": Equal(operatorv1alpha1.ExtensionRequiredVirtual), + "Status": Equal(expectedVirtualStatus), + }))) +} + +func waitForOperatorExtensionToBeDeleted(ctx context.Context, extension *operatorv1alpha1.Extension) { + CEventually(ctx, func() error { + return runtimeClient.Get(ctx, client.ObjectKeyFromObject(extension), extension) + }).WithPolling(2 * time.Second).Should(BeNotFoundError()) +} + +func waitForExtensionToBeReconciled(ctx context.Context, extension *extensionsv1alpha1.Extension) { + CEventually(ctx, func(g Gomega) gardencorev1beta1.LastOperationState { + g.Expect(runtimeClient.Get(ctx, client.ObjectKeyFromObject(extension), extension)).To(Succeed()) + if extension.Status.LastOperation == nil || extension.Status.ObservedGeneration != extension.Generation { + return "" + } + return extension.Status.LastOperation.State + }).WithPolling(2 * time.Second).Should(Equal(gardencorev1beta1.LastOperationStateSucceeded)) +} + +func waitForCertificateToBeReconciled(ctx context.Context, cert *certv1alpha1.Certificate, statusMatcher gomegatypes.GomegaMatcher) { + CEventually(ctx, func(g Gomega) certv1alpha1.CertificateStatus { + g.Expect(runtimeClient.Get(ctx, client.ObjectKeyFromObject(cert), cert)).To(Succeed()) + return cert.Status + }).WithPolling(2 * time.Second).Should(statusMatcher) +} + +func createDummyTLSSecret(ctx context.Context, certificate *certv1alpha1.Certificate) error { + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: certificate.Spec.SecretRef.Namespace, + Name: certificate.Spec.SecretRef.Name, + }, + } + _, err := controllerutils.GetAndCreateOrMergePatch(ctx, runtimeClient, secret, func() error { + if secret.Data == nil { + certPEM, keyPEM, err := createSelfSignedTLSSecret() + if err != nil { + return err + } + secret.Data = map[string][]byte{ + "tls.crt": certPEM, + "tls.key": keyPEM, + } + } + secret.Type = corev1.SecretTypeTLS + secret.Labels = certificate.Spec.SecretLabels + return nil + }) + return err +} + +func createSelfSignedTLSSecret() ([]byte, []byte, error) { + certPrivateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, nil, err + } + certPrivateKeyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(certPrivateKey)}) + template := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: "dummy", + }, + DNSNames: []string{"dummy"}, + NotBefore: time.Now(), + NotAfter: time.Now().Add(24 * time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + IsCA: true, + BasicConstraintsValid: true, + MaxPathLen: 0, + } + + certDerBytes, _ := x509.CreateCertificate(rand.Reader, &template, &template, certPrivateKey.Public(), certPrivateKey) + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDerBytes}) + return certPEM, certPrivateKeyPEM, nil +} + +func waitForVirtualGardenKubeAPIServerPatched(ctx context.Context) { + idFn := func(element interface{}) string { + return fmt.Sprintf("%v", element) + } + expectedArg := "--tls-sni-cert-key=/srv/kubernetes/tls-sni/shoot-cert-service-injected/tls.crt,/srv/kubernetes/tls-sni/shoot-cert-service-injected/tls.key:api.vg.local.gardener.cloud" + CEventually(ctx, func(g Gomega) []string { + deployment := &appsv1.Deployment{} + g.Expect(runtimeClient.Get(ctx, client.ObjectKey{Namespace: "garden", Name: "virtual-garden-kube-apiserver"}, deployment)).To(Succeed()) + return deployment.Spec.Template.Spec.Containers[0].Args + }).WithPolling(2 * time.Second).Should(MatchElements(idFn, IgnoreExtras, Elements{expectedArg: Equal(expectedArg)})) +} + +func getExtensionNamespace(ctx context.Context, controllerRegistrationName string) string { + namespaces := &corev1.NamespaceList{} + Expect(runtimeClient.List(ctx, namespaces, client.MatchingLabels{ + v1beta1constants.GardenRole: v1beta1constants.GardenRoleExtension, + v1beta1constants.LabelControllerRegistrationName: controllerRegistrationName, + })).To(Succeed()) + Expect(namespaces.Items).To(HaveLen(1)) + return namespaces.Items[0].Name +} + +// ExecMake executes one or multiple make targets. +func execMake(ctx context.Context, targets ...string) error { + cmd := exec.CommandContext(ctx, "make", targets...) + cmd.Dir = os.Getenv("REPO_ROOT") + for _, key := range []string{"PATH", "GOPATH", "HOME", "KUBECONFIG"} { + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, os.Getenv(key))) + } + cmdString := fmt.Sprintf("running make %s", strings.Join(targets, " ")) + logf.Log.Info(cmdString) + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("%s failed: %s\n%s", cmdString, err, string(output)) + } + return nil +} diff --git a/test/e2e/garden/create_delete.go b/test/e2e/garden/create_delete.go new file mode 100644 index 00000000..c4534e6d --- /dev/null +++ b/test/e2e/garden/create_delete.go @@ -0,0 +1,127 @@ +// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Gardener contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package garden + +import ( + "context" + "encoding/json" + "strings" + "time" + + certv1alpha1 "github.com/gardener/cert-management/pkg/apis/cert/v1alpha1" + gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1" + v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants" + extensionsv1alpha1 "github.com/gardener/gardener/pkg/apis/extensions/v1alpha1" + operatorv1alpha1 "github.com/gardener/gardener/pkg/apis/operator/v1alpha1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gstruct" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var _ = Describe("Shoot-Cert-Service Tests", func() { + var ( + garden = &operatorv1alpha1.Garden{ObjectMeta: metav1.ObjectMeta{Name: "local"}} + operatorExtension = &operatorv1alpha1.Extension{ObjectMeta: metav1.ObjectMeta{Name: "extension-shoot-cert-service"}} + runtimeExtension = &extensionsv1alpha1.Extension{ObjectMeta: metav1.ObjectMeta{Namespace: "garden", Name: "shoot-cert-service"}} + seedExtension = &extensionsv1alpha1.Extension{ObjectMeta: metav1.ObjectMeta{Name: "shoot-cert-service"}} + runtimeCertificate = &certv1alpha1.Certificate{ObjectMeta: metav1.ObjectMeta{Namespace: "garden", Name: "tls"}} + seedCertificate = &certv1alpha1.Certificate{ObjectMeta: metav1.ObjectMeta{Namespace: "garden", Name: "ingress-wildcard-cert"}} + ) + + It("Create, Delete", Label("simple"), func() { + By("Patch Garden") + ctx, cancel := context.WithTimeout(parentCtx, 15*time.Minute) + defer cancel() + + Expect(runtimeClient.Get(ctx, client.ObjectKeyFromObject(garden), garden)).To(Succeed()) + newDomainName := strings.ReplaceAll(garden.Spec.VirtualCluster.DNS.Domains[0].Name, "virtual-garden", "vg") + found := false + for _, domain := range garden.Spec.VirtualCluster.DNS.Domains { + if domain.Name == newDomainName { + found = true + break + } + } + if !found { + patch := client.MergeFrom(garden.DeepCopy()) + garden.Spec.VirtualCluster.DNS.Domains = append(garden.Spec.VirtualCluster.DNS.Domains, operatorv1alpha1.DNSDomain{ + Name: newDomainName, + Provider: garden.Spec.VirtualCluster.DNS.Domains[0].Provider, + }) + Expect(runtimeClient.Patch(ctx, garden, patch)).To(Succeed()) + } + waitForGardenToBeReconciled(ctx, garden) + + By("Deploy Extension") + Expect(execMake(ctx, "extension-up")).To(Succeed()) + + By("Check Operator Extension") + waitForOperatorExtensionToBeReconciled(ctx, operatorExtension, gardencorev1beta1.ConditionTrue, gardencorev1beta1.ConditionTrue) + + By("Check Garden Runtime Extension") + waitForExtensionToBeReconciled(ctx, runtimeExtension) + + By("Check Seed Extension") + seedExtension.Namespace = getExtensionNamespace(ctx, "extension-shoot-cert-service") + waitForExtensionToBeReconciled(ctx, runtimeExtension) + + By("Check Virtual Garden/Ingress TLS Certificate") + waitForCertificateToBeReconciled(ctx, runtimeCertificate, MatchFields(IgnoreExtras, Fields{ + "State": Equal("Error"), // Error as DNS Challenge is not possible in the test environment + "DNSNames": Equal([]string{"*.virtual-garden.local.gardener.cloud", "*.vg.local.gardener.cloud", "*.ingress.runtime-garden.local.gardener.cloud"}), + "Message": PointTo(ContainSubstring("Failed check: DNS record propagation")), + })) + + By("Check Seed Ingress TLS Certificate") + waitForCertificateToBeReconciled(ctx, seedCertificate, MatchFields(IgnoreExtras, Fields{ + "State": Equal("Error"), // Error as DNS Challenge is not possible in the test environment + "CommonName": PointTo(Equal("*.ingress.local.seed.local.gardener.cloud")), + "Message": PointTo(ContainSubstring("Failed check: DNS record propagation")), + })) + + By("Simulate Virtual Garden TLS Certificate Ready") + Expect(createDummyTLSSecret(ctx, runtimeCertificate)).To(Succeed()) + patch := client.MergeFrom(runtimeCertificate.DeepCopy()) + runtimeCertificate.Annotations["service.cert.extensions.gardener.cloud/test-simulate-ready"] = "true" + Expect(runtimeClient.Patch(ctx, runtimeCertificate, patch)).To(Succeed()) + + By("Wait for Virtual Garden Kube API Server") + waitForVirtualGardenKubeAPIServerPatched(ctx) + + By("Prepare de-installation of extension") + + // 1. Set the gardenerCertificates.seed.enabled to false,this will delete the extension in the extension namespace + patch = client.MergeFrom(operatorExtension.DeepCopy()) + extensionValues := map[string]any{} + Expect(json.Unmarshal(operatorExtension.Spec.Deployment.ExtensionDeployment.Values.Raw, &extensionValues)).To(Succeed()) + extensionValues["gardenerCertificates"].(map[string]any)["seed"].(map[string]any)["enabled"] = false + patchedValues, err := json.Marshal(extensionValues) + Expect(err).NotTo(HaveOccurred()) + operatorExtension.Spec.Deployment.ExtensionDeployment.Values.Raw = patchedValues + if operatorExtension.Annotations == nil { + operatorExtension.Annotations = map[string]string{} + } + Expect(runtimeClient.Patch(ctx, operatorExtension, patch)).To(Succeed()) + + // 2. Delete the extension in garden namespace + patch = client.MergeFrom(runtimeExtension.DeepCopy()) + runtimeExtension.Annotations[v1beta1constants.ConfirmationDeletion] = "true" + Expect(runtimeClient.Patch(ctx, runtimeExtension, patch)).To(Succeed()) + Expect(runtimeClient.Delete(ctx, runtimeExtension)).To(Succeed()) + + // 3. Delete the extension's webhook configuration + Expect(runtimeClient.Delete(ctx, &admissionregistrationv1.MutatingWebhookConfiguration{ObjectMeta: metav1.ObjectMeta{Name: "gardener-extension-shoot-cert-service"}})).To(Succeed()) + + By("Check Operator Extension") + waitForOperatorExtensionToBeReconciled(ctx, operatorExtension, gardencorev1beta1.ConditionFalse, gardencorev1beta1.ConditionFalse) + + By("Delete Extension") + Expect(runtimeClient.Delete(ctx, operatorExtension)).To(Succeed()) + waitForOperatorExtensionToBeDeleted(ctx, operatorExtension) + }) +}) diff --git a/test/e2e/garden/e2e_suite_test.go b/test/e2e/garden/e2e_suite_test.go new file mode 100644 index 00000000..94caee80 --- /dev/null +++ b/test/e2e/garden/e2e_suite_test.go @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Gardener contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package garden_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestE2E(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Shoot-Cert-Service Garden Runtime E2E Test Suite") +}