From 3284575a92254fc8c2823c40615c137ebb31b6aa Mon Sep 17 00:00:00 2001 From: achimweigel Date: Fri, 25 Aug 2023 11:09:10 +0200 Subject: [PATCH] Upgrade to landscaper v0.76.0 (#206) * upgrade to landscaper v0.76.0 (run-int-tests) * adapt to new webhook lib (run-int-tests) --- .../landscaper/deploy-execution.yaml | 2 +- .../component-references.yaml | 2 +- .../app/app.go | 5 +- go.mod | 6 +- go.sum | 12 +- integration-test/go.mod | 6 +- integration-test/go.sum | 12 +- .../apis/config/types_landscaper_config.go | 17 +- .../v1alpha1/types_landscaper_config.go | 17 +- .../v1alpha1/zz_generated.conversion.go | 36 +++ .../config/v1alpha1/zz_generated.deepcopy.go | 28 +- .../apis/config/zz_generated.deepcopy.go | 28 +- .../pkg/kubernetes/kubernetes.go | 34 +++ .../controller-utils/pkg/logging/logger.go | 22 +- integration-test/vendor/modules.txt | 6 +- .../controller-runtime/pkg/cache/cache.go | 5 + .../pkg/client/apiutil/restmapper.go | 6 + .../controller-runtime/pkg/client/client.go | 8 +- .../controller-runtime/pkg/client/options.go | 20 +- .../pkg/client/unstructured_client.go | 8 +- .../controller-runtime/pkg/cluster/cluster.go | 9 +- .../controller-runtime/pkg/manager/manager.go | 9 + .../apis/config/types_landscaper_config.go | 17 +- .../apis/config/zz_generated.deepcopy.go | 28 +- .../controller-utils/pkg/errors/errors.go | 55 ++++ .../pkg/kubernetes/kubernetes.go | 34 +++ .../controller-utils/pkg/logging/logger.go | 22 +- .../controller-utils/pkg/utils/utils.go | 10 + .../controller-utils/pkg/webhook/add.go | 187 ++++++++++++ .../pkg/webhook/certificates.go | 67 +++-- .../pkg/webhook/certificates/certificates.go | 2 +- .../controller-utils/pkg/webhook/config.go | 253 ++++++++++++++++ .../controller-utils/pkg/webhook/flags.go | 271 ++++++++++++++++++ .../controller-utils/pkg/webhook/registry.go | 118 ++++++++ .../controller-utils/pkg/webhook/utils.go | 70 +++++ .../controller-utils/pkg/webhook/webhook.go | 180 ++++++++++++ vendor/modules.txt | 8 +- .../controller-runtime/pkg/cache/cache.go | 5 + .../pkg/client/apiutil/restmapper.go | 6 + .../controller-runtime/pkg/client/client.go | 8 +- .../controller-runtime/pkg/client/options.go | 20 +- .../pkg/client/unstructured_client.go | 8 +- .../controller-runtime/pkg/cluster/cluster.go | 9 +- .../controller-runtime/pkg/manager/manager.go | 9 + 44 files changed, 1602 insertions(+), 83 deletions(-) create mode 100644 vendor/github.com/gardener/landscaper/controller-utils/pkg/errors/errors.go create mode 100644 vendor/github.com/gardener/landscaper/controller-utils/pkg/utils/utils.go create mode 100644 vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/add.go create mode 100644 vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/config.go create mode 100644 vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/flags.go create mode 100644 vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/registry.go create mode 100644 vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/utils.go create mode 100644 vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/webhook.go diff --git a/.landscaper/landscaper-instance/blueprint/landscaper/deploy-execution.yaml b/.landscaper/landscaper-instance/blueprint/landscaper/deploy-execution.yaml index b5eb69740..093a6636d 100644 --- a/.landscaper/landscaper-instance/blueprint/landscaper/deploy-execution.yaml +++ b/.landscaper/landscaper-instance/blueprint/landscaper/deploy-execution.yaml @@ -20,7 +20,7 @@ deployItems: resourceSelector: - apiVersion: landscaper.gardener.cloud/v1alpha1 kind: LsHealthCheck - name: {{ .imports.hostingClusterNamespace }} + name: landscaper namespace: {{ .imports.hostingClusterNamespace }} requirements: - jsonPath: .status diff --git a/.landscaper/landscaper-instance/component-references.yaml b/.landscaper/landscaper-instance/component-references.yaml index 9c24274d1..277134539 100644 --- a/.landscaper/landscaper-instance/component-references.yaml +++ b/.landscaper/landscaper-instance/component-references.yaml @@ -1,5 +1,5 @@ --- componentName: github.com/gardener/landscaper name: landscaper -version: v0.75.0 +version: v0.76.0 ... diff --git a/cmd/landscaper-service-webhooks-server/app/app.go b/cmd/landscaper-service-webhooks-server/app/app.go index 4f50014e6..1e62e7295 100644 --- a/cmd/landscaper-service-webhooks-server/app/app.go +++ b/cmd/landscaper-service-webhooks-server/app/app.go @@ -134,11 +134,14 @@ func registerWebhooks(ctx context.Context, // generate certificates var err error dnsNames := webhookcert.GeDNSNamesFromNamespacedName(wo.ServiceNamespace, wo.ServiceName) - wo.CABundle, err = webhookcert.GenerateCertificates(ctx, kubeClient, certDir, o.webhook.certificatesNamespace, "landscaper-service-webhook", "landscaper-service-webhook-cert", dnsNames) + caCert, _, err := webhookcert.GenerateCertificates(ctx, kubeClient, certDir, o.webhook.certificatesNamespace, + "landscaper-service-webhook", "landscaper-service-webhook-cert", dnsNames) if err != nil { return fmt.Errorf("unable to generate webhook certificates: %w", err) } + wo.CABundle = caCert.CertificatePEM + // log which resources are being watched webhookedResourcesLog := []string{} for _, elem := range wo.WebhookedResources { diff --git a/go.mod b/go.mod index 12d42bcae..53db4d232 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,8 @@ go 1.20 require ( github.com/gardener/component-spec/bindings-go v0.0.66 - github.com/gardener/landscaper/apis v0.75.0 - github.com/gardener/landscaper/controller-utils v0.75.0 + github.com/gardener/landscaper/apis v0.76.0 + github.com/gardener/landscaper/controller-utils v0.76.0 github.com/go-logr/logr v1.2.4 github.com/google/uuid v1.3.0 github.com/onsi/ginkgo v1.16.5 @@ -19,7 +19,7 @@ require ( k8s.io/code-generator v0.27.2 k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 - sigs.k8s.io/controller-runtime v0.15.0 + sigs.k8s.io/controller-runtime v0.15.1 ) require ( diff --git a/go.sum b/go.sum index f59759d6f..2c2234589 100644 --- a/go.sum +++ b/go.sum @@ -89,10 +89,10 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4 github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gardener/component-spec/bindings-go v0.0.66 h1:FvtnnTxXJi2ZCC/GgijUNJCcwwdlZAiEWpZTtyHviz0= github.com/gardener/component-spec/bindings-go v0.0.66/go.mod h1:qr7kADDXbXB0huul+ih/B43YkwyiMFYQepp/tqJ331c= -github.com/gardener/landscaper/apis v0.75.0 h1:4N+UG2rCqz6InttA7ncxvq2KeEiWRuhWOjHE1NKGwnI= -github.com/gardener/landscaper/apis v0.75.0/go.mod h1:MTl0cPmwUhYB2QUFiCUkVqmH/iWt4PJ55PTPbrXXEQo= -github.com/gardener/landscaper/controller-utils v0.75.0 h1:o18H1e4jram9KkR8asKgdmaQEk664gzvJ27lFWspt6A= -github.com/gardener/landscaper/controller-utils v0.75.0/go.mod h1:dWD1WsGePMAILOa37OOW7jACfiTMp8wz9rM3ATYXv5w= +github.com/gardener/landscaper/apis v0.76.0 h1:e4OQ2/lRtnSaaOIapZ5dYpfMwmHE5dgehCN6AJAqw1E= +github.com/gardener/landscaper/apis v0.76.0/go.mod h1:MTl0cPmwUhYB2QUFiCUkVqmH/iWt4PJ55PTPbrXXEQo= +github.com/gardener/landscaper/controller-utils v0.76.0 h1:VeJK38yuAbJ/FqqRXhnWaPwStcX9tpqMTHXPkDID2HQ= +github.com/gardener/landscaper/controller-utils v0.76.0/go.mod h1:DPP6H935DrtDtAPujx2LFyKU1X5xSCjJYaiIKFLwFu8= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -639,8 +639,8 @@ 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/apiserver-network-proxy/konnectivity-client v0.1.2 h1:trsWhjU5jZrx6UvFu4WzQDrN7Pga4a7Qg+zcfcj64PA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.2/go.mod h1:+qG7ISXqCDVVcyO8hLn12AKVYYUjM7ftlqsqmrhMZE0= -sigs.k8s.io/controller-runtime v0.15.0 h1:ML+5Adt3qZnMSYxZ7gAverBLNPSMQEibtzAgp0UPojU= -sigs.k8s.io/controller-runtime v0.15.0/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk= +sigs.k8s.io/controller-runtime v0.15.1 h1:9UvgKD4ZJGcj24vefUFgZFP3xej/3igL9BsOUTb/+4c= +sigs.k8s.io/controller-runtime v0.15.1/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk= 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.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= diff --git a/integration-test/go.mod b/integration-test/go.mod index cb3cb5f7e..22b1f684d 100644 --- a/integration-test/go.mod +++ b/integration-test/go.mod @@ -5,13 +5,13 @@ go 1.20 require ( github.com/gardener/component-spec/bindings-go v0.0.66 github.com/gardener/landscaper-service v0.0.0-00010101000000-000000000000 - github.com/gardener/landscaper/apis v0.75.0 - github.com/gardener/landscaper/controller-utils v0.75.0 + github.com/gardener/landscaper/apis v0.76.0 + github.com/gardener/landscaper/controller-utils v0.76.0 github.com/gardener/landscapercli v0.21.0 k8s.io/api v0.27.3 k8s.io/apimachinery v0.27.3 k8s.io/client-go v0.27.3 - sigs.k8s.io/controller-runtime v0.15.0 + sigs.k8s.io/controller-runtime v0.15.1 ) require ( diff --git a/integration-test/go.sum b/integration-test/go.sum index f1a2cf03d..d9be82982 100644 --- a/integration-test/go.sum +++ b/integration-test/go.sum @@ -47,10 +47,10 @@ github.com/gardener/component-spec/bindings-go v0.0.66/go.mod h1:qr7kADDXbXB0huu github.com/gardener/image-vector v0.10.0 h1:Ysg3hxfiGUG/doajiZ0nQuUaJYwfO5BZCOcijL3tRuo= github.com/gardener/landscaper v0.52.0 h1:jIaVtyGjS1OIoDdOXOYnF91hZA0q2YFC4h8LBfOSYbw= github.com/gardener/landscaper v0.52.0/go.mod h1:zQdeb1pC75ou9MVSos19MG1MSel5t9u11UWQO1SXK68= -github.com/gardener/landscaper/apis v0.75.0 h1:4N+UG2rCqz6InttA7ncxvq2KeEiWRuhWOjHE1NKGwnI= -github.com/gardener/landscaper/apis v0.75.0/go.mod h1:MTl0cPmwUhYB2QUFiCUkVqmH/iWt4PJ55PTPbrXXEQo= -github.com/gardener/landscaper/controller-utils v0.75.0 h1:o18H1e4jram9KkR8asKgdmaQEk664gzvJ27lFWspt6A= -github.com/gardener/landscaper/controller-utils v0.75.0/go.mod h1:dWD1WsGePMAILOa37OOW7jACfiTMp8wz9rM3ATYXv5w= +github.com/gardener/landscaper/apis v0.76.0 h1:e4OQ2/lRtnSaaOIapZ5dYpfMwmHE5dgehCN6AJAqw1E= +github.com/gardener/landscaper/apis v0.76.0/go.mod h1:MTl0cPmwUhYB2QUFiCUkVqmH/iWt4PJ55PTPbrXXEQo= +github.com/gardener/landscaper/controller-utils v0.76.0 h1:VeJK38yuAbJ/FqqRXhnWaPwStcX9tpqMTHXPkDID2HQ= +github.com/gardener/landscaper/controller-utils v0.76.0/go.mod h1:DPP6H935DrtDtAPujx2LFyKU1X5xSCjJYaiIKFLwFu8= github.com/gardener/landscapercli v0.21.0 h1:FbIVNMUUQod74uVbh79TiHLDNxbuE1LET8ukfvL8w1Y= github.com/gardener/landscapercli v0.21.0/go.mod h1:tZzrKBf8wfMHDZVk6qwO7tymrbC7OWIOAnanotv+ANU= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= @@ -363,8 +363,8 @@ k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5F k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.15.0 h1:ML+5Adt3qZnMSYxZ7gAverBLNPSMQEibtzAgp0UPojU= -sigs.k8s.io/controller-runtime v0.15.0/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk= +sigs.k8s.io/controller-runtime v0.15.1 h1:9UvgKD4ZJGcj24vefUFgZFP3xej/3igL9BsOUTb/+4c= +sigs.k8s.io/controller-runtime v0.15.1/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk= 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.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= diff --git a/integration-test/vendor/github.com/gardener/landscaper/apis/config/types_landscaper_config.go b/integration-test/vendor/github.com/gardener/landscaper/apis/config/types_landscaper_config.go index c97b23752..9fe36b6c8 100644 --- a/integration-test/vendor/github.com/gardener/landscaper/apis/config/types_landscaper_config.go +++ b/integration-test/vendor/github.com/gardener/landscaper/apis/config/types_landscaper_config.go @@ -45,8 +45,23 @@ type LandscaperConfiguration struct { // LsDeployments contains the names of the landscaper deployments. type LsDeployments struct { + // LsController is the name of the Landscaper controller deployment. LsController string - WebHook string + // LsController is the name of the Landscaper webhook server deployment. + WebHook string + // DeploymentsNamespace is the namespace in which the deployments are located. + DeploymentsNamespace string + // LsHealthCheckName is the name of the LsHealthCheck object. + LsHealthCheckName string + // AdditionalDeployments is the definition of additional deployments that shall be watched. + // +optional + AdditionalDeployments *AdditionalDeployments +} + +// AdditionalDeployments is the definition of additional deployments that shall be watched. +type AdditionalDeployments struct { + // Deployments is the list of deployments that shall be watched. + Deployments []string } // CommonControllerConfig describes common controller configuration that can be included in diff --git a/integration-test/vendor/github.com/gardener/landscaper/apis/config/v1alpha1/types_landscaper_config.go b/integration-test/vendor/github.com/gardener/landscaper/apis/config/v1alpha1/types_landscaper_config.go index bd97fe17a..e4976d38b 100644 --- a/integration-test/vendor/github.com/gardener/landscaper/apis/config/v1alpha1/types_landscaper_config.go +++ b/integration-test/vendor/github.com/gardener/landscaper/apis/config/v1alpha1/types_landscaper_config.go @@ -45,8 +45,23 @@ type LandscaperConfiguration struct { // LsDeployments contains the names of the landscaper deployments. type LsDeployments struct { + // LsController is the name of the Landscaper controller deployment. LsController string `json:"lsController"` - WebHook string `json:"webHook"` + // LsController is the name of the Landscaper webhook server deployment. + WebHook string `json:"webHook"` + // DeploymentsNamespace is the namespace in which the deployments are located. + DeploymentsNamespace string `json:"deploymentsNamespace"` + // LsHealthCheckName is the name of the LsHealthCheck object. + LsHealthCheckName string `json:"lsHealthCheckName"` + // AdditionalDeployments is the definition of additional deployments that shall be watched. + // +optional + AdditionalDeployments *AdditionalDeployments `json:"additionalDeployments,omitempty"` +} + +// AdditionalDeployments is the definition of additional deployments that shall be watched. +type AdditionalDeployments struct { + // Deployments is the list of deployments that shall be watched. + Deployments []string `json:"deployments"` } // CommonControllerConfig describes common controller configuration that can be included in diff --git a/integration-test/vendor/github.com/gardener/landscaper/apis/config/v1alpha1/zz_generated.conversion.go b/integration-test/vendor/github.com/gardener/landscaper/apis/config/v1alpha1/zz_generated.conversion.go index 60bad93f5..3f3a1c71e 100644 --- a/integration-test/vendor/github.com/gardener/landscaper/apis/config/v1alpha1/zz_generated.conversion.go +++ b/integration-test/vendor/github.com/gardener/landscaper/apis/config/v1alpha1/zz_generated.conversion.go @@ -30,6 +30,16 @@ func init() { // RegisterConversions adds conversion functions to the given scheme. // Public to allow building arbitrary schemes. func RegisterConversions(s *runtime.Scheme) error { + if err := s.AddGeneratedConversionFunc((*AdditionalDeployments)(nil), (*config.AdditionalDeployments)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_AdditionalDeployments_To_config_AdditionalDeployments(a.(*AdditionalDeployments), b.(*config.AdditionalDeployments), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*config.AdditionalDeployments)(nil), (*AdditionalDeployments)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_config_AdditionalDeployments_To_v1alpha1_AdditionalDeployments(a.(*config.AdditionalDeployments), b.(*AdditionalDeployments), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*AgentConfiguration)(nil), (*config.AgentConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha1_AgentConfiguration_To_config_AgentConfiguration(a.(*AgentConfiguration), b.(*config.AgentConfiguration), scope) }); err != nil { @@ -253,6 +263,26 @@ func RegisterConversions(s *runtime.Scheme) error { return nil } +func autoConvert_v1alpha1_AdditionalDeployments_To_config_AdditionalDeployments(in *AdditionalDeployments, out *config.AdditionalDeployments, s conversion.Scope) error { + out.Deployments = *(*[]string)(unsafe.Pointer(&in.Deployments)) + return nil +} + +// Convert_v1alpha1_AdditionalDeployments_To_config_AdditionalDeployments is an autogenerated conversion function. +func Convert_v1alpha1_AdditionalDeployments_To_config_AdditionalDeployments(in *AdditionalDeployments, out *config.AdditionalDeployments, s conversion.Scope) error { + return autoConvert_v1alpha1_AdditionalDeployments_To_config_AdditionalDeployments(in, out, s) +} + +func autoConvert_config_AdditionalDeployments_To_v1alpha1_AdditionalDeployments(in *config.AdditionalDeployments, out *AdditionalDeployments, s conversion.Scope) error { + out.Deployments = *(*[]string)(unsafe.Pointer(&in.Deployments)) + return nil +} + +// Convert_config_AdditionalDeployments_To_v1alpha1_AdditionalDeployments is an autogenerated conversion function. +func Convert_config_AdditionalDeployments_To_v1alpha1_AdditionalDeployments(in *config.AdditionalDeployments, out *AdditionalDeployments, s conversion.Scope) error { + return autoConvert_config_AdditionalDeployments_To_v1alpha1_AdditionalDeployments(in, out, s) +} + func autoConvert_v1alpha1_AgentConfiguration_To_config_AgentConfiguration(in *AgentConfiguration, out *config.AgentConfiguration, s conversion.Scope) error { out.Name = in.Name out.Namespace = in.Namespace @@ -740,6 +770,9 @@ func Convert_config_LocalRegistryConfiguration_To_v1alpha1_LocalRegistryConfigur func autoConvert_v1alpha1_LsDeployments_To_config_LsDeployments(in *LsDeployments, out *config.LsDeployments, s conversion.Scope) error { out.LsController = in.LsController out.WebHook = in.WebHook + out.DeploymentsNamespace = in.DeploymentsNamespace + out.LsHealthCheckName = in.LsHealthCheckName + out.AdditionalDeployments = (*config.AdditionalDeployments)(unsafe.Pointer(in.AdditionalDeployments)) return nil } @@ -751,6 +784,9 @@ func Convert_v1alpha1_LsDeployments_To_config_LsDeployments(in *LsDeployments, o func autoConvert_config_LsDeployments_To_v1alpha1_LsDeployments(in *config.LsDeployments, out *LsDeployments, s conversion.Scope) error { out.LsController = in.LsController out.WebHook = in.WebHook + out.DeploymentsNamespace = in.DeploymentsNamespace + out.LsHealthCheckName = in.LsHealthCheckName + out.AdditionalDeployments = (*AdditionalDeployments)(unsafe.Pointer(in.AdditionalDeployments)) return nil } diff --git a/integration-test/vendor/github.com/gardener/landscaper/apis/config/v1alpha1/zz_generated.deepcopy.go b/integration-test/vendor/github.com/gardener/landscaper/apis/config/v1alpha1/zz_generated.deepcopy.go index 87f940b62..e93faa989 100644 --- a/integration-test/vendor/github.com/gardener/landscaper/apis/config/v1alpha1/zz_generated.deepcopy.go +++ b/integration-test/vendor/github.com/gardener/landscaper/apis/config/v1alpha1/zz_generated.deepcopy.go @@ -17,6 +17,27 @@ import ( corev1alpha1 "github.com/gardener/landscaper/apis/core/v1alpha1" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdditionalDeployments) DeepCopyInto(out *AdditionalDeployments) { + *out = *in + if in.Deployments != nil { + in, out := &in.Deployments, &out.Deployments + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalDeployments. +func (in *AdditionalDeployments) DeepCopy() *AdditionalDeployments { + if in == nil { + return nil + } + out := new(AdditionalDeployments) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AgentConfiguration) DeepCopyInto(out *AgentConfiguration) { *out = *in @@ -370,7 +391,7 @@ func (in *LandscaperConfiguration) DeepCopyInto(out *LandscaperConfiguration) { if in.LsDeployments != nil { in, out := &in.LsDeployments, &out.LsDeployments *out = new(LsDeployments) - **out = **in + (*in).DeepCopyInto(*out) } return } @@ -412,6 +433,11 @@ func (in *LocalRegistryConfiguration) DeepCopy() *LocalRegistryConfiguration { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LsDeployments) DeepCopyInto(out *LsDeployments) { *out = *in + if in.AdditionalDeployments != nil { + in, out := &in.AdditionalDeployments, &out.AdditionalDeployments + *out = new(AdditionalDeployments) + (*in).DeepCopyInto(*out) + } return } diff --git a/integration-test/vendor/github.com/gardener/landscaper/apis/config/zz_generated.deepcopy.go b/integration-test/vendor/github.com/gardener/landscaper/apis/config/zz_generated.deepcopy.go index d9bd70d37..88e8e2d56 100644 --- a/integration-test/vendor/github.com/gardener/landscaper/apis/config/zz_generated.deepcopy.go +++ b/integration-test/vendor/github.com/gardener/landscaper/apis/config/zz_generated.deepcopy.go @@ -18,6 +18,27 @@ import ( v1alpha1 "github.com/gardener/landscaper/apis/core/v1alpha1" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdditionalDeployments) DeepCopyInto(out *AdditionalDeployments) { + *out = *in + if in.Deployments != nil { + in, out := &in.Deployments, &out.Deployments + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalDeployments. +func (in *AdditionalDeployments) DeepCopy() *AdditionalDeployments { + if in == nil { + return nil + } + out := new(AdditionalDeployments) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AgentConfiguration) DeepCopyInto(out *AgentConfiguration) { *out = *in @@ -367,7 +388,7 @@ func (in *LandscaperConfiguration) DeepCopyInto(out *LandscaperConfiguration) { if in.LsDeployments != nil { in, out := &in.LsDeployments, &out.LsDeployments *out = new(LsDeployments) - **out = **in + (*in).DeepCopyInto(*out) } return } @@ -409,6 +430,11 @@ func (in *LocalRegistryConfiguration) DeepCopy() *LocalRegistryConfiguration { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LsDeployments) DeepCopyInto(out *LsDeployments) { *out = *in + if in.AdditionalDeployments != nil { + in, out := &in.AdditionalDeployments, &out.AdditionalDeployments + *out = new(AdditionalDeployments) + (*in).DeepCopyInto(*out) + } return } diff --git a/integration-test/vendor/github.com/gardener/landscaper/controller-utils/pkg/kubernetes/kubernetes.go b/integration-test/vendor/github.com/gardener/landscaper/controller-utils/pkg/kubernetes/kubernetes.go index 57c20caa6..6a2691e85 100644 --- a/integration-test/vendor/github.com/gardener/landscaper/controller-utils/pkg/kubernetes/kubernetes.go +++ b/integration-test/vendor/github.com/gardener/landscaper/controller-utils/pkg/kubernetes/kubernetes.go @@ -682,3 +682,37 @@ func SetRequiredNestedFieldsFromObj(currObj, obj *unstructured.Unstructured) err return nil } + +// HasOwnerReference returns the index of the owner reference if the 'owned' object has a owner reference pointing to the 'owner' object. +// If not, -1 is returned. +// Note that name and uid are only compared if set in the owner object. This means that the function will return a positive index +// for an owner object with empty name and uid if the owned object contains a owner reference which fits just apiversion and kind. +// The scheme argument may be nil if the owners GVK is populated. +func HasOwnerReference(owned, owner client.Object, scheme *runtime.Scheme) (int, error) { + if owned == nil || owner == nil { + return -1, fmt.Errorf("neither dependent nor owner may be nil when checking for owner references") + } + if owner.GetNamespace() != owned.GetNamespace() && owner.GetNamespace() != "" { + // cross-namespace owner references are not possible + return -1, nil + } + gvk := owner.GetObjectKind().GroupVersionKind() + if gvk.Version == "" || gvk.Kind == "" { + var err error + gvk, err = apiutil.GVKForObject(owner, scheme) + if err != nil { + return -1, fmt.Errorf("unable to determine owner's GVK: %w", err) + } + } + gv := gvk.GroupVersion().String() + for idx, or := range owned.GetOwnerReferences() { + if (owner.GetName() != "" && or.Name != owner.GetName()) || + (owner.GetUID() != "" && or.UID != owner.GetUID()) || + (or.APIVersion != gv) || + (or.Kind != gvk.Kind) { + continue + } + return idx, nil + } + return -1, nil +} diff --git a/integration-test/vendor/github.com/gardener/landscaper/controller-utils/pkg/logging/logger.go b/integration-test/vendor/github.com/gardener/landscaper/controller-utils/pkg/logging/logger.go index 03d7ba350..5b8d8ab2d 100644 --- a/integration-test/vendor/github.com/gardener/landscaper/controller-utils/pkg/logging/logger.go +++ b/integration-test/vendor/github.com/gardener/landscaper/controller-utils/pkg/logging/logger.go @@ -247,12 +247,14 @@ func (l Logger) Logr() logr.Logger { // StartReconcileFromContext fetches the logger from the context and adds the reconciled resource. // It also logs a 'start reconcile' message. -func StartReconcileFromContext(ctx context.Context, req reconcile.Request) (Logger, error) { +// The returned context contains the enriched logger. +func StartReconcileFromContext(ctx context.Context, req reconcile.Request) (Logger, context.Context, error) { log, err := FromContext(ctx) if err != nil { - return Logger{}, fmt.Errorf("unable to get logger from context: %w", err) + return Logger{}, ctx, fmt.Errorf("unable to get logger from context: %w", err) } - return log.StartReconcile(req), nil + log, ctx = log.StartReconcileAndAddToContext(ctx, req) + return log, ctx, nil } // StartReconcile works like StartReconcileFromContext, but it is called on an existing logger instead of fetching one from the context. @@ -276,3 +278,17 @@ func MustStartReconcileFromContext(ctx context.Context, req reconcile.Request, k log, ctx := FromContextOrNew(ctx, keysAndValuesFallback, keysAndValues...) return log.StartReconcileAndAddToContext(ctx, req) } + +// WithValuesAndContext works like WithValues, but also adds the logger directly to a context and returns the new context. +func (l Logger) WithValuesAndContext(ctx context.Context, keysAndValues ...interface{}) (Logger, context.Context) { + log := l.WithValues(keysAndValues...) + ctx = NewContext(ctx, log) + return log, ctx +} + +// WithNameAndContext works like WithName, but also adds the logger directly to a context and returns the new context. +func (l Logger) WithNameAndContext(ctx context.Context, name string) (Logger, context.Context) { + log := l.WithName(name) + ctx = NewContext(ctx, log) + return log, ctx +} diff --git a/integration-test/vendor/modules.txt b/integration-test/vendor/modules.txt index 6943c0301..6aefb43ae 100644 --- a/integration-test/vendor/modules.txt +++ b/integration-test/vendor/modules.txt @@ -94,7 +94,7 @@ github.com/gardener/landscaper-service/pkg/apis/core github.com/gardener/landscaper-service/pkg/apis/core/v1alpha1 github.com/gardener/landscaper-service/pkg/apis/installation github.com/gardener/landscaper-service/pkg/utils -# github.com/gardener/landscaper/apis v0.75.0 +# github.com/gardener/landscaper/apis v0.76.0 ## explicit; go 1.20 github.com/gardener/landscaper/apis/config github.com/gardener/landscaper/apis/config/install @@ -116,7 +116,7 @@ github.com/gardener/landscaper/apis/errors github.com/gardener/landscaper/apis/mediatype github.com/gardener/landscaper/apis/schema github.com/gardener/landscaper/apis/utils -# github.com/gardener/landscaper/controller-utils v0.75.0 +# github.com/gardener/landscaper/controller-utils v0.76.0 ## explicit; go 1.20 github.com/gardener/landscaper/controller-utils/pkg/kubernetes github.com/gardener/landscaper/controller-utils/pkg/logging @@ -691,7 +691,7 @@ k8s.io/utils/net k8s.io/utils/pointer k8s.io/utils/strings/slices k8s.io/utils/trace -# sigs.k8s.io/controller-runtime v0.15.0 +# sigs.k8s.io/controller-runtime v0.15.1 ## explicit; go 1.20 sigs.k8s.io/controller-runtime/pkg/cache sigs.k8s.io/controller-runtime/pkg/cache/internal diff --git a/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/cache/cache.go b/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/cache/cache.go index f01de4381..760038704 100644 --- a/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/cache/cache.go +++ b/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/cache/cache.go @@ -236,6 +236,11 @@ func New(config *rest.Config, opts Options) (Cache, error) { } func defaultOpts(config *rest.Config, opts Options) (Options, error) { + config = rest.CopyConfig(config) + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + logger := log.WithName("setup") // Use the rest HTTP client for the provided config if unset diff --git a/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/restmapper.go b/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/restmapper.go index f14f8a9f5..e0ff72dc1 100644 --- a/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/restmapper.go +++ b/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/restmapper.go @@ -152,6 +152,12 @@ func (m *mapper) getMapper() meta.RESTMapper { // addKnownGroupAndReload reloads the mapper with updated information about missing API group. // versions can be specified for partial updates, for instance for v1beta1 version only. func (m *mapper) addKnownGroupAndReload(groupName string, versions ...string) error { + // versions will here be [""] if the forwarded Version value of + // GroupVersionResource (in calling method) was not specified. + if len(versions) == 1 && versions[0] == "" { + versions = nil + } + // If no specific versions are set by user, we will scan all available ones for the API group. // This operation requires 2 requests: /api and /apis, but only once. For all subsequent calls // this data will be taken from cache. diff --git a/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/client/client.go b/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/client/client.go index 21067b6f8..0d8b9fbe1 100644 --- a/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/client/client.go +++ b/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/client/client.go @@ -110,6 +110,11 @@ func newClient(config *rest.Config, options Options) (*client, error) { return nil, fmt.Errorf("must provide non-nil rest.Config to client.New") } + config = rest.CopyConfig(config) + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + if !options.WarningHandler.SuppressWarnings { // surface warnings logger := log.Log.WithName("KubeAPIWarningLogger") @@ -117,7 +122,6 @@ func newClient(config *rest.Config, options Options) (*client, error) { // is log.KubeAPIWarningLogger with deduplication enabled. // See log.KubeAPIWarningLoggerOptions for considerations // regarding deduplication. - config = rest.CopyConfig(config) config.WarningHandler = log.NewKubeAPIWarningLogger( logger, log.KubeAPIWarningLoggerOptions{ @@ -160,7 +164,7 @@ func newClient(config *rest.Config, options Options) (*client, error) { unstructuredResourceByType: make(map[schema.GroupVersionKind]*resourceMeta), } - rawMetaClient, err := metadata.NewForConfigAndClient(config, options.HTTPClient) + rawMetaClient, err := metadata.NewForConfigAndClient(metadata.ConfigFor(config), options.HTTPClient) if err != nil { return nil, fmt.Errorf("unable to construct metadata-only client for use as part of client: %w", err) } diff --git a/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/client/options.go b/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/client/options.go index 50a461f1c..d81bf25de 100644 --- a/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/client/options.go +++ b/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/client/options.go @@ -513,8 +513,15 @@ type MatchingLabels map[string]string // ApplyToList applies this configuration to the given list options. func (m MatchingLabels) ApplyToList(opts *ListOptions) { // TODO(directxman12): can we avoid reserializing this over and over? - sel := labels.SelectorFromValidatedSet(map[string]string(m)) - opts.LabelSelector = sel + if opts.LabelSelector == nil { + opts.LabelSelector = labels.NewSelector() + } + // If there's already a selector, we need to AND the two together. + noValidSel := labels.SelectorFromValidatedSet(map[string]string(m)) + reqs, _ := noValidSel.Requirements() + for _, req := range reqs { + opts.LabelSelector = opts.LabelSelector.Add(req) + } } // ApplyToDeleteAllOf applies this configuration to the given an List options. @@ -528,14 +535,17 @@ type HasLabels []string // ApplyToList applies this configuration to the given list options. func (m HasLabels) ApplyToList(opts *ListOptions) { - sel := labels.NewSelector() + if opts.LabelSelector == nil { + opts.LabelSelector = labels.NewSelector() + } + // TODO: ignore invalid labels will result in an empty selector. + // This is inconsistent to the that of MatchingLabels. for _, label := range m { r, err := labels.NewRequirement(label, selection.Exists, nil) if err == nil { - sel = sel.Add(*r) + opts.LabelSelector = opts.LabelSelector.Add(*r) } } - opts.LabelSelector = sel } // ApplyToDeleteAllOf applies this configuration to the given an List options. diff --git a/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/client/unstructured_client.go b/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/client/unstructured_client.go index b8d4146c9..0d9695178 100644 --- a/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/client/unstructured_client.go +++ b/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/client/unstructured_client.go @@ -224,11 +224,11 @@ func (uc *unstructuredClient) List(ctx context.Context, obj ObjectList, opts ... func (uc *unstructuredClient) GetSubResource(ctx context.Context, obj, subResourceObj Object, subResource string, opts ...SubResourceGetOption) error { if _, ok := obj.(runtime.Unstructured); !ok { - return fmt.Errorf("unstructured client did not understand object: %T", subResource) + return fmt.Errorf("unstructured client did not understand object: %T", obj) } if _, ok := subResourceObj.(runtime.Unstructured); !ok { - return fmt.Errorf("unstructured client did not understand object: %T", obj) + return fmt.Errorf("unstructured client did not understand object: %T", subResourceObj) } if subResourceObj.GetName() == "" { @@ -255,11 +255,11 @@ func (uc *unstructuredClient) GetSubResource(ctx context.Context, obj, subResour func (uc *unstructuredClient) CreateSubResource(ctx context.Context, obj, subResourceObj Object, subResource string, opts ...SubResourceCreateOption) error { if _, ok := obj.(runtime.Unstructured); !ok { - return fmt.Errorf("unstructured client did not understand object: %T", subResourceObj) + return fmt.Errorf("unstructured client did not understand object: %T", obj) } if _, ok := subResourceObj.(runtime.Unstructured); !ok { - return fmt.Errorf("unstructured client did not understand object: %T", obj) + return fmt.Errorf("unstructured client did not understand object: %T", subResourceObj) } if subResourceObj.GetName() == "" { diff --git a/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/cluster/cluster.go b/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/cluster/cluster.go index 7d00c3c4b..7ab76555b 100644 --- a/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/cluster/cluster.go +++ b/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/cluster/cluster.go @@ -179,6 +179,13 @@ func New(config *rest.Config, opts ...Option) (Cluster, error) { return nil, errors.New("must specify Config") } + originalConfig := config + + config = rest.CopyConfig(config) + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + options := Options{} for _, opt := range opts { opt(&options) @@ -275,7 +282,7 @@ func New(config *rest.Config, opts ...Option) (Cluster, error) { } return &cluster{ - config: config, + config: originalConfig, httpClient: options.HTTPClient, scheme: options.Scheme, cache: cache, diff --git a/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/manager/manager.go b/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/manager/manager.go index 7e65ef0c3..72a4a7801 100644 --- a/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/manager/manager.go +++ b/integration-test/vendor/sigs.k8s.io/controller-runtime/pkg/manager/manager.go @@ -19,6 +19,7 @@ package manager import ( "context" "crypto/tls" + "errors" "fmt" "net" "net/http" @@ -391,6 +392,9 @@ type LeaderElectionRunnable interface { // New returns a new Manager for creating Controllers. func New(config *rest.Config, options Options) (Manager, error) { + if config == nil { + return nil, errors.New("must specify Config") + } // Set default values for options fields options = setOptionsDefaults(options) @@ -412,6 +416,11 @@ func New(config *rest.Config, options Options) (Manager, error) { return nil, err } + config = rest.CopyConfig(config) + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + // Create the recorder provider to inject event recorders for the components. // TODO(directxman12): the log for the event provider should have a context (name, tags, etc) specific // to the particular controller that it's being injected into, rather than a generic one like is here. diff --git a/vendor/github.com/gardener/landscaper/apis/config/types_landscaper_config.go b/vendor/github.com/gardener/landscaper/apis/config/types_landscaper_config.go index c97b23752..9fe36b6c8 100644 --- a/vendor/github.com/gardener/landscaper/apis/config/types_landscaper_config.go +++ b/vendor/github.com/gardener/landscaper/apis/config/types_landscaper_config.go @@ -45,8 +45,23 @@ type LandscaperConfiguration struct { // LsDeployments contains the names of the landscaper deployments. type LsDeployments struct { + // LsController is the name of the Landscaper controller deployment. LsController string - WebHook string + // LsController is the name of the Landscaper webhook server deployment. + WebHook string + // DeploymentsNamespace is the namespace in which the deployments are located. + DeploymentsNamespace string + // LsHealthCheckName is the name of the LsHealthCheck object. + LsHealthCheckName string + // AdditionalDeployments is the definition of additional deployments that shall be watched. + // +optional + AdditionalDeployments *AdditionalDeployments +} + +// AdditionalDeployments is the definition of additional deployments that shall be watched. +type AdditionalDeployments struct { + // Deployments is the list of deployments that shall be watched. + Deployments []string } // CommonControllerConfig describes common controller configuration that can be included in diff --git a/vendor/github.com/gardener/landscaper/apis/config/zz_generated.deepcopy.go b/vendor/github.com/gardener/landscaper/apis/config/zz_generated.deepcopy.go index d9bd70d37..88e8e2d56 100644 --- a/vendor/github.com/gardener/landscaper/apis/config/zz_generated.deepcopy.go +++ b/vendor/github.com/gardener/landscaper/apis/config/zz_generated.deepcopy.go @@ -18,6 +18,27 @@ import ( v1alpha1 "github.com/gardener/landscaper/apis/core/v1alpha1" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdditionalDeployments) DeepCopyInto(out *AdditionalDeployments) { + *out = *in + if in.Deployments != nil { + in, out := &in.Deployments, &out.Deployments + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalDeployments. +func (in *AdditionalDeployments) DeepCopy() *AdditionalDeployments { + if in == nil { + return nil + } + out := new(AdditionalDeployments) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AgentConfiguration) DeepCopyInto(out *AgentConfiguration) { *out = *in @@ -367,7 +388,7 @@ func (in *LandscaperConfiguration) DeepCopyInto(out *LandscaperConfiguration) { if in.LsDeployments != nil { in, out := &in.LsDeployments, &out.LsDeployments *out = new(LsDeployments) - **out = **in + (*in).DeepCopyInto(*out) } return } @@ -409,6 +430,11 @@ func (in *LocalRegistryConfiguration) DeepCopy() *LocalRegistryConfiguration { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LsDeployments) DeepCopyInto(out *LsDeployments) { *out = *in + if in.AdditionalDeployments != nil { + in, out := &in.AdditionalDeployments, &out.AdditionalDeployments + *out = new(AdditionalDeployments) + (*in).DeepCopyInto(*out) + } return } diff --git a/vendor/github.com/gardener/landscaper/controller-utils/pkg/errors/errors.go b/vendor/github.com/gardener/landscaper/controller-utils/pkg/errors/errors.go new file mode 100644 index 000000000..320a0305b --- /dev/null +++ b/vendor/github.com/gardener/landscaper/controller-utils/pkg/errors/errors.go @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import ( + "errors" + "strings" +) + +// ErrorList is a helper struct for situations in which multiple errors should be returned as a single one. +type ErrorList struct { + Errs []error +} + +// NewErrorList creates a new ErrorList containing the provided errors. +func NewErrorList(errs ...error) *ErrorList { + res := &ErrorList{ + Errs: []error{}, + } + return res.Append(errs...) +} + +// Aggregate aggregates all errors in the ErrorList into a single error. +// Returns nil if the ErrorList is either nil or empty. +// If the list contains a single error, that error is returned. +// Otherwise, a new error is constructed by appending all contained errors' messages. +func (el *ErrorList) Aggregate() error { + if el == nil || len(el.Errs) == 0 { + return nil + } else if len(el.Errs) == 1 { + return el.Errs[0] + } + sb := strings.Builder{} + sb.WriteString("multiple errors occurred:") + for _, e := range el.Errs { + sb.WriteString("\n") + sb.WriteString(e.Error()) + } + return errors.New(sb.String()) +} + +// Append appends all given errors to the ErrorList. +// This modifies the receiver object. +// nil pointers in the arguments are ignored. +// Returns the receiver for chaining. +func (el *ErrorList) Append(errs ...error) *ErrorList { + for _, e := range errs { + if e != nil { + el.Errs = append(el.Errs, e) + } + } + return el +} diff --git a/vendor/github.com/gardener/landscaper/controller-utils/pkg/kubernetes/kubernetes.go b/vendor/github.com/gardener/landscaper/controller-utils/pkg/kubernetes/kubernetes.go index 57c20caa6..6a2691e85 100644 --- a/vendor/github.com/gardener/landscaper/controller-utils/pkg/kubernetes/kubernetes.go +++ b/vendor/github.com/gardener/landscaper/controller-utils/pkg/kubernetes/kubernetes.go @@ -682,3 +682,37 @@ func SetRequiredNestedFieldsFromObj(currObj, obj *unstructured.Unstructured) err return nil } + +// HasOwnerReference returns the index of the owner reference if the 'owned' object has a owner reference pointing to the 'owner' object. +// If not, -1 is returned. +// Note that name and uid are only compared if set in the owner object. This means that the function will return a positive index +// for an owner object with empty name and uid if the owned object contains a owner reference which fits just apiversion and kind. +// The scheme argument may be nil if the owners GVK is populated. +func HasOwnerReference(owned, owner client.Object, scheme *runtime.Scheme) (int, error) { + if owned == nil || owner == nil { + return -1, fmt.Errorf("neither dependent nor owner may be nil when checking for owner references") + } + if owner.GetNamespace() != owned.GetNamespace() && owner.GetNamespace() != "" { + // cross-namespace owner references are not possible + return -1, nil + } + gvk := owner.GetObjectKind().GroupVersionKind() + if gvk.Version == "" || gvk.Kind == "" { + var err error + gvk, err = apiutil.GVKForObject(owner, scheme) + if err != nil { + return -1, fmt.Errorf("unable to determine owner's GVK: %w", err) + } + } + gv := gvk.GroupVersion().String() + for idx, or := range owned.GetOwnerReferences() { + if (owner.GetName() != "" && or.Name != owner.GetName()) || + (owner.GetUID() != "" && or.UID != owner.GetUID()) || + (or.APIVersion != gv) || + (or.Kind != gvk.Kind) { + continue + } + return idx, nil + } + return -1, nil +} diff --git a/vendor/github.com/gardener/landscaper/controller-utils/pkg/logging/logger.go b/vendor/github.com/gardener/landscaper/controller-utils/pkg/logging/logger.go index 03d7ba350..5b8d8ab2d 100644 --- a/vendor/github.com/gardener/landscaper/controller-utils/pkg/logging/logger.go +++ b/vendor/github.com/gardener/landscaper/controller-utils/pkg/logging/logger.go @@ -247,12 +247,14 @@ func (l Logger) Logr() logr.Logger { // StartReconcileFromContext fetches the logger from the context and adds the reconciled resource. // It also logs a 'start reconcile' message. -func StartReconcileFromContext(ctx context.Context, req reconcile.Request) (Logger, error) { +// The returned context contains the enriched logger. +func StartReconcileFromContext(ctx context.Context, req reconcile.Request) (Logger, context.Context, error) { log, err := FromContext(ctx) if err != nil { - return Logger{}, fmt.Errorf("unable to get logger from context: %w", err) + return Logger{}, ctx, fmt.Errorf("unable to get logger from context: %w", err) } - return log.StartReconcile(req), nil + log, ctx = log.StartReconcileAndAddToContext(ctx, req) + return log, ctx, nil } // StartReconcile works like StartReconcileFromContext, but it is called on an existing logger instead of fetching one from the context. @@ -276,3 +278,17 @@ func MustStartReconcileFromContext(ctx context.Context, req reconcile.Request, k log, ctx := FromContextOrNew(ctx, keysAndValuesFallback, keysAndValues...) return log.StartReconcileAndAddToContext(ctx, req) } + +// WithValuesAndContext works like WithValues, but also adds the logger directly to a context and returns the new context. +func (l Logger) WithValuesAndContext(ctx context.Context, keysAndValues ...interface{}) (Logger, context.Context) { + log := l.WithValues(keysAndValues...) + ctx = NewContext(ctx, log) + return log, ctx +} + +// WithNameAndContext works like WithName, but also adds the logger directly to a context and returns the new context. +func (l Logger) WithNameAndContext(ctx context.Context, name string) (Logger, context.Context) { + log := l.WithName(name) + ctx = NewContext(ctx, log) + return log, ctx +} diff --git a/vendor/github.com/gardener/landscaper/controller-utils/pkg/utils/utils.go b/vendor/github.com/gardener/landscaper/controller-utils/pkg/utils/utils.go new file mode 100644 index 000000000..f10c6684b --- /dev/null +++ b/vendor/github.com/gardener/landscaper/controller-utils/pkg/utils/utils.go @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package utils + +// Ptr returns a pointer to the given object. +func Ptr[T any](value T) *T { + return &value +} diff --git a/vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/add.go b/vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/add.go new file mode 100644 index 000000000..3d45cc5cd --- /dev/null +++ b/vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/add.go @@ -0,0 +1,187 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package webhook + +import ( + "context" + "fmt" + + "sigs.k8s.io/controller-runtime/pkg/client" + ctrlwebhook "sigs.k8s.io/controller-runtime/pkg/webhook" + + "github.com/gardener/landscaper/controller-utils/pkg/logging" + lc "github.com/gardener/landscaper/controller-utils/pkg/logging/constants" + "github.com/gardener/landscaper/controller-utils/pkg/webhook/certificates" +) + +// WebhookNaming is a helper struct which stores naming rules for Validating-/MutatingWebhookConfigurations and their webhooks. +type WebhookNaming struct { + // Name is used as the name of the Validating-/MutatingWebhookConfiguration. + Name string + // WebhookSuffix is appended to the webhook names from the registered webhooks. + WebhookSuffix string +} + +// ApplyWebhooksOptions is a helper struct to organize the arguments to the ApplyWebhooks function. +type ApplyWebhooksOptions struct { + // NameValidating contains the naming rules for the ValidatingWebhookConfiguration, if any. + // If nil, no ValidatingWebhookConfiguration will be created or deleted. + NameValidating *WebhookNaming + // NameMutating contains the naming rules for the MutatingWebhookConfiguration, if any. + // If nil, no MutatingWebhookConfiguration will be created or deleted. + NameMutating *WebhookNaming + // Server is the webhook server to which the webhooks will be registered. + Server ctrlwebhook.Server + // Client is the k8s client, used to create the Validating-/MutatingWebhookConfiguration. + Client client.Client + // Registry is the webhook registry. + // Will be filtered with the disabled webhooks provided in the WebhookFlags object. + Registry WebhookRegistry + // Flags contains the configuration received via CLI flags. + // Note that the ApplyWebhooks function can only handle single clusters. + // For multi-cluster configurations, call the ApplyWebhooks function multiple times and pass in a single-cluster configuration, + // which can be generated by calling the WebhookFlag's ForSingleCluster method. + Flags *WebhookFlags + // CertName is the name to be used for the certificates. + // Will also be used as name for the secret, with a "-certs" suffix, so it has to adhere to the k8s naming rules for secrets. + // This field is only evaluated if the certificates are generated and not passed in. + CertName string + // CertDir is the path to the directory which stores the certificates. + // This has to be the same that was given to the webhook server. + // This field is only evaluated if the certificates are generated and not passed in. + CertDir string + // CACert is the CA certificate. + // Can be passed in if the certificates have been generated beforehand, otherwise it will be generated. + // Setting only one of CACert and ServerCert to nil is not supported at the moment, either both or none have to be specified. + CACert *certificates.Certificate + // ServerCert is the server certificate. + // Can be passed in if the certificates have been generated beforehand, otherwise it will be generated. + // Setting only one of CACert and ServerCert to nil is not supported at the moment, either both or none have to be specified. + ServerCert *certificates.Certificate +} + +// ApplyWebhooks is an auxiliary function which groups the commands that are usually required to make webhooks work in the cluster. +// This includes: +// - generating certificates, if the certificates in the options are nil +// - creating a MutatingWebhookConfiguration, if the registry contains any non-disabled mutating webhooks, deleting it otherwise +// - creating a ValidatingWebhookConfiguration, if the registry contains any non-disabled validating webhooks, deleting it otherwise +// - registering all non-disabled webhooks at the webhook server +// +// This function does not: +// - instantiate a webhook server, this has to happen before +// - start the webhook server, this has to be done afterwards +func ApplyWebhooks(ctx context.Context, opts *ApplyWebhooksOptions) error { + baseLog := logging.FromContextOrDiscard(ctx) + log := baseLog.WithName("webhookInit") + + if opts == nil { + return fmt.Errorf("invalid ApplyWebhooks options: must not be nil") + } + if opts.Flags == nil { + return fmt.Errorf("invalid ApplyWebhooks options: webhook flags must not be nil") + } + if opts.Flags.IsMultiCluster() { + return fmt.Errorf("ApplyWebhooks can only handle single-cluster webhook flags") + } + + // Certificate generation + if opts.CACert == nil { + log.Info("No certificates provided, checking for existing ones in the cluster or generating new ones") + if opts.CertName == "" { + return fmt.Errorf("invalid ApplyWebhooks options: cert name must be specified if the certificates are not provided") + } + var err error + var dnsNames []string + if opts.Flags.WebhookService != nil { + dnsNames = GeDNSNamesFromNamespacedName(opts.Flags.WebhookService.Namespace, opts.Flags.WebhookService.Name) + } else { + if dnsNames, err = GetDNSNamesFromURL(opts.Flags.WebhookURL); err != nil { + return fmt.Errorf("unable to create webhook certificate configuration: %w", err) + } + } + secretName := fmt.Sprintf("%s-certs", opts.CertName) + opts.CACert, opts.ServerCert, err = GenerateCertificates(ctx, opts.Client, opts.CertDir, opts.Flags.CertNamespace, opts.CertName, secretName, dnsNames) + if err != nil { + return fmt.Errorf("error during certificate generation: %w", err) + } + } else { + log.Info("Certificates provided") + } + + enabledWebhooks := opts.Registry.GetEnabledWebhooks(opts.Flags.DisabledWebhooks) + + // MutatingWebhookConfigurations + if opts.NameMutating != nil { + mutReg := enabledWebhooks.Filter(MutatingWebhooksFilter()) + + mutRegCount := len(mutReg) + if mutRegCount > 0 { + log.Info("Mutating webhooks enabled, creating/updating MutatingWebhookConfiguration", "webhookCount", mutRegCount, lc.KeyResourceKind, "MutatingWebhookConfiguration", lc.KeyResource, opts.NameMutating.Name) + + if err := mutReg.InitializeAll(ctx, opts.Client.Scheme()); err != nil { + return err + } + + cfg := &ConfigOptions{ + WebhookConfigurationName: opts.NameMutating.Name, + WebhookNameSuffix: opts.NameMutating.WebhookSuffix, + Service: opts.Flags.WebhookService, + WebhookURL: opts.Flags.WebhookURL, + CABundle: opts.CACert.CertificatePEM, + } + if err := UpdateWebhookConfiguration(ctx, MutatingWebhook, opts.Client, mutReg, cfg); err != nil { + return fmt.Errorf("error creating/updating MutatingWebhookConfiguration: %w", err) + } + } else { + log.Info("Mutating webhooks disabled, deleting MutatingWebhookConfiguration", lc.KeyResourceKind, "MutatingWebhookConfiguration", lc.KeyResource, opts.NameMutating.Name) + if err := DeleteWebhookConfiguration(ctx, MutatingWebhook, opts.Client, opts.NameMutating.Name); err != nil { + return fmt.Errorf("error deleting MutatingWebhookConfiguration: %w", err) + } + } + } + + // ValidatingWebhookConfigurations + if opts.NameValidating != nil { + valReg := enabledWebhooks.Filter(ValidatingWebhooksFilter()) + + valRegCount := len(valReg) + if valRegCount > 0 { + log.Info("Validating webhooks enabled, creating/updating ValidatingWebhookConfiguration", "webhookCount", valRegCount, lc.KeyResourceKind, "ValidatingWebhookConfiguration", lc.KeyResource, opts.NameValidating.Name) + + if err := valReg.InitializeAll(ctx, opts.Client.Scheme()); err != nil { + return err + } + + cfg := &ConfigOptions{ + WebhookConfigurationName: opts.NameValidating.Name, + WebhookNameSuffix: opts.NameValidating.WebhookSuffix, + Service: opts.Flags.WebhookService, + WebhookURL: opts.Flags.WebhookURL, + CABundle: opts.CACert.CertificatePEM, + } + if err := UpdateWebhookConfiguration(ctx, ValidatingWebhook, opts.Client, valReg, cfg); err != nil { + return fmt.Errorf("error creating/updating ValidatingWebhookConfiguration: %w", err) + } + } else { + log.Info("Validating webhooks disabled, deleting ValidatingWebhookConfiguration", lc.KeyResourceKind, "ValidatingWebhookConfiguration", lc.KeyResource, opts.NameValidating.Name) + if err := DeleteWebhookConfiguration(ctx, ValidatingWebhook, opts.Client, opts.NameValidating.Name); err != nil { + return fmt.Errorf("error deleting ValidatingWebhookConfiguration: %w", err) + } + } + } + + // Log webhooks + for _, w := range enabledWebhooks { + log.Info("Enabling webhook", "type", string(w.Type), "name", w.Name, "resourceName", w.ResourceName) + } + + // Register webhooks at webhook server + ctx = logging.NewContext(ctx, baseLog.WithName("webhook")) + if err := enabledWebhooks.AddToServer(ctx, opts.Server, opts.Client.Scheme()); err != nil { + return fmt.Errorf("unable to register webhooks at webhook server: %w", err) + } + + return nil +} diff --git a/vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/certificates.go b/vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/certificates.go index b7fcf17af..6e75ba904 100644 --- a/vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/certificates.go +++ b/vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/certificates.go @@ -12,6 +12,7 @@ import ( "os" "path/filepath" + utilerrors "github.com/gardener/landscaper/controller-utils/pkg/errors" "github.com/gardener/landscaper/controller-utils/pkg/logging" lc "github.com/gardener/landscaper/controller-utils/pkg/logging/constants" @@ -55,10 +56,11 @@ func GetDNSNamesFromURL(rawurl string) ([]string, error) { } } -// GenerateCertificates generates the certificates that are required for a webhook. It returns the ca bundle, and it -// stores the server certificate and key locally on the file system. +// GenerateCertificates generates the certificates that are required for a webhook. It returns the generated certificates, +// and it stores the server certificate and key locally on the file system. +// The first return value is the CA certificate, the second one the server certificate. func GenerateCertificates(ctx context.Context, kubeClient client.Client, certDir, namespace, name, certSecretName string, - dnsNames []string) ([]byte, error) { + dnsNames []string) (*certificates.Certificate, *certificates.Certificate, error) { logger, ctx := logging.FromContextOrNew(ctx, nil, lc.KeyMethod, "GenerateCertificates") @@ -79,26 +81,31 @@ func GenerateCertificates(ctx context.Context, kubeClient client.Client, certDir if !apierrors.IsNotFound(err) { logger.Info("GenerateCertificates: fetch failed hard") - return nil, errors.Wrapf(err, "error fetching secret for webhook server") + return nil, nil, errors.Wrapf(err, "error fetching secret for webhook server") } logger.Info("GenerateCertificates: generate new cert") caCert, serverCert, err := generateNewCAAndServerCert(name, dnsNames, *caConfig) if err != nil { logger.Info("GenerateCertificates: generate new cert failed") - return nil, errors.Wrapf(err, "error generating new certificates for webhook server") + return nil, nil, errors.Wrapf(err, "error generating new certificates for webhook server") } err = createOrUpdateSecret(ctx, kubeClient, caCert, serverCert, namespace, certSecretName, true) if err == nil { logger.Info("GenerateCertificates: new secret generated") - return writeCertificates(certDir, caCert, serverCert) + return caCert, serverCert, writeCertificate(certDir, serverCert) } else { + errs := utilerrors.NewErrorList(err) // try to refetch secret if it was created by another replica logger.Info("GenerateCertificates: new secret generation failed") if err := kubeClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: certSecretName}, secret); err != nil { + if apierrors.IsNotFound(err) { + return nil, nil, errors.Wrapf(errs.Aggregate(), "error writing certificates into secret") + } + errs.Append(err) logger.Info("GenerateCertificates: fetching new secret failed") - return nil, errors.Wrapf(err, "could not fetch secret for webhook") + return nil, nil, errors.Wrapf(errs.Aggregate(), "unable to write certificates into secret and check for existing secret") } } } @@ -114,28 +121,28 @@ func GenerateCertificates(ctx context.Context, kubeClient client.Client, certDir secret = &corev1.Secret{} if err := kubeClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: certSecretName}, secret); err != nil { logger.Info("GenerateCertificates: fetch updated secret failed") - return nil, err + return nil, nil, err } caCert, serverCert, _, err = loadAndUpdateSecret(ctx, kubeClient, secret, name, dnsNames, caConfig) if err != nil { logger.Info("GenerateCertificates: loadAndUpdateSecret retry failed") - return nil, err + return nil, nil, err } } else { logger.Info("GenerateCertificates: loadAndUpdateSecret failed") - return nil, err + return nil, nil, err } } logger.Info("GenerateCertificates: cert returned") - return writeCertificates(certDir, caCert, serverCert) + return caCert, serverCert, writeCertificate(certDir, serverCert) } func loadAndUpdateSecret(ctx context.Context, kubeClient client.Client, secret *corev1.Secret, name string, dnsNames []string, caConfig *certificates.CertificateSecretConfig) (*certificates.Certificate, *certificates.Certificate, bool, error) { - logger, ctx := logging.FromContextOrNew(ctx, nil, lc.KeyMethod, "GenerateCertificates") + logger, ctx := logging.FromContextOrNew(ctx, nil) logger.Info("loadAndUpdateSecret: load existing cert") @@ -243,25 +250,47 @@ func loadExistingCAAndServerCert(data map[string][]byte, pkcs int) (*certificate return caCert, serverCert, nil } -func writeCertificates(certDir string, caCert, serverCert *certificates.Certificate) ([]byte, error) { +// writeCertificate writes the given certificates to the local filesystem. +func writeCertificate(certDir string, cert *certificates.Certificate) error { var ( serverKeyPath = filepath.Join(certDir, certificates.DataKeyPrivateKey) serverCertPath = filepath.Join(certDir, certificates.DataKeyCertificate) ) if err := os.MkdirAll(certDir, 0755); err != nil { - return nil, err + return nil } - if err := os.WriteFile(serverKeyPath, serverCert.PrivateKeyPEM, 0666); err != nil { - return nil, err + if err := os.WriteFile(serverKeyPath, cert.PrivateKeyPEM, 0666); err != nil { + return nil } - if err := os.WriteFile(serverCertPath, serverCert.CertificatePEM, 0666); err != nil { - return nil, err + if err := os.WriteFile(serverCertPath, cert.CertificatePEM, 0666); err != nil { + return nil } - return caCert.CertificatePEM, nil + return nil } +// readCertificate is the sibling function to writeCertificates and reads a certificate including its CA from the local filesystem. +// Uses PKCS8 by default. +// name is the name which will be given to the certificate, not the filename. +// func readCertificate(certDir string, name string) (*certificates.Certificate, error) { +// var ( +// keyPath = filepath.Join(certDir, certificates.DataKeyPrivateKey) +// certPath = filepath.Join(certDir, certificates.DataKeyCertificate) +// ) + +// keyBytes, err := os.ReadFile(keyPath) +// if err != nil { +// return nil, err +// } +// certBytes, err := os.ReadFile(certPath) +// if err != nil { +// return nil, err +// } + +// return certificates.LoadCertificate(name, keyBytes, certBytes, certificates.PKCS8) +// } + func StringArrayIncludes(list []string, expects ...string) bool { actual := sets.NewString(list...) return actual.HasAll(expects...) diff --git a/vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/certificates/certificates.go b/vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/certificates/certificates.go index b18a94fa4..1b98a7f1d 100644 --- a/vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/certificates/certificates.go +++ b/vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/certificates/certificates.go @@ -277,7 +277,7 @@ func LoadCAFromSecret(k8sClient client.Client, namespace, name string, pkcs int) // generateCertificateTemplate creates a X509 Certificate object based on the provided information regarding // common name, organization, SANs (DNS names and IP addresses). It can create a server or a client certificate // or both, depending on the value. If is true, then a CA certificate is being created. -// The certificates a valid for 10 years. +// The certificates are valid for 10 years. func (s *CertificateSecretConfig) generateCertificateTemplate() *x509.Certificate { now := time.Now() expiration := now.AddDate(10, 0, 0) // + 10 years diff --git a/vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/config.go b/vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/config.go new file mode 100644 index 000000000..193565e5e --- /dev/null +++ b/vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/config.go @@ -0,0 +1,253 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package webhook + +import ( + "context" + "fmt" + "net/url" + "path" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/gardener/landscaper/controller-utils/pkg/logging" + lc "github.com/gardener/landscaper/controller-utils/pkg/logging/constants" + "github.com/gardener/landscaper/controller-utils/pkg/utils" + + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// ConfigOptions contains the configuration that is necessary to create a ValidatingWebhookConfiguration or MutatingWebhookConfiguration. +type ConfigOptions struct { + // Name of the WebhookConfiguration that will be created. + WebhookConfigurationName string + // WebhookNameSuffix will be appended to the webhooks' names. + WebhookNameSuffix string + // ServiceConfig is the configuration for reaching a webhook exposed via a service (running in the same cluster). + // It is mutually exclusive with WebhookURL. + Service *WebhookServiceOptions + // WebhookURL is used for reaching a webhook running outside of the cluster. + // It is mutually exclusive with Service. + WebhookURL string + // CABundle contains the certificates for the webhook. + CABundle []byte +} + +// WebhookServiceOptions contains the configuration for reaching a webhook which is running in the same cluster and exposed via a service. +// If the webhook server is running in a different cluster, WebhookURL must be used instead. +type WebhookServiceOptions struct { + // Name is the name of the service under which the webhook can be reached. + Name string + // Namespace is the namespace of the webhook service. + Namespace string + // Port is the port of the webhook service. + Port int32 +} + +// UpdateWebhookConfiguration will create or update a ValidatingWebhookConfiguration or MutatingWebhookConfiguration. +func UpdateWebhookConfiguration(ctx context.Context, wt WebhookType, kubeClient client.Client, wr WebhookRegistry, o *ConfigOptions) error { + log := logging.FromContextOrDiscard(ctx).WithValues(lc.KeyResourceKind, fmt.Sprintf("%sConfiguration", string(wt))) + + // exactly one of service and webhook url must be set + if (o.Service == nil) == (o.WebhookURL == "") { + return fmt.Errorf("invalid webhook configuration options: exactly one of [Service, WebhookURL] must be set") + } + + var whc client.Object + var whcUpdate func(client.Object) func() error + var err error + switch wt { + case ValidatingWebhook: + whc, whcUpdate, err = o.buildValidatingWebhookConfiguration(wr) + case MutatingWebhook: + whc, whcUpdate, err = o.buildMutatingWebhookConfiguration(wr) + } + if err != nil { + return fmt.Errorf("error building %s: %w", string(wt), err) + } + + log.Info("Creating/updating webhook configuration", lc.KeyResource, o.WebhookConfigurationName) + _, err = ctrl.CreateOrUpdate(ctx, kubeClient, whc, whcUpdate(whc)) + if err != nil { + return fmt.Errorf("unable to create/update webhook configuration: %w", err) + } + log.Info("Webhook configuration created/updated", lc.KeyResource, o.WebhookConfigurationName) + + return nil +} + +// DeleteValidatingWebhookConfiguration deletes a ValidatingWebhookConfiguration or MutatingWebhookConfiguration. +func DeleteWebhookConfiguration(ctx context.Context, wt WebhookType, kubeClient client.Client, name string) error { + log := logging.FromContextOrDiscard(ctx).WithValues(lc.KeyResourceKind, fmt.Sprintf("%sConfiguration", string(wt))) + + var whc client.Object + switch wt { + case ValidatingWebhook: + whc = &admissionregistrationv1.ValidatingWebhookConfiguration{} + case MutatingWebhook: + whc = &admissionregistrationv1.MutatingWebhookConfiguration{} + } + whc.SetName(name) + + log.Info("Removing webhook configuration, if it exists", lc.KeyResource, name) + if err := kubeClient.Delete(ctx, whc); err != nil { + if apierrors.IsNotFound(err) { + log.Debug("Webhook configuration not found", lc.KeyResource, name) + } else { + return fmt.Errorf("unable to delete webhook configuration %q: %w", name, err) + } + } else { + log.Info("Webhook configuration deleted", lc.KeyResource, name) + } + return nil +} + +// commonWebhookConfig contains the fields which ValidatingWebhookConfigurations and MutatingWebhookConfigurations have in common. +type commonWebhookConfig struct { + Name string + SideEffects *admissionregistrationv1.SideEffectClass + FailurePolicy *admissionregistrationv1.FailurePolicyType + ObjectSelector *metav1.LabelSelector + AdmissionReviewVersions []string + Rules []admissionregistrationv1.RuleWithOperations + ClientConfig admissionregistrationv1.WebhookClientConfig + TimeoutSeconds *int32 +} + +func (c *commonWebhookConfig) toValidatingWebhook() admissionregistrationv1.ValidatingWebhook { + return admissionregistrationv1.ValidatingWebhook{ + Name: c.Name, + SideEffects: c.SideEffects, + FailurePolicy: c.FailurePolicy, + ObjectSelector: c.ObjectSelector, + AdmissionReviewVersions: c.AdmissionReviewVersions, + Rules: c.Rules, + ClientConfig: c.ClientConfig, + TimeoutSeconds: c.TimeoutSeconds, + } +} + +func (c *commonWebhookConfig) toMutatingWebhook() admissionregistrationv1.MutatingWebhook { + return admissionregistrationv1.MutatingWebhook{ + Name: c.Name, + SideEffects: c.SideEffects, + FailurePolicy: c.FailurePolicy, + ObjectSelector: c.ObjectSelector, + AdmissionReviewVersions: c.AdmissionReviewVersions, + Rules: c.Rules, + ClientConfig: c.ClientConfig, + TimeoutSeconds: c.TimeoutSeconds, + } +} + +func (o *ConfigOptions) buildValidatingWebhookConfiguration(wr WebhookRegistry) (*admissionregistrationv1.ValidatingWebhookConfiguration, func(client.Object) func() error, error) { + vwc := &admissionregistrationv1.ValidatingWebhookConfiguration{} + vwc.SetName(o.WebhookConfigurationName) + + // construct ValidatingWebhookConfiguration + vwcWebhooks := []admissionregistrationv1.ValidatingWebhook{} + + for _, w := range wr.Filter(ValidatingWebhooksFilter()) { + if !w.isInitialized { + return nil, nil, fmt.Errorf("webhook '%s' is not initialized, please call Initialize on the webhook or InitializeAll on the registry before generating the ValidatingWebhookConfiguration", w.Name) + } + whCfg, err := o.buildCommonWebhookConfiguration(w) + if err != nil { + return nil, nil, err + } + vwcWebhooks = append(vwcWebhooks, whCfg.toValidatingWebhook()) + } + + vwc.Webhooks = vwcWebhooks + + return vwc, func(obj client.Object) func() error { + return func() error { + typedObj, ok := obj.(*admissionregistrationv1.ValidatingWebhookConfiguration) + if !ok { + return fmt.Errorf("update function for ValidatingWebhookConfigurations called on invalid object") + } + typedObj.Webhooks = vwcWebhooks + return nil + } + }, nil +} + +func (o *ConfigOptions) buildMutatingWebhookConfiguration(wr WebhookRegistry) (*admissionregistrationv1.MutatingWebhookConfiguration, func(client.Object) func() error, error) { + mwc := &admissionregistrationv1.MutatingWebhookConfiguration{} + mwc.SetName(o.WebhookConfigurationName) + + // construct MutatingWebhookConfiguration + mwcWebhooks := []admissionregistrationv1.MutatingWebhook{} + + for _, w := range wr.Filter(MutatingWebhooksFilter()) { + if !w.isInitialized { + return nil, nil, fmt.Errorf("webhook '%s' is not initialized, please call Initialize on the webhook or InitializeAll on the registry before generating the MutatingWebhookConfiguration", w.Name) + } + whCfg, err := o.buildCommonWebhookConfiguration(w) + if err != nil { + return nil, nil, err + } + mwcWebhooks = append(mwcWebhooks, whCfg.toMutatingWebhook()) + } + + mwc.Webhooks = mwcWebhooks + + return mwc, func(obj client.Object) func() error { + return func() error { + typedObj, ok := obj.(*admissionregistrationv1.MutatingWebhookConfiguration) + if !ok { + return fmt.Errorf("update function for MutatingWebhookConfigurations called on invalid object") + } + typedObj.Webhooks = mwcWebhooks + return nil + } + }, nil +} + +func (o *ConfigOptions) buildCommonWebhookConfiguration(w *Webhook) (*commonWebhookConfig, error) { + rule := admissionregistrationv1.RuleWithOperations{ + Operations: w.Operations, + Rule: admissionregistrationv1.Rule{}, + } + rule.Rule.APIGroups = []string{w.APIGroup} + rule.Rule.APIVersions = w.APIVersions + rule.Rule.Resources = []string{w.ResourceName} + clientConfig := admissionregistrationv1.WebhookClientConfig{ + CABundle: o.CABundle, + } + if o.WebhookURL != "" { + parsedURL, err := url.Parse(o.WebhookURL) + if err != nil { + return nil, fmt.Errorf("unable to parse webhook url: %w", err) + } + parsedURL.Path = path.Join(parsedURL.Path, w.Path()) + webhookURL := parsedURL.String() + clientConfig.URL = &webhookURL + } else { + webhookPath := w.Path() + clientConfig.Service = &admissionregistrationv1.ServiceReference{ + Namespace: o.Service.Namespace, + Name: o.Service.Name, + Path: &webhookPath, + Port: &o.Service.Port, + } + } + + common := &commonWebhookConfig{ + Name: w.Name + o.WebhookNameSuffix, + SideEffects: utils.Ptr(admissionregistrationv1.SideEffectClassNone), + FailurePolicy: utils.Ptr(admissionregistrationv1.Fail), + ObjectSelector: w.LabelSelector, + AdmissionReviewVersions: []string{"v1"}, + Rules: []admissionregistrationv1.RuleWithOperations{rule}, + ClientConfig: clientConfig, + TimeoutSeconds: utils.Ptr(int32(w.Timeout)), + } + + return common, nil +} diff --git a/vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/flags.go b/vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/flags.go new file mode 100644 index 000000000..09571eb8a --- /dev/null +++ b/vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/flags.go @@ -0,0 +1,271 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package webhook + +import ( + "fmt" + "net/url" + "sort" + "strings" + "sync" + + flag "github.com/spf13/pflag" + "k8s.io/apimachinery/pkg/util/sets" + + utilerrors "github.com/gardener/landscaper/controller-utils/pkg/errors" +) + +var kcidSync = &sync.Mutex{} +var knownClusterIDs = map[string]*ClusterID{} + +// ClusterID is a helper struct to identify a cluster. +// It should always be created by using the ID function! +// Note that ID and name must both be unique across all ClusterID objects. There must not be two ClusterID resources with the same ID, nor two ClusterID resources with the same name! +type ClusterID struct { + ID string + Name string +} + +// ID returns a cluster ID. +// It ensures uniqueness of cluster IDs - basically, calling this function multiple times with the same id will always return a pointer to the same object. +// The given name is only relevant if the function is called for the first time with the given ID. +// This means that the name in the returned object can actually differ from the one given, but only if cluster IDs are used in the wrong way. +func ID(id, name string) *ClusterID { + kcidSync.Lock() + defer kcidSync.Unlock() + c, ok := knownClusterIDs[id] + if ok { + return c + } + c = &ClusterID{ + ID: id, + Name: name, + } + knownClusterIDs[id] = c + return c +} + +// WebhookFlags is a helper struct for making the webhook server configuration configurable via command-line flags. +// It should be instantiated using the NewWebhookFlags function. +// +// Accessing cluster-specific configuration: +// Single-cluster example (getting the certificate namespace from a WebhookFlags object named 'wf'): +// +// wf.CertNamespace (or: wf.MultiWebhookFlags.CertNamespace) +// +// Multi-cluster example (getting the certificate namespace for a cluster with ClusterID 'cid' from a WebhookFlags object named 'wf'): +// +// wf.MultiCluster[cid].CertNamespace +type WebhookFlags struct { + *MultiWebhookFlags // contains the configuration if isMulti is false + + Port int // port where the webhook server is running + DisabledWebhooksRaw string // lists disabled webhooks as a comma-separated string + DisabledWebhooks sets.Set[string] // transformed version of DisabledWebhooksRaw + MultiCluster map[*ClusterID]*MultiWebhookFlags // contains the configuration if isMulti is true +} + +// MultiWebhookFlags contain flags which need to be added multiple times if multiple clusters need to reach the webhook server. +// It is automatically instantiated with the NewWebhookFlags function as part of the returned WebhookFlags object. +type MultiWebhookFlags struct { + WebhookServiceNamespaceName string // webhook service namespace and name in the format / + WebhookServicePort int32 // port of the webhook service + WebhookService *WebhookServiceOptions // transformed version of the webhook service flags + CertNamespace string // the namespace in which the webhook credentials are being created/updated + WebhookURL string // URL of the webhook server if running outside of cluster +} + +func (wf *WebhookFlags) AddFlags(fs *flag.FlagSet) { + fs.IntVar(&wf.Port, "port", 9443, "Specify the port of the webhook server") + fs.StringVar(&wf.DisabledWebhooksRaw, "disable-webhooks", "", "Specify validation webhooks that should be disabled ('all' to disable validation completely)") + + if wf.IsMultiCluster() { + for cluster, mwf := range wf.MultiCluster { + mwf.addMultiFlags(fs, cluster) + } + } else { + wf.addMultiFlags(fs, nil) + } +} + +func (mwf *MultiWebhookFlags) addMultiFlags(fs *flag.FlagSet, cluster *ClusterID) { + prefix := "" + cphrase := "" + if cluster != nil { + prefix = fmt.Sprintf("%s-", cluster.ID) + cphrase = fmt.Sprintf("%s cluster ", cluster.Name) + } + + fs.StringVar(&mwf.WebhookServiceNamespaceName, fmt.Sprintf("%swebhook-service", prefix), "", fmt.Sprintf("Specify namespace and name of the %swebhook service (format: /)", cphrase)) + fs.Int32Var(&mwf.WebhookServicePort, fmt.Sprintf("%swebhook-service-port", prefix), 9443, fmt.Sprintf("Specify the port of the %swebhook service", cphrase)) + fs.StringVar(&mwf.WebhookURL, fmt.Sprintf("%swebhook-url", prefix), "", fmt.Sprintf("Specify the URL of the external %swebhook service (scheme://host:port)", cphrase)) + fs.StringVar(&mwf.CertNamespace, fmt.Sprintf("%scert-ns", prefix), "", fmt.Sprintf("Specify the namespace in which the %scertificates are being stored", cphrase)) +} + +// NewWebhookFlags returns a new WebhookFlags object. +// +// How this is supposed to work: +// +// If you have only one cluster, from which the webhook server must be reachable, call this function without any arguments. +// In the returned object, the cluster-specific configuration is contained in the embedded MultiWebhookFlags struct. +// Example (getting the certificate namespace from a WebhookFlags object named 'wf'): +// +// wf.CertNamespace (or: wf.MultiWebhookFlags.CertNamespace) +// +// If the webhook server must be reachable from multiple clusters, create a cluster ID for each cluster via the ID(id, name) function and pass all of them as arguments. +// In the returned object, the cluster-specific configuration is contained in the MultiCluster map, with the previously created ClusterID object serving as key. +// Example (getting the certificate namespace for a cluster with ClusterID 'cid' from a WebhookFlags object named 'wf'): +// +// wf.MultiCluster[cid].CertNamespace +func NewWebhookFlags(clusters ...*ClusterID) *WebhookFlags { + res := &WebhookFlags{} + + if len(clusters) > 0 { + res.MultiCluster = map[*ClusterID]*MultiWebhookFlags{} + + for _, c := range clusters { + res.MultiCluster[c] = &MultiWebhookFlags{} + } + } else { + res.MultiWebhookFlags = &MultiWebhookFlags{} + } + + return res +} + +// Complete transforms and validates the arguments given via the CLI. +// The registry argument may be nil (or empty), then the values for the disabled webhooks flag cannot be validated. +func (wf *WebhookFlags) Complete(wr WebhookRegistry) error { + errs := utilerrors.NewErrorList() + + // transform disabled webhooks string into slice + wf.DisabledWebhooks = sets.New[string]() + if wf.DisabledWebhooksRaw != "" { + dws := strings.Split(wf.DisabledWebhooksRaw, ",") + for _, dw := range dws { + wf.DisabledWebhooks.Insert(strings.TrimSpace(dw)) + } + } + + if wf.Port < 0 { + errs.Append(fmt.Errorf("port must not be negative")) + } + if len(wr) != 0 && len(wf.DisabledWebhooks) != 0 { + allowedDisablesSet := sets.KeySet[string, *Webhook](wr) + allowedDisablesSet.Insert("all") + allowedDisablesList := allowedDisablesSet.UnsortedList() + sort.Strings(allowedDisablesList) // doesn't necessarily need to be sorted, but order should be stable so that the tooltip doesn't change every time + allowedDisablesString := strings.Join(allowedDisablesList, ", ") + for dw := range wf.DisabledWebhooks { + if !allowedDisablesSet.Has(dw) { + errs.Append(fmt.Errorf("invalid disabled webhook '%s', allowed values are [%s]", dw, allowedDisablesString)) + } + } + } + + var cf map[*ClusterID]*MultiWebhookFlags + if wf.IsMultiCluster() { + cf = wf.MultiCluster + } else { + cf = map[*ClusterID]*MultiWebhookFlags{ + nil: wf.MultiWebhookFlags, + } + } + for cid, fl := range cf { + idString := "" + if cid != nil { + idString = fmt.Sprintf(" for %s cluster", cid.Name) + } + if (fl.WebhookServiceNamespaceName != "") == (fl.WebhookURL != "") { + // either both or none are set + errs.Append(fmt.Errorf("invalid flags%s: exactly one of url and service must be specified", idString)) + } + if fl.WebhookServiceNamespaceName != "" { + svc := strings.Split(fl.WebhookServiceNamespaceName, "/") + if len(svc) != 2 { + errs.Append(fmt.Errorf("invalid format of webhook service namespace and name%s: expected format is '/', but got '%s'", idString, fl.WebhookServiceNamespaceName)) + } else { + fl.WebhookService = &WebhookServiceOptions{ + Name: svc[1], + Namespace: svc[0], + Port: fl.WebhookServicePort, + } + if fl.WebhookService.Name == "" { + errs.Append(fmt.Errorf("webhook service name%s must not be empty", idString)) + } + if fl.WebhookService.Namespace == "" { + errs.Append(fmt.Errorf("webhook service namespace%s must not be empty", idString)) + } + } + } + if fl.WebhookServicePort < 0 { + errs.Append(fmt.Errorf("webhook service port%s '%d' is invalid: port must not be negative", idString, fl.WebhookServicePort)) + } + if fl.WebhookURL != "" { + parsedUrl, err := url.Parse(fl.WebhookURL) + if err != nil { + errs.Append(fmt.Errorf("invalid webhook url '%s'%s: %w", fl.WebhookURL, idString, err)) + } else if parsedUrl.Host == "" { + errs.Append(fmt.Errorf("invalid webhook url%s: expected format is '://:', but got %s", idString, fl.WebhookURL)) + } + } + if fl.CertNamespace == "" { + if fl.WebhookService != nil { + fl.CertNamespace = fl.WebhookService.Namespace + } else { + errs.Append(fmt.Errorf("certificate namespace%s is empty and cannot be derived from service namespace", idString)) + } + } + } + + return errs.Aggregate() +} + +// ForSingleCluster returns the webhook flags for a single cluster. +// Cluster id may be nil if and only if wf.IsMultiCluster() == false. +// If the given cluster id is nil, the receiver object is returned as is. +// Otherwise, a new WebhookFlags object is created with IsMultiCluster set to 'false' and the embedded MultiWebhookFlags object's fields set to the values from MultiCluster[cid]. +// Returns an error if cid and wf.IsMultiCluster() don't fit together, or when no matching configuration for the given cluster id is found. +func (wf *WebhookFlags) ForSingleCluster(cid *ClusterID) (*WebhookFlags, error) { + if cid == nil { + if wf.IsMultiCluster() { + return nil, fmt.Errorf("WebhookFlags object contains multi-cluster configuration, calling ForSingleCluster with nil argument is not allowed") + } + return wf, nil + } + if !wf.IsMultiCluster() { + return nil, fmt.Errorf("WebhookFlags object contains single-cluster configuration, calling ForSingleCluster with non-nil argument is not allowed") + } + mc, ok := wf.MultiCluster[cid] + if !ok { + return nil, fmt.Errorf("no matching multi-cluster configuration found for cluster id '%s'", cid.ID) + } + res := &WebhookFlags{ + Port: wf.Port, + DisabledWebhooksRaw: wf.DisabledWebhooksRaw, + MultiWebhookFlags: &MultiWebhookFlags{ + WebhookServiceNamespaceName: mc.WebhookServiceNamespaceName, + WebhookServicePort: mc.WebhookServicePort, + CertNamespace: mc.CertNamespace, + WebhookURL: mc.WebhookURL, + }, + } + if wf.DisabledWebhooks != nil { + res.DisabledWebhooks = sets.New[string](wf.DisabledWebhooks.UnsortedList()...) + } + if mc.WebhookService != nil { + res.WebhookService = &WebhookServiceOptions{ + Name: mc.WebhookService.Name, + Namespace: mc.WebhookService.Namespace, + Port: mc.WebhookService.Port, + } + } + return res, nil +} + +// IsMultiCluster returns true if the webhook flags object contains configuration for multiple clusters (or modified the object accordingly afterwards). +func (wf *WebhookFlags) IsMultiCluster() bool { + return wf.MultiCluster != nil +} diff --git a/vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/registry.go b/vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/registry.go new file mode 100644 index 000000000..55ce2c5ff --- /dev/null +++ b/vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/registry.go @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package webhook + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/sets" + ctrlwebhook "sigs.k8s.io/controller-runtime/pkg/webhook" + + "github.com/gardener/landscaper/controller-utils/pkg/logging" +) + +var registry WebhookRegistry + +// WebhookRegistry is a helper struct for listing webhooks which can be instantiated automatically from flags. +type WebhookRegistry map[string]*Webhook + +// WebhookRegistryFilter is a filter function which can be used to filter the registry for webhooks with specific properties. +type WebhookRegistryFilter func(*Webhook) bool + +// NewWebhookRegistry returns a new webhook registry. +// Alternatively, the Registry() function can be used to get a singleton registry. +func NewWebhookRegistry() WebhookRegistry { + return WebhookRegistry(map[string]*Webhook{}) +} + +// Registry returns a singleton registry. +// Use NewWebhookRegistry instead if you want to run multiple webhook servers with different sets of webhooks or can't use a singleton for some other reason. +func Registry() WebhookRegistry { + if registry == nil { + registry = NewWebhookRegistry() + } + return registry +} + +// Register is an alias for registry[webhook.name] = webhook. +// Returns the registry for chaining. +func (wr WebhookRegistry) Register(webhook *Webhook) WebhookRegistry { + wr[webhook.Name] = webhook + return wr +} + +// GetEnabledWebhooks takes a set of webhook names and returns a registry containing only the webhooks whose names were NOT in the set. +// If the set contains an entry 'all', an empty registry is returned. +func (wr WebhookRegistry) GetEnabledWebhooks(disabled sets.Set[string]) WebhookRegistry { + res := NewWebhookRegistry() + if disabled.Has("all") { + return res + } + for name, w := range wr { + if !disabled.Has(name) { + res.Register(w) + } + } + return res +} + +// Filter returns a new WebhookRegistry containing only the webhooks for which a filter returned true. +// Note that the filters are ORed. To AND filters, call the function multiples times: reg.Filter(f1).Filter(f2) +func (wr WebhookRegistry) Filter(filters ...WebhookRegistryFilter) WebhookRegistry { + res := NewWebhookRegistry() + for _, w := range wr { + for _, f := range filters { + if f(w) { + res.Register(w) + break + } + } + } + return res +} + +// InitializeAll calls Initialize on all webhooks in the registry. +// No-op if the registry is empty. +func (wr WebhookRegistry) InitializeAll(ctx context.Context, scheme *runtime.Scheme) error { + if len(wr) == 0 { + return nil + } + log := logging.FromContextOrDiscard(ctx) + for name, w := range wr { + if err := w.Initialize(log, scheme); err != nil { + return fmt.Errorf("error initializing webhook registered as '%s': %w", name, err) + } + } + return nil +} + +// AddToServer initializes all webhooks of the registry and registers them at the given webhook server. +// No-op if the webhook registry is empty or nil. +func (wr WebhookRegistry) AddToServer(ctx context.Context, webhookServer ctrlwebhook.Server, scheme *runtime.Scheme) error { + log := logging.FromContextOrDiscard(ctx) + + if len(wr) == 0 { + log.Info("AddToServer called on empty webhook registry, nothing to do") + return nil + } + + // registering webhooks + for _, w := range wr { + rsLogger := log.WithName(w.Name) + + if err := w.Initialize(rsLogger, scheme); err != nil { + return fmt.Errorf("unable to initialize webhook '%s': %w", w.Name, err) + } + + webhookPath := w.Path() + rsLogger.Info("Registering webhook", "name", w.ResourceName, "path", webhookPath) + admission := &ctrlwebhook.Admission{Handler: w} + webhookServer.Register(webhookPath, admission) + } + + return nil +} diff --git a/vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/utils.go b/vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/utils.go new file mode 100644 index 000000000..90a7d1e41 --- /dev/null +++ b/vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/utils.go @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package webhook + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// GetCachelessClient is a helper function that returns a client that can be used before the manager is started. +// It calls all given installFuncs on the created scheme. +func GetCachelessClient(restConfig *rest.Config, installFuncs ...func(*runtime.Scheme)) (client.Client, error) { + s := runtime.NewScheme() + if err := scheme.AddToScheme(s); err != nil { + return nil, err + } + + for _, f := range installFuncs { + f(s) + } + + return client.New(restConfig, client.Options{Scheme: s}) +} + +// ValidatingWebhooksFilter returns a predefined filter to be used with the WebhookRegistry's Filter method. +// The returned filter returns true for webhooks which have the type ValidatingWebhook. +func ValidatingWebhooksFilter() WebhookRegistryFilter { + return func(w *Webhook) bool { + return w.Type == ValidatingWebhook + } +} + +// MutatingWebhooksFilter returns a predefined filter to be used with the WebhookRegistry's Filter method. +// The returned filter returns true for webhooks which have the type MutatingWebhook. +func MutatingWebhooksFilter() WebhookRegistryFilter { + return func(w *Webhook) bool { + return w.Type == MutatingWebhook + } +} + +// LabelFilter returns a filter that can be used with the WebhookRegistry's Filter method. +// The returned filter returns true if the webhook has a corresponding label. +// If the second argument is non-nil, true is only returned if the label's value matches it, +// otherwise only existence of the label is checked. +func LabelFilter(key string, value *string) WebhookRegistryFilter { + return func(w *Webhook) bool { + if w.Labels == nil { + return false + } + val, ok := w.Labels[key] + if !ok { + return false + } + if value != nil && val != *value { + return false + } + return true + } +} + +// Not negates the result of the passed filter. +func Not(f WebhookRegistryFilter) WebhookRegistryFilter { + return func(w *Webhook) bool { + return !f(w) + } +} diff --git a/vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/webhook.go b/vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/webhook.go new file mode 100644 index 000000000..18b1b41e0 --- /dev/null +++ b/vendor/github.com/gardener/landscaper/controller-utils/pkg/webhook/webhook.go @@ -0,0 +1,180 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package webhook + +import ( + "context" + "fmt" + "path" + "time" + + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/gardener/landscaper/controller-utils/pkg/logging" + lc "github.com/gardener/landscaper/controller-utils/pkg/logging/constants" +) + +const ( + JSONPatchAddOperation = "add" + JSONPatchReplaceOperation = "replace" + JSONPatchRemoveOperation = "remove" + + DefaultWebhookTimeoutSeconds = 15 + + // forwarded for easier consumption + CREATE = admissionregistrationv1.Create + UPDATE = admissionregistrationv1.Update + DELETE = admissionregistrationv1.Delete + + ValidatingWebhookBasePath = "/webhook/validate" + MutatingWebhookBasePath = "/webhook/mutate" +) + +// Operations returns the given arguments as slice. +// This is an auxiliary function which removes the need for importing admissionregistration/v1 in the calling file. +func Operations(ops ...admissionregistrationv1.OperationType) []admissionregistrationv1.OperationType { + return ops +} + +// AllOperations is an alias for Operations(CREATE, UPDATE, DELETE) +func AllOperations() []admissionregistrationv1.OperationType { + return Operations(CREATE, UPDATE, DELETE) +} + +type WebhookType string + +const ( + ValidatingWebhook WebhookType = "ValidatingWebhook" + MutatingWebhook WebhookType = "MutatingWebhook" +) + +// WebhookLogic represents the actual logic of a webhook. +// It is similar to admission.Handler.Handle, but in addition, a default decoder is passed in. +// The decoder is derived from the scheme which was passed in during webhook initialization. +type WebhookLogic func(context.Context, admission.Request, runtime.Decoder) admission.Response + +var _ admission.Handler = &Webhook{} + +type Webhook struct { + // (usually) statically defined fields + // Name is the name of the webhook. Used for logging, webhook path, and inside Validating-/MutatingWebhookConfigurations. + Name string + // Type is the type, whether the webhook is a validating or a mutating webhook. + Type WebhookType + // APIGroup is the api group of the resource watched by this webhook. + APIGroup string + // APIVersions are the api versions watched by this webhook. + APIVersions []string + // ResourceName is the name of the resource watched by this webhook. + ResourceName string + // Operations lists the operations on which this webhook should react. + Operations []admissionregistrationv1.OperationType + // Timeout is the timeout for the webhook in seconds. + Timeout int + // Process is the function which holds the actual webhook logic. + // Note that it is wrapped in some additional logic which logs the result. + Process WebhookLogic + // Labels is a custom set of labels. + // This field is not evaluated by the webhook library. + // It can be used to group webhooks in combination with the WebhookRegistry's 'Filter' method. + Labels map[string]string + // LabelSelector allows to filter resources by labels. + LabelSelector *metav1.LabelSelector + + // dynamic fields + // Decoder is used to decode the raw object in the webhook's request into a structured object. + Decoder runtime.Decoder + // Log is the logger. + Log logging.Logger + + isInitialized bool +} + +// Initialize validates the webhook fields, defaults some of them and sets the dynamic fields. +// Is a no-op if called more than once. +func (w *Webhook) Initialize(log logging.Logger, scheme *runtime.Scheme) error { + if w.isInitialized { + return nil + } + + if w.Name == "" { + return fmt.Errorf("webhook name must not be empty") + } + if w.Type == "" { + return fmt.Errorf("webhook type must not be empty") + } + if len(w.APIVersions) == 0 { + return fmt.Errorf("no api versions specified") + } + if w.ResourceName == "" { + return fmt.Errorf("webhook resource name must not be empty") + } + if len(w.Operations) == 0 { + w.Operations = AllOperations() + } + if w.Timeout < 1 || w.Timeout > 30 { + w.Timeout = DefaultWebhookTimeoutSeconds + } + if w.Process == nil { + return fmt.Errorf("webhook's Process method must not be nil") + } + + w.Decoder = serializer.NewCodecFactory(scheme).UniversalDecoder() + w.Log = log + + w.isInitialized = true + + return nil +} + +func (w *Webhook) Handle(ctx context.Context, req admission.Request) admission.Response { + log, ctx := w.Log.WithValuesAndContext(ctx, lc.KeyResourceGroup, req.Kind.Group, lc.KeyResourceKind, req.Kind.Kind, lc.KeyResourceVersion, req.Kind.Version, "event", string(req.Operation), lc.KeyResource, fmt.Sprintf("%s/%s", req.Namespace, req.Name)) + + log.Debug("Request received") + + timeBefore := time.Now() + res := w.Process(ctx, req, w.Decoder) + + logIfDurationExceeded(log, w.Timeout, timeBefore) + + msg := "" + if res.Result != nil && string(res.Result.Message) != "" { + msg = string(res.Result.Message) + } + + if res.Allowed { + if w.Type == MutatingWebhook { + log = log.WithValues("patched", len(res.Patches) > 0) + } + log.Info("Request allowed", lc.KeyReason, msg) + } else { + log.Info("Request denied", lc.KeyReason, msg) + } + return res +} + +// Path returns the path under which this webhook can be reached. +// It's a combination of the base path and the webhook's name. +func (w *Webhook) Path() string { + switch w.Type { + case ValidatingWebhook: + return path.Join(ValidatingWebhookBasePath, w.Name) + case MutatingWebhook: + return path.Join(MutatingWebhookBasePath, w.Name) + } + return "" +} + +func logIfDurationExceeded(log logging.Logger, timeout int, timeBefore time.Time) { + timeAfter := time.Now() + diff := timeAfter.Sub(timeBefore) + if diff >= time.Duration(timeout)*time.Second { + log.Info("Webhook request took more time than the defined timeout", "timeout", time.Duration(timeout).String(), "duration", diff.String()) + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index bebb263b6..16c9ccbfe 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -36,7 +36,7 @@ github.com/fsnotify/fsnotify ## explicit; go 1.18 github.com/gardener/component-spec/bindings-go/apis/v2 github.com/gardener/component-spec/bindings-go/utils/selector -# github.com/gardener/landscaper/apis v0.75.0 +# github.com/gardener/landscaper/apis v0.76.0 ## explicit; go 1.20 github.com/gardener/landscaper/apis/config github.com/gardener/landscaper/apis/core @@ -47,12 +47,14 @@ github.com/gardener/landscaper/apis/hack/generate-schemes/app github.com/gardener/landscaper/apis/hack/generate-schemes/generators github.com/gardener/landscaper/apis/schema github.com/gardener/landscaper/apis/utils -# github.com/gardener/landscaper/controller-utils v0.75.0 +# github.com/gardener/landscaper/controller-utils v0.76.0 ## explicit; go 1.20 github.com/gardener/landscaper/controller-utils/pkg/crdmanager +github.com/gardener/landscaper/controller-utils/pkg/errors github.com/gardener/landscaper/controller-utils/pkg/kubernetes github.com/gardener/landscaper/controller-utils/pkg/logging github.com/gardener/landscaper/controller-utils/pkg/logging/constants +github.com/gardener/landscaper/controller-utils/pkg/utils github.com/gardener/landscaper/controller-utils/pkg/webhook github.com/gardener/landscaper/controller-utils/pkg/webhook/certificates # github.com/ghodss/yaml v1.0.0 @@ -927,7 +929,7 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client/pkg/client sigs.k8s.io/apiserver-network-proxy/konnectivity-client/pkg/client/metrics sigs.k8s.io/apiserver-network-proxy/konnectivity-client/pkg/common/metrics sigs.k8s.io/apiserver-network-proxy/konnectivity-client/proto/client -# sigs.k8s.io/controller-runtime v0.15.0 +# sigs.k8s.io/controller-runtime v0.15.1 ## explicit; go 1.20 sigs.k8s.io/controller-runtime sigs.k8s.io/controller-runtime/pkg/builder diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/cache/cache.go b/vendor/sigs.k8s.io/controller-runtime/pkg/cache/cache.go index f01de4381..760038704 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/cache/cache.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/cache/cache.go @@ -236,6 +236,11 @@ func New(config *rest.Config, opts Options) (Cache, error) { } func defaultOpts(config *rest.Config, opts Options) (Options, error) { + config = rest.CopyConfig(config) + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + logger := log.WithName("setup") // Use the rest HTTP client for the provided config if unset diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/restmapper.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/restmapper.go index f14f8a9f5..e0ff72dc1 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/restmapper.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/restmapper.go @@ -152,6 +152,12 @@ func (m *mapper) getMapper() meta.RESTMapper { // addKnownGroupAndReload reloads the mapper with updated information about missing API group. // versions can be specified for partial updates, for instance for v1beta1 version only. func (m *mapper) addKnownGroupAndReload(groupName string, versions ...string) error { + // versions will here be [""] if the forwarded Version value of + // GroupVersionResource (in calling method) was not specified. + if len(versions) == 1 && versions[0] == "" { + versions = nil + } + // If no specific versions are set by user, we will scan all available ones for the API group. // This operation requires 2 requests: /api and /apis, but only once. For all subsequent calls // this data will be taken from cache. diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/client.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/client.go index 21067b6f8..0d8b9fbe1 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/client/client.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/client.go @@ -110,6 +110,11 @@ func newClient(config *rest.Config, options Options) (*client, error) { return nil, fmt.Errorf("must provide non-nil rest.Config to client.New") } + config = rest.CopyConfig(config) + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + if !options.WarningHandler.SuppressWarnings { // surface warnings logger := log.Log.WithName("KubeAPIWarningLogger") @@ -117,7 +122,6 @@ func newClient(config *rest.Config, options Options) (*client, error) { // is log.KubeAPIWarningLogger with deduplication enabled. // See log.KubeAPIWarningLoggerOptions for considerations // regarding deduplication. - config = rest.CopyConfig(config) config.WarningHandler = log.NewKubeAPIWarningLogger( logger, log.KubeAPIWarningLoggerOptions{ @@ -160,7 +164,7 @@ func newClient(config *rest.Config, options Options) (*client, error) { unstructuredResourceByType: make(map[schema.GroupVersionKind]*resourceMeta), } - rawMetaClient, err := metadata.NewForConfigAndClient(config, options.HTTPClient) + rawMetaClient, err := metadata.NewForConfigAndClient(metadata.ConfigFor(config), options.HTTPClient) if err != nil { return nil, fmt.Errorf("unable to construct metadata-only client for use as part of client: %w", err) } diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/options.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/options.go index 50a461f1c..d81bf25de 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/client/options.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/options.go @@ -513,8 +513,15 @@ type MatchingLabels map[string]string // ApplyToList applies this configuration to the given list options. func (m MatchingLabels) ApplyToList(opts *ListOptions) { // TODO(directxman12): can we avoid reserializing this over and over? - sel := labels.SelectorFromValidatedSet(map[string]string(m)) - opts.LabelSelector = sel + if opts.LabelSelector == nil { + opts.LabelSelector = labels.NewSelector() + } + // If there's already a selector, we need to AND the two together. + noValidSel := labels.SelectorFromValidatedSet(map[string]string(m)) + reqs, _ := noValidSel.Requirements() + for _, req := range reqs { + opts.LabelSelector = opts.LabelSelector.Add(req) + } } // ApplyToDeleteAllOf applies this configuration to the given an List options. @@ -528,14 +535,17 @@ type HasLabels []string // ApplyToList applies this configuration to the given list options. func (m HasLabels) ApplyToList(opts *ListOptions) { - sel := labels.NewSelector() + if opts.LabelSelector == nil { + opts.LabelSelector = labels.NewSelector() + } + // TODO: ignore invalid labels will result in an empty selector. + // This is inconsistent to the that of MatchingLabels. for _, label := range m { r, err := labels.NewRequirement(label, selection.Exists, nil) if err == nil { - sel = sel.Add(*r) + opts.LabelSelector = opts.LabelSelector.Add(*r) } } - opts.LabelSelector = sel } // ApplyToDeleteAllOf applies this configuration to the given an List options. diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/unstructured_client.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/unstructured_client.go index b8d4146c9..0d9695178 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/client/unstructured_client.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/unstructured_client.go @@ -224,11 +224,11 @@ func (uc *unstructuredClient) List(ctx context.Context, obj ObjectList, opts ... func (uc *unstructuredClient) GetSubResource(ctx context.Context, obj, subResourceObj Object, subResource string, opts ...SubResourceGetOption) error { if _, ok := obj.(runtime.Unstructured); !ok { - return fmt.Errorf("unstructured client did not understand object: %T", subResource) + return fmt.Errorf("unstructured client did not understand object: %T", obj) } if _, ok := subResourceObj.(runtime.Unstructured); !ok { - return fmt.Errorf("unstructured client did not understand object: %T", obj) + return fmt.Errorf("unstructured client did not understand object: %T", subResourceObj) } if subResourceObj.GetName() == "" { @@ -255,11 +255,11 @@ func (uc *unstructuredClient) GetSubResource(ctx context.Context, obj, subResour func (uc *unstructuredClient) CreateSubResource(ctx context.Context, obj, subResourceObj Object, subResource string, opts ...SubResourceCreateOption) error { if _, ok := obj.(runtime.Unstructured); !ok { - return fmt.Errorf("unstructured client did not understand object: %T", subResourceObj) + return fmt.Errorf("unstructured client did not understand object: %T", obj) } if _, ok := subResourceObj.(runtime.Unstructured); !ok { - return fmt.Errorf("unstructured client did not understand object: %T", obj) + return fmt.Errorf("unstructured client did not understand object: %T", subResourceObj) } if subResourceObj.GetName() == "" { diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/cluster/cluster.go b/vendor/sigs.k8s.io/controller-runtime/pkg/cluster/cluster.go index 7d00c3c4b..7ab76555b 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/cluster/cluster.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/cluster/cluster.go @@ -179,6 +179,13 @@ func New(config *rest.Config, opts ...Option) (Cluster, error) { return nil, errors.New("must specify Config") } + originalConfig := config + + config = rest.CopyConfig(config) + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + options := Options{} for _, opt := range opts { opt(&options) @@ -275,7 +282,7 @@ func New(config *rest.Config, opts ...Option) (Cluster, error) { } return &cluster{ - config: config, + config: originalConfig, httpClient: options.HTTPClient, scheme: options.Scheme, cache: cache, diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/manager/manager.go b/vendor/sigs.k8s.io/controller-runtime/pkg/manager/manager.go index 7e65ef0c3..72a4a7801 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/manager/manager.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/manager/manager.go @@ -19,6 +19,7 @@ package manager import ( "context" "crypto/tls" + "errors" "fmt" "net" "net/http" @@ -391,6 +392,9 @@ type LeaderElectionRunnable interface { // New returns a new Manager for creating Controllers. func New(config *rest.Config, options Options) (Manager, error) { + if config == nil { + return nil, errors.New("must specify Config") + } // Set default values for options fields options = setOptionsDefaults(options) @@ -412,6 +416,11 @@ func New(config *rest.Config, options Options) (Manager, error) { return nil, err } + config = rest.CopyConfig(config) + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + // Create the recorder provider to inject event recorders for the components. // TODO(directxman12): the log for the event provider should have a context (name, tags, etc) specific // to the particular controller that it's being injected into, rather than a generic one like is here.