From ee5743019ee11fdf20edad90b1261697112f595e Mon Sep 17 00:00:00 2001 From: Felix Breuer Date: Tue, 7 Jul 2026 17:10:09 +0200 Subject: [PATCH] refactor valuesprovider_test to use fake client Signed-off-by: Felix Breuer --- Makefile | 1 - .../controlplane/valuesprovider_test.go | 1572 ++++++++--------- pkg/mock/controller-runtime/client/mocks.go | 702 -------- 3 files changed, 746 insertions(+), 1529 deletions(-) delete mode 100644 pkg/mock/controller-runtime/client/mocks.go diff --git a/Makefile b/Makefile index ff6528d7..d8c2b645 100644 --- a/Makefile +++ b/Makefile @@ -120,7 +120,6 @@ generate-mocks: $(MOCKGEN) @$(MOCKGEN) -destination ./pkg/stackit/client/mock/mocks.go -package client github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/stackit/client Factory,DNSClient @$(MOCKGEN) -destination ./pkg/stackit/client/mock/iaas_mock.go -package client github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/stackit/client IaaSClient @$(MOCKGEN) -destination ./pkg/stackit/client/mock/loadbalancing_mock.go -package client github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/stackit/client LoadBalancingClient - @$(MOCKGEN) -destination ./pkg/mock/controller-runtime/client/mocks.go -package client sigs.k8s.io/controller-runtime/pkg/client Client,StatusWriter,Reader,Writer,SubResourceClient .PHONY: generate generate: $(CONTROLLER_GEN) $(CRD_REF_DOCS) $(HELM) $(MOCKGEN) $(YQ) $(YAML2JSON) $(GOIMPORTS) generate-mocks ## Generates the controller-registration, other code-gen, the imagename constants as well as executes go:generate directives diff --git a/pkg/controller/controlplane/valuesprovider_test.go b/pkg/controller/controlplane/valuesprovider_test.go index 7ac628d5..1e9fdda9 100644 --- a/pkg/controller/controlplane/valuesprovider_test.go +++ b/pkg/controller/controlplane/valuesprovider_test.go @@ -10,36 +10,37 @@ import ( "fmt" "time" + calicov1alpha1 "github.com/gardener/gardener-extension-networking-calico/pkg/apis/calico/v1alpha1" extensionscontroller "github.com/gardener/gardener/extensions/pkg/controller" - "github.com/gardener/gardener/extensions/pkg/controller/controlplane/genericactuator" gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1" v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants" extensionsv1alpha1 "github.com/gardener/gardener/pkg/apis/extensions/v1alpha1" - "github.com/gardener/gardener/pkg/utils" + gardenerutils "github.com/gardener/gardener/pkg/utils" + "github.com/gardener/gardener/pkg/utils/chart" + kutil "github.com/gardener/gardener/pkg/utils/kubernetes" secretutils "github.com/gardener/gardener/pkg/utils/secrets" secretsmanager "github.com/gardener/gardener/pkg/utils/secrets/manager" fakesecretsmanager "github.com/gardener/gardener/pkg/utils/secrets/manager/fake" testutils "github.com/gardener/gardener/pkg/utils/test" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/onsi/gomega/format" - "go.uber.org/mock/gomock" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" policyv1 "k8s.io/api/policy/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" vpaautoscalingv1 "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" "sigs.k8s.io/controller-runtime/pkg/client" - fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/client/interceptor" stackitv1alpha1 "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/apis/stackit/v1alpha1" "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/feature" - mockclient "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/mock/controller-runtime/client" "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/openstack" "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/stackit" ) @@ -52,27 +53,26 @@ const ( genericTokenKubeconfigSecretName = "generic-token-kubeconfig-92e9ae14" ) -var ( - requestTimeout = &metav1.Duration{ - Duration: func() time.Duration { d, _ := time.ParseDuration("5m"); return d }(), - } -) - -func defaultControlPlane() *extensionsv1alpha1.ControlPlane { - cpConfig := defaultControlPlaneConfig() - cp := controlPlane("floating-network-id", cpConfig) - return cp +var testRequestTimeout = &metav1.Duration{Duration: 5 * time.Minute} + +func newTestScheme() *runtime.Scheme { + scheme := runtime.NewScheme() + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + utilruntime.Must(apiextensionsv1.AddToScheme(scheme)) + utilruntime.Must(calicov1alpha1.AddToScheme(scheme)) + utilruntime.Must(extensionsv1alpha1.AddToScheme(scheme)) + utilruntime.Must(stackitv1alpha1.AddToScheme(scheme)) + utilruntime.Must(vpaautoscalingv1.AddToScheme(scheme)) + return scheme } -func defaultControlPlaneWithSTACKIT() *extensionsv1alpha1.ControlPlane { - cpConfig := defaultControlPlaneConfig() - cpConfig.Storage.CSI.Name = string(stackitv1alpha1.STACKIT) - cp := controlPlane("floating-network-id", cpConfig) - return cp +func newTestValuesProvider(cl client.Client, scheme *runtime.Scheme, deployALB bool, customLabelDomain string) *valuesProvider { + mgr := &testutils.FakeManager{Scheme: scheme, Client: cl} + return NewValuesProvider(mgr, deployALB, customLabelDomain).(*valuesProvider) } -func defaultControlPlaneConfig() *stackitv1alpha1.ControlPlaneConfig { - cpConfig := &stackitv1alpha1.ControlPlaneConfig{ +func baseControlPlaneConfig() *stackitv1alpha1.ControlPlaneConfig { + return &stackitv1alpha1.ControlPlaneConfig{ CloudControllerManager: &stackitv1alpha1.CloudControllerManagerConfig{ FeatureGates: map[string]bool{ "SomeKubernetesFeature": true, @@ -80,14 +80,13 @@ func defaultControlPlaneConfig() *stackitv1alpha1.ControlPlaneConfig { }, Storage: &stackitv1alpha1.Storage{ CSI: &stackitv1alpha1.CSI{ - Name: stackitv1alpha1.DefaultCSIName, + Name: string(stackitv1alpha1.STACKIT), }, }, } - return cpConfig } -func controlPlane(floatingPoolID string, cfg *stackitv1alpha1.ControlPlaneConfig) *extensionsv1alpha1.ControlPlane { +func baseControlPlane() *extensionsv1alpha1.ControlPlane { return &extensionsv1alpha1.ControlPlane{ ObjectMeta: metav1.ObjectMeta{ Name: "control-plane", @@ -100,7 +99,7 @@ func controlPlane(floatingPoolID string, cfg *stackitv1alpha1.ControlPlaneConfig }, DefaultSpec: extensionsv1alpha1.DefaultSpec{ ProviderConfig: &runtime.RawExtension{ - Raw: encode(cfg), + Raw: encode(baseControlPlaneConfig()), }, }, Region: region, @@ -110,7 +109,7 @@ func controlPlane(floatingPoolID string, cfg *stackitv1alpha1.ControlPlaneConfig Name: technicalID, ID: "network-acbd1234", FloatingPool: stackitv1alpha1.FloatingPoolStatus{ - ID: floatingPoolID, + ID: "floating-network-id", }, Router: stackitv1alpha1.RouterStatus{ ID: "routerID", @@ -128,706 +127,620 @@ func controlPlane(floatingPoolID string, cfg *stackitv1alpha1.ControlPlaneConfig } } -var _ = Describe("ValuesProvider", func() { - format.MaxLength = 8000 - var ( - ctx = context.TODO() - - ctrl *gomock.Controller - - scheme = runtime.NewScheme() - _ = stackitv1alpha1.AddToScheme(scheme) - - fakeClient client.Client - fakeSecretsManager secretsmanager.Interface - - vp genericactuator.ValuesProvider - c *mockclient.MockClient - mgr *testutils.FakeManager - - cp = defaultControlPlane() +func baseCloudProfileConfig() *stackitv1alpha1.CloudProfileConfig { + rescanBlockStorageOnResize := true + ignoreVolumeAZ := true + nodeVolumeAttachLimit := int32(25) + + return &stackitv1alpha1.CloudProfileConfig{ + KeyStoneURL: authURL, + RequestTimeout: testRequestTimeout, + RescanBlockStorageOnResize: &rescanBlockStorageOnResize, + IgnoreVolumeAZ: &ignoreVolumeAZ, + NodeVolumeAttachLimit: &nodeVolumeAttachLimit, + } +} - cidr = "10.250.0.0/19" - rescanBlockStorageOnResize = true - ignoreVolumeAZ = true - nodeVoluemAttachLimit int32 = 25 +func baseCluster() *extensionscontroller.Cluster { + podCIDR := "10.250.0.0/19" - cloudProfileConfig = &stackitv1alpha1.CloudProfileConfig{ - KeyStoneURL: authURL, - RequestTimeout: requestTimeout, - RescanBlockStorageOnResize: new(rescanBlockStorageOnResize), - IgnoreVolumeAZ: new(ignoreVolumeAZ), - NodeVolumeAttachLimit: &nodeVoluemAttachLimit, - } - cloudProfileConfigJSON, _ = json.Marshal(cloudProfileConfig) - - cluster = &extensionscontroller.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - "generic-token-kubeconfig.secret.gardener.cloud/name": genericTokenKubeconfigSecretName, - }, - }, - CloudProfile: &gardencorev1beta1.CloudProfile{ - Spec: gardencorev1beta1.CloudProfileSpec{ - ProviderConfig: &runtime.RawExtension{ - Raw: cloudProfileConfigJSON, - }, - }, + return &extensionscontroller.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "generic-token-kubeconfig.secret.gardener.cloud/name": genericTokenKubeconfigSecretName, }, - Seed: &gardencorev1beta1.Seed{}, - Shoot: &gardencorev1beta1.Shoot{ - Spec: gardencorev1beta1.ShootSpec{ - Region: "RegionOne", - Networking: &gardencorev1beta1.Networking{ - Pods: &cidr, - }, - Kubernetes: gardencorev1beta1.Kubernetes{ - Version: "1.29.0", - VerticalPodAutoscaler: &gardencorev1beta1.VerticalPodAutoscaler{ - Enabled: true, - }, - }, - Provider: gardencorev1beta1.Provider{ - InfrastructureConfig: &runtime.RawExtension{ - Raw: encode(&stackitv1alpha1.InfrastructureConfig{ - TypeMeta: metav1.TypeMeta{ - APIVersion: stackitv1alpha1.SchemeGroupVersion.String(), - Kind: "InfrastructureConfig", - }, - Networks: stackitv1alpha1.Networks{ - Workers: "10.200.0.0/19", - }, - }), - }, - Workers: []gardencorev1beta1.Worker{ - { - Name: "worker", - Zones: []string{"zone2", "zone1"}, - }, - }, - }, - }, - Status: gardencorev1beta1.ShootStatus{ - TechnicalID: technicalID, + }, + CloudProfile: &gardencorev1beta1.CloudProfile{ + Spec: gardencorev1beta1.CloudProfileSpec{ + ProviderConfig: &runtime.RawExtension{ + Raw: encode(baseCloudProfileConfig()), }, }, - } - clusterNoOverlay = &extensionscontroller.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - "generic-token-kubeconfig.secret.gardener.cloud/name": genericTokenKubeconfigSecretName, + }, + Seed: &gardencorev1beta1.Seed{}, + Shoot: &gardencorev1beta1.Shoot{ + ObjectMeta: metav1.ObjectMeta{Name: "test-shoot"}, + Spec: gardencorev1beta1.ShootSpec{ + Region: "RegionOne", + Networking: &gardencorev1beta1.Networking{ + Pods: &podCIDR, }, - }, - CloudProfile: &gardencorev1beta1.CloudProfile{ - Spec: gardencorev1beta1.CloudProfileSpec{ - ProviderConfig: &runtime.RawExtension{ - Raw: cloudProfileConfigJSON, + Kubernetes: gardencorev1beta1.Kubernetes{ + Version: "1.29.0", + VerticalPodAutoscaler: &gardencorev1beta1.VerticalPodAutoscaler{ + Enabled: true, }, }, - }, - Shoot: &gardencorev1beta1.Shoot{ - Spec: gardencorev1beta1.ShootSpec{ - Networking: &gardencorev1beta1.Networking{ - Type: new("calico"), - ProviderConfig: &runtime.RawExtension{ - Raw: []byte(`{"overlay":{"enabled": false}}`), - }, - Pods: &cidr, + Provider: gardencorev1beta1.Provider{ + InfrastructureConfig: &runtime.RawExtension{ + Raw: encode(&stackitv1alpha1.InfrastructureConfig{ + TypeMeta: metav1.TypeMeta{ + APIVersion: stackitv1alpha1.SchemeGroupVersion.String(), + Kind: "InfrastructureConfig", + }, + Networks: stackitv1alpha1.Networks{ + Workers: "10.200.0.0/19", + }, + }), }, - Kubernetes: gardencorev1beta1.Kubernetes{ - Version: "1.31.1", - VerticalPodAutoscaler: &gardencorev1beta1.VerticalPodAutoscaler{ - Enabled: true, + Workers: []gardencorev1beta1.Worker{ + { + Name: "worker", + Zones: []string{"zone2", "zone1"}, }, }, }, - Status: gardencorev1beta1.ShootStatus{ - TechnicalID: technicalID, - }, }, - } + Status: gardencorev1beta1.ShootStatus{ + TechnicalID: technicalID, + }, + }, + } +} - domainName = "domain-name" - tenantName = "tenant-name" - cpSecretKey = client.ObjectKey{Namespace: namespace, Name: v1beta1constants.SecretNameCloudProvider} - cpSecret = &corev1.Secret{ +func clusterWithoutOverlay() *extensionscontroller.Cluster { + cluster := baseCluster() + networkType := "calico" + cluster.Shoot.Spec.Networking = &gardencorev1beta1.Networking{ + Type: &networkType, + ProviderConfig: &runtime.RawExtension{ + Raw: []byte(`{"overlay":{"enabled":false}}`), + }, + Pods: cluster.Shoot.Spec.Networking.Pods, + } + cluster.Shoot.Spec.Kubernetes.Version = "1.31.1" + return cluster +} + +func baseProviderSecret() *corev1.Secret { + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: v1beta1constants.SecretNameCloudProvider, + Namespace: namespace, + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{ + "domainName": []byte("domain-name"), + "tenantName": []byte("tenant-name"), + "username": []byte("username"), + "password": []byte("password"), + "authURL": []byte(authURL), + stackit.ProjectID: []byte("foo"), + stackit.SaKeyJSON: []byte("{}"), + }, + } +} + +func baseCloudProviderConfigSecret() *corev1.Secret { + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: openstack.CloudProviderConfigName, + Namespace: namespace, + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{ + openstack.CloudProviderConfigDataKey: []byte("some data"), + }, + } +} + +func baseCSIDiskConfigSecret() *corev1.Secret { + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: openstack.CloudProviderCSIDiskConfigName, + Namespace: namespace, + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{ + openstack.CloudProviderConfigDataKey: []byte("foo"), + }, + } +} + +func managedSecrets() []client.Object { + return []client.Object{ + &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: v1beta1constants.SecretNameCloudProvider, + Name: caNameControlPlane, Namespace: namespace, }, - Type: corev1.SecretTypeOpaque, Data: map[string][]byte{ - "domainName": []byte(domainName), - "tenantName": []byte(tenantName), - "username": []byte(`username`), - "password": []byte(`password`), - "authURL": []byte(authURL), - stackit.ProjectID: []byte("foo"), - stackit.SaKeyJSON: []byte("{}"), + secretutils.DataKeyCertificateBundle: []byte("fake-ca-cert"), }, - } - - cpConfigKey = client.ObjectKey{Namespace: namespace, Name: openstack.CloudProviderConfigName} - cpConfig = &corev1.Secret{ + }, + &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: openstack.CloudProviderConfigName, + Name: cloudControllerManagerServerName, Namespace: namespace, }, - Type: corev1.SecretTypeOpaque, - Data: map[string][]byte{ - openstack.CloudProviderConfigDataKey: []byte("some data"), - }, - } - - cloudProviderDiskConfig = []byte("foo") - cpCSIDiskConfigKey = client.ObjectKey{Namespace: namespace, Name: openstack.CloudProviderCSIDiskConfigName} - cpCSIDiskConfig = &corev1.Secret{ + }, + &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: openstack.CloudProviderCSIDiskConfigName, + Name: stackitPodIdentityWebhookServerName, Namespace: namespace, }, - Type: corev1.SecretTypeOpaque, - Data: map[string][]byte{ - openstack.CloudProviderConfigDataKey: cloudProviderDiskConfig, - }, - } + }, + } +} + +func legacyCleanupObjects() []client.Object { + stackitSnapshotName := fmt.Sprintf("%s-%s", CSIStackitPrefix, openstack.CSISnapshotValidationName) + + return []client.Object{ + &networkingv1.NetworkPolicy{ObjectMeta: metav1.ObjectMeta{Name: "allow-kube-apiserver-to-csi-snapshot-validation", Namespace: namespace}}, + &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: openstack.CSISnapshotValidationName, Namespace: namespace}}, + &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: openstack.CSISnapshotValidationName, Namespace: namespace}}, + &vpaautoscalingv1.VerticalPodAutoscaler{ObjectMeta: metav1.ObjectMeta{Name: "csi-snapshot-webhook-vpa", Namespace: namespace}}, + &policyv1.PodDisruptionBudget{ObjectMeta: metav1.ObjectMeta{Name: openstack.CSISnapshotValidationName, Namespace: namespace}}, + &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: stackitSnapshotName, Namespace: namespace}}, + &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: stackitSnapshotName, Namespace: namespace}}, + &vpaautoscalingv1.VerticalPodAutoscaler{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%s-csi-snapshot-webhook-vpa", CSIStackitPrefix), Namespace: namespace}}, + &policyv1.PodDisruptionBudget{ObjectMeta: metav1.ObjectMeta{Name: stackitSnapshotName, Namespace: namespace}}, + &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%s-%s", CSIStackitPrefix, openstack.CloudProviderConfigName), Namespace: namespace}}, + } +} + +func emergencyLBSecret() *corev1.Secret { + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: LoadBalancerEmergencyAccessSecretName, + Namespace: namespace, + }, + Data: map[string][]byte{ + LoadBalancerEmergencyAccessAPIURLKey: []byte("foo"), + LoadBalancerEmergencyAccessAPITokenKey: []byte("bar"), + }, + } +} + +func encode(obj any) []byte { + data, err := json.Marshal(obj) + Expect(err).NotTo(HaveOccurred()) + return data +} + +func createObjects(ctx context.Context, cl client.Client, objects ...client.Object) { + for _, obj := range objects { + ExpectWithOffset(1, cl.Create(ctx, obj)).To(Succeed()) + } +} - checksums = map[string]string{ - v1beta1constants.SecretNameCloudProvider: "8bafb35ff1ac60275d62e1cbd495aceb511fb354f74a20f7d06ecb48b3a68432", - openstack.CloudProviderConfigName: "bf19236c3ff3be18cf28cb4f58532bda4fd944857dd163baa05d23f952550392", - openstack.CloudProviderCSIDiskConfigName: "77627eb2343b9f2dc2fca3cce35f2f9eec55783aa5f7dac21c473019e5825de2", +func checksumsFor(secrets ...*corev1.Secret) map[string]string { + checksums := make(map[string]string, len(secrets)) + for _, secret := range secrets { + checksums[secret.Name] = gardenerutils.ComputeChecksum(secret.Data) + } + return checksums +} + +func chartValues(values map[string]any, chartName string) map[string]any { + ExpectWithOffset(1, values).To(HaveKey(chartName)) + if values[chartName] == nil { + return nil + } + + chartValues, ok := values[chartName].(map[string]any) + ExpectWithOffset(1, ok).To(BeTrue(), "expected chart %q values to be a map", chartName) + return chartValues +} + +func instantiateChartObject(template client.Object, name, namespace string) client.Object { + obj := template.DeepCopyObject().(client.Object) + obj.SetName(name) + obj.SetNamespace(namespace) + return obj +} + +func subChartObjects(subCharts []*chart.Chart, subChartName, namespace string) []client.Object { + var objects []client.Object + for _, subChart := range subCharts { + if subChart.Name != subChartName { + continue } + for _, obj := range subChart.Objects { + objects = append(objects, instantiateChartObject(obj.Type, obj.Name, namespace)) + } + } + ExpectWithOffset(1, objects).NotTo(BeEmpty(), "expected subchart %q to exist", subChartName) + return objects +} - enabledTrue = map[string]any{"enabled": true} - enabledFalse = map[string]any{"enabled": false} - empty = func() map[string]any { return nil } // no idea why this is required, apparently there is a difference between and empty map and just `nil`... - ) +func seedUnusedControlPlaneCSIObjects(ctx context.Context, cl client.Client, subChartName string) []client.Object { + objects := subChartObjects(controlPlaneChart.SubCharts, subChartName, namespace) + createObjects(ctx, cl, objects...) + return objects +} - BeforeEach(func() { - ctrl = gomock.NewController(GinkgoT()) +func seedReadyControlPlane(ctx context.Context, cl client.Client) (*extensionsv1alpha1.ControlPlane, *extensionscontroller.Cluster, *corev1.Secret, *corev1.Secret, *corev1.Secret) { + cp := baseControlPlane() + cluster := baseCluster() + providerSecret := baseProviderSecret() + configSecret := baseCloudProviderConfigSecret() + diskSecret := baseCSIDiskConfigSecret() + + //nolint:prealloc // this is just a test + var objects []client.Object + objects = append(objects, providerSecret, configSecret, diskSecret) + objects = append(objects, managedSecrets()...) + createObjects(ctx, cl, objects...) + return cp, cluster, providerSecret, configSecret, diskSecret +} - c = mockclient.NewMockClient(ctrl) +func seedReadyShoot(ctx context.Context, cl client.Client) (*extensionsv1alpha1.ControlPlane, *extensionscontroller.Cluster) { + cp := baseControlPlane() + cluster := baseCluster() + createObjects(ctx, cl, baseProviderSecret()) + createObjects(ctx, cl, managedSecrets()...) + return cp, cluster +} - fakeClient = fakeclient.NewClientBuilder().Build() - fakeSecretsManager = fakesecretsmanager.New(fakeClient, namespace) +func expectObjectsDeleted(ctx context.Context, cl client.Client, objects ...client.Object) { + for _, obj := range objects { + err := cl.Get(ctx, client.ObjectKeyFromObject(obj), obj) + ExpectWithOffset(1, errors.IsNotFound(err)).To(BeTrue(), "expected %T %s to be deleted", obj, client.ObjectKeyFromObject(obj).String()) + } +} - mgr = &testutils.FakeManager{Scheme: scheme, Client: c} +func expectedUserAgentHeaders() []string { + return []string{"domain-name", "tenant-name", technicalID} +} - vp = NewValuesProvider(mgr, true, "kubernetes.io") - }) +func expectedSTACKITCCMConfig(customLabelDomain string, apiEndpoints *stackitv1alpha1.APIEndpoints) map[string]any { + config := map[string]any{ + "stackitProjectID": "foo", + "stackitNetworkID": "network-acbd1234", + "stackitRegion": "eu01", + "extraLabels": map[string]string{ + STACKITLBClusterLabelKey: technicalID, + }, + "customLabelDomain": customLabelDomain, + } + + if apiEndpoints != nil { + if apiEndpoints.LoadBalancer != nil { + config["loadBalancerApiUrl"] = *apiEndpoints.LoadBalancer + } + if apiEndpoints.IaaS != nil { + config["iaasApiUrl"] = *apiEndpoints.IaaS + } + if apiEndpoints.TokenEndpoint != nil { + config["tokenUrl"] = *apiEndpoints.TokenEndpoint + } + } + + return config +} + +var _ = Describe("ValuesProvider fake client", func() { + var ( + ctx context.Context + scheme *runtime.Scheme + c client.Client + secretsManager secretsmanager.Interface + vp *valuesProvider + ) - AfterEach(func() { - ctrl.Finish() + BeforeEach(func() { + ctx = context.Background() + scheme = newTestScheme() + c = fake.NewClientBuilder().WithScheme(scheme).Build() + secretsManager = fakesecretsmanager.New(c, namespace) + vp = newTestValuesProvider(c, scheme, true, "kubernetes.io") }) Describe("#GetConfigChartValues", func() { - configChartValues := map[string]any{ - "domainName": "domain-name", - "tenantName": "tenant-name", - "username": "username", - "password": "password", - "region": region, - "insecure": false, - "authUrl": authURL, - "requestTimeout": requestTimeout, - "ignoreVolumeAZ": ignoreVolumeAZ, - "applicationCredentialID": "", - "applicationCredentialSecret": "", - "applicationCredentialName": "", - "internalNetworkName": technicalID, - } - BeforeEach(func() { - // Disable STACKIT feature flags for OpenStack-only tests DeferCleanup(testutils.WithFeatureGate(feature.MutableGate, feature.UseSTACKITAPIInfrastructureController, false)) DeferCleanup(testutils.WithFeatureGate(feature.MutableGate, feature.UseSTACKITMachineControllerManager, false)) }) - It("should return correct config chart values", func() { - c.EXPECT().Get(ctx, cpSecretKey, &corev1.Secret{}).DoAndReturn(clientGet(cpSecret)) + expectedConfigChartValues := func() map[string]any { + return map[string]any{ + "domainName": "domain-name", + "tenantName": "tenant-name", + "username": "username", + "password": "password", + "region": region, + "insecure": false, + "authUrl": authURL, + "requestTimeout": testRequestTimeout, + "ignoreVolumeAZ": true, + "applicationCredentialID": "", + "applicationCredentialSecret": "", + "applicationCredentialName": "", + "internalNetworkName": technicalID, + } + } + + It("returns config values for username and password credentials", func() { + cp := baseControlPlane() + cluster := baseCluster() + providerSecret := baseProviderSecret() + createObjects(ctx, c, providerSecret) values, err := vp.GetConfigChartValues(ctx, cp, cluster) Expect(err).NotTo(HaveOccurred()) - Expect(values).To(Equal(configChartValues)) + Expect(values).To(Equal(expectedConfigChartValues())) }) - It("should return correct config chart values with application credentials", func() { - secret2 := *cpSecret - secret2.Data = map[string][]byte{ - "domainName": []byte(domainName), - "tenantName": []byte(tenantName), - "applicationCredentialID": []byte(`app-id`), - "applicationCredentialSecret": []byte(`app-secret`), + It("returns config values for application credentials", func() { + cp := baseControlPlane() + cluster := baseCluster() + providerSecret := baseProviderSecret() + providerSecret.Data = map[string][]byte{ + "domainName": []byte("domain-name"), + "tenantName": []byte("tenant-name"), + "applicationCredentialID": []byte("app-id"), + "applicationCredentialSecret": []byte("app-secret"), "authURL": []byte(authURL), + stackit.ProjectID: []byte("foo"), + stackit.SaKeyJSON: []byte("{}"), } + createObjects(ctx, c, providerSecret) - c.EXPECT().Get(ctx, cpSecretKey, &corev1.Secret{}).DoAndReturn(clientGet(&secret2)) - - expectedValues := utils.MergeMaps(configChartValues, map[string]any{ - "username": "", - "password": "", - "applicationCredentialID": "app-id", - "applicationCredentialSecret": "app-secret", - "insecure": false, - }) values, err := vp.GetConfigChartValues(ctx, cp, cluster) Expect(err).NotTo(HaveOccurred()) + expectedValues := expectedConfigChartValues() + expectedValues["username"] = "" + expectedValues["password"] = "" + expectedValues["applicationCredentialID"] = "app-id" + expectedValues["applicationCredentialSecret"] = "app-secret" Expect(values).To(Equal(expectedValues)) }) - It("should configure cloud routes when not using overlay", func() { - c.EXPECT().Get(ctx, cpSecretKey, &corev1.Secret{}).DoAndReturn(clientGet(cpSecret)) - expectedValues := utils.MergeMaps(configChartValues, map[string]any{ - "routerID": "routerID", - }) - values, err := vp.GetConfigChartValues(ctx, cp, clusterNoOverlay) + It("enables route controller when overlay is disabled", func() { + cp := baseControlPlane() + cluster := clusterWithoutOverlay() + createObjects(ctx, c, baseProviderSecret()) + + values, err := vp.GetConfigChartValues(ctx, cp, cluster) Expect(err).NotTo(HaveOccurred()) + expectedValues := expectedConfigChartValues() + expectedValues["routerID"] = "routerID" Expect(values).To(Equal(expectedValues)) }) - It("should not configure cloud routes when not using overlay but using BGP as backend", func() { - c.EXPECT().Get(ctx, cpSecretKey, &corev1.Secret{}).DoAndReturn(clientGet(cpSecret)) - net := &gardencorev1beta1.Networking{ - Type: new("calico"), - ProviderConfig: &runtime.RawExtension{ - Raw: []byte(`{"backend":"bird","overlay":{"enabled": false}}`), - }, - Pods: &cidr, + It("disables route controller when overlay is disabled but BGP backend is active", func() { + cp := baseControlPlane() + cluster := clusterWithoutOverlay() + cluster.Shoot.Spec.Networking.ProviderConfig = &runtime.RawExtension{ + Raw: []byte(`{"backend":"bird","overlay":{"enabled":false}}`), } - clusterNoOverlay.Shoot.Spec.Networking = net - values, err := vp.GetConfigChartValues(ctx, cp, clusterNoOverlay) + createObjects(ctx, c, baseProviderSecret()) + + values, err := vp.GetConfigChartValues(ctx, cp, cluster) Expect(err).NotTo(HaveOccurred()) - Expect(values).To(Equal(configChartValues)) + Expect(values).To(Equal(expectedConfigChartValues())) }) - It("should return correct config chart values with KeyStone CA Cert", func() { - secret2 := cpSecret.DeepCopy() - caCert := "custom-cert" - secret2.Data["caCert"] = []byte(caCert) - c.EXPECT().Get(ctx, cpSecretKey, &corev1.Secret{}).DoAndReturn(clientGet(secret2)) - expectedValues := utils.MergeMaps(configChartValues, map[string]any{ - "caCert": caCert, - }) + It("propagates a custom keystone CA certificate", func() { + cp := baseControlPlane() + cluster := baseCluster() + providerSecret := baseProviderSecret() + providerSecret.Data["caCert"] = []byte("custom-cert") + createObjects(ctx, c, providerSecret) + values, err := vp.GetConfigChartValues(ctx, cp, cluster) Expect(err).NotTo(HaveOccurred()) + expectedValues := expectedConfigChartValues() + expectedValues["caCert"] = "custom-cert" Expect(values).To(Equal(expectedValues)) }) }) Describe("#GetControlPlaneChartValues", func() { - ccmChartValues := utils.MergeMaps(enabledFalse, map[string]any{ - "replicas": 1, - "kubernetesVersion": "1.29.0", - "technicalID": technicalID, - "podNetwork": cidr, - "podAnnotations": map[string]any{ - "checksum/secret-" + v1beta1constants.SecretNameCloudProvider: checksums[v1beta1constants.SecretNameCloudProvider], - "checksum/secret-" + openstack.CloudProviderConfigName: checksums[openstack.CloudProviderConfigName], - }, - "podLabels": map[string]any{ - "maintenance.gardener.cloud/restart": "true", - }, - "featureGates": map[string]bool{ - "SomeKubernetesFeature": true, - }, - "tlsCipherSuites": []string{ - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "TLS_AES_128_GCM_SHA256", - "TLS_AES_256_GCM_SHA384", - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - "TLS_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", - "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", - }, - "secrets": map[string]any{ - "server": "cloud-controller-manager-server", - }, - }) + It("returns default control plane values with STACKIT CSI active", func() { + cp, cluster, providerSecret, configSecret, diskSecret := seedReadyControlPlane(ctx, c) - stackitCcmChartValues := utils.MergeMaps(enabledTrue, map[string]any{ - "replicas": 1, - "config": map[string]any{ - "stackitProjectID": "foo", - "stackitNetworkID": "network-acbd1234", - "stackitRegion": "eu01", - "extraLabels": map[string]string{ - STACKITLBClusterLabelKey: "shoot--dev--test", - // Disabled as the load balancer API is currently not accepting `/` in the label - // TODO: enable this as soon as the load balancer API supports this - // "kubernetes.io/cluster": "shoot--dev--test", - }, - "customLabelDomain": "kubernetes.io", - }, - "technicalID": technicalID, - "podAnnotations": map[string]any{ - "checksum/secret-" + v1beta1constants.SecretNameCloudProvider: checksums[v1beta1constants.SecretNameCloudProvider], - "checksum/config-" + openstack.STACKITCloudControllerManagerImageName: utils.ComputeChecksum(map[string]any{ - "stackitProjectID": "foo", - "stackitNetworkID": "network-acbd1234", - "stackitRegion": "eu01", - "extraLabels": map[string]string{ - STACKITLBClusterLabelKey: "shoot--dev--test", - // Disabled as the load balancer API is currently not accepting `/` in the label - // TODO: enable this as soon as the load balancer API supports this - // "kubernetes.io/cluster": "shoot--dev--test", - }, - "customLabelDomain": "kubernetes.io", - }), - }, - "podLabels": map[string]any{ - "maintenance.gardener.cloud/restart": "true", - }, - "featureGates": map[string]bool{ - "SomeKubernetesFeature": true, - }, - "controllers": []string{"*"}, - }) + values, err := vp.GetControlPlaneChartValues(ctx, cp, cluster, secretsManager, checksumsFor(providerSecret), false) + Expect(err).NotTo(HaveOccurred()) - stackitAlbChartValues := utils.MergeMaps(enabledTrue, map[string]any{ - "replicas": 1, - "config": map[string]any{ - "stackitProjectID": "foo", - "stackitNetworkID": "network-acbd1234", - "region": "eu01", - }, - }) + Expect(values).To(HaveKeyWithValue("global", map[string]any{ + "genericTokenKubeconfigSecretName": genericTokenKubeconfigSecretName, + })) - stackitPodIdentityWebhookChartSeedValues := map[string]any{ - "enabled": false, - "replicaCount": 1, - "webhook": map[string]any{ - "tlsSecretName": stackitPodIdentityWebhookServerName, - }, - } + ccmValues := chartValues(values, openstack.CloudControllerManagerName) + Expect(ccmValues).To(BeComparableTo(map[string]any{ + "enabled": false, + "replicas": 1, + "technicalID": technicalID, + "kubernetesVersion": "1.29.0", + "podNetwork": "10.250.0.0/19", + "podAnnotations": map[string]any{ + "checksum/secret-" + v1beta1constants.SecretNameCloudProvider: gardenerutils.ComputeChecksum(providerSecret.Data), + "checksum/secret-" + openstack.CloudProviderConfigName: gardenerutils.ComputeChecksum(configSecret.Data), + }, + "podLabels": map[string]any{ + v1beta1constants.LabelPodMaintenanceRestart: "true", + }, + "featureGates": map[string]bool{ + "SomeKubernetesFeature": true, + }, + "tlsCipherSuites": kutil.TLSCipherSuites, + "secrets": map[string]any{ + "server": cloudControllerManagerServerName, + }, + "userAgentHeaders": expectedUserAgentHeaders(), + })) - BeforeEach(func() { - c.EXPECT().Get(ctx, cpConfigKey, &corev1.Secret{}).DoAndReturn(clientGet(cpConfig)) - c.EXPECT().Delete(context.TODO(), &networkingv1.NetworkPolicy{ObjectMeta: metav1.ObjectMeta{Name: "allow-kube-apiserver-to-csi-snapshot-validation", Namespace: cp.Namespace}}) - - // TODO: Remove once cleanup is completed (aka. next release) - c.EXPECT().Delete(context.TODO(), &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: openstack.CSISnapshotValidationName, Namespace: namespace}}) - c.EXPECT().Delete(context.TODO(), &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: openstack.CSISnapshotValidationName, Namespace: namespace}}) - c.EXPECT().Delete(context.TODO(), &vpaautoscalingv1.VerticalPodAutoscaler{ObjectMeta: metav1.ObjectMeta{Name: "csi-snapshot-webhook-vpa", Namespace: namespace}}) - c.EXPECT().Delete(context.TODO(), &policyv1.PodDisruptionBudget{ObjectMeta: metav1.ObjectMeta{Name: "csi-snapshot-validation", Namespace: namespace}}) - // STACKIT CSI Cleanup - c.EXPECT().Delete(context.TODO(), &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%s-%s", CSIStackitPrefix, openstack.CSISnapshotValidationName), Namespace: namespace}}) - c.EXPECT().Delete(context.TODO(), &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%s-%s", CSIStackitPrefix, openstack.CSISnapshotValidationName), Namespace: namespace}}) - c.EXPECT().Delete(context.TODO(), &vpaautoscalingv1.VerticalPodAutoscaler{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%s-csi-snapshot-webhook-vpa", CSIStackitPrefix), Namespace: namespace}}) - c.EXPECT().Delete(context.TODO(), &policyv1.PodDisruptionBudget{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%s-%s", CSIStackitPrefix, openstack.CSISnapshotValidationName), Namespace: namespace}}) - // STACKIT Cloud Provider Config Cleanup - c.EXPECT().Delete(context.TODO(), &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%s-%s", CSIStackitPrefix, openstack.CloudProviderConfigName), Namespace: namespace}}) - - By("creating secrets managed outside of this package for whose secretsmanager.Get() will be called") - Expect(fakeClient.Create(context.TODO(), &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "ca-provider-openstack-controlplane", Namespace: namespace}, Data: map[string][]byte{secretutils.DataKeyCertificateBundle: []byte("fake-ca-cert")}})).To(Succeed()) - Expect(fakeClient.Create(context.TODO(), &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "cloud-controller-manager-server", Namespace: namespace}})).To(Succeed()) - Expect(fakeClient.Create(context.TODO(), &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: stackitPodIdentityWebhookServerName, Namespace: namespace}})).To(Succeed()) - - // This call is made for emergency Loadbalancer API access. - // It will return a NotFound error by default to not interfere with existing tests. - // Returning this error effectively disables the emergency access feature. - c.EXPECT().Get(ctx, types.NamespacedName{Name: LoadBalancerEmergencyAccessSecretName, Namespace: namespace}, &corev1.Secret{}).Return( - errors.NewNotFound(schema.GroupResource{Resource: "secret"}, LoadBalancerEmergencyAccessSecretName)) - }) + expectedSTACKITCCMConfig := expectedSTACKITCCMConfig("kubernetes.io", nil) + stackitCCMValues := chartValues(values, openstack.STACKITCloudControllerManagerName) + Expect(stackitCCMValues).To(BeComparableTo(map[string]any{ + "enabled": true, + "replicas": 1, + "technicalID": technicalID, + "config": expectedSTACKITCCMConfig, + "controllers": []string{"*"}, + "podAnnotations": map[string]any{ + "checksum/secret-" + v1beta1constants.SecretNameCloudProvider: gardenerutils.ComputeChecksum(providerSecret.Data), + "checksum/config-" + openstack.STACKITCloudControllerManagerImageName: gardenerutils.ComputeChecksum(expectedSTACKITCCMConfig), + }, + "podLabels": map[string]any{ + v1beta1constants.LabelPodMaintenanceRestart: "true", + }, + "featureGates": map[string]bool{ + "SomeKubernetesFeature": true, + }, + })) - It("should return correct control plane chart values", func() { - c.EXPECT().Get(ctx, cpCSIDiskConfigKey, &corev1.Secret{}).DoAndReturn(clientGet(cpCSIDiskConfig)) - c.EXPECT().Get(ctx, cpSecretKey, &corev1.Secret{}).DoAndReturn(clientGet(cpSecret)).Times(2) + Expect(chartValues(values, openstack.CSIControllerName)).To(Equal(map[string]any{ + "enabled": false, + })) - values, err := vp.GetControlPlaneChartValues(ctx, cp, cluster, fakeSecretsManager, checksums, false) - Expect(err).NotTo(HaveOccurred()) - val := values[openstack.CloudControllerManagerName] - Expect(val).To(HaveKeyWithValue("enabled", false)) - Expect(values).To(BeComparableTo(map[string]any{ - "global": map[string]any{ - "genericTokenKubeconfigSecretName": genericTokenKubeconfigSecretName, + stackitCSIValues := chartValues(values, openstack.CSISTACKITControllerName) + Expect(stackitCSIValues).To(BeComparableTo(map[string]any{ + "enabled": true, + "projectID": "foo", + "region": "eu01", + "replicas": 1, + "podAnnotations": map[string]any{ + "checksum/secret-" + openstack.CloudProviderCSIDiskConfigName: gardenerutils.ComputeChecksum(diskSecret.Data), + "checksum/secret-" + v1beta1constants.SecretNameCloudProvider: gardenerutils.ComputeChecksum(providerSecret.Data), }, - openstack.CSIControllerName: enabledFalse, - openstack.CloudControllerManagerName: utils.MergeMaps(ccmChartValues, map[string]any{ - "userAgentHeaders": []string{domainName, tenantName, technicalID}, - "kubernetesVersion": cluster.Shoot.Spec.Kubernetes.Version, - }), - openstack.STACKITCloudControllerManagerName: utils.MergeMaps(stackitCcmChartValues, map[string]any{ - "config": map[string]any{"stackitProjectID": string(cpSecret.Data[stackit.ProjectID])}, - }), - openstack.CSISTACKITControllerName: utils.MergeMaps(enabledTrue, map[string]any{ + "csiSnapshotController": map[string]any{ "replicas": 1, - "podAnnotations": map[string]any{ - "checksum/secret-" + openstack.CloudProviderCSIDiskConfigName: checksums[openstack.CloudProviderCSIDiskConfigName], - "checksum/secret-" + v1beta1constants.SecretNameCloudProvider: checksums[v1beta1constants.SecretNameCloudProvider], - }, - "projectID": string(cpSecret.Data[stackit.ProjectID]), - "region": "eu01", - "stackitEndpoints": map[string]string{}, - "userAgentHeaders": []string{domainName, tenantName, technicalID}, - "customLabelDomain": "kubernetes.io", - "csiSnapshotController": map[string]any{ - "replicas": 1, - }, - }), - stackit.PodIdentityWebhookName: stackitPodIdentityWebhookChartSeedValues, - openstack.STACKITALBControllerManagerName: empty(), + }, + "stackitEndpoints": map[string]string{}, + "customLabelDomain": "kubernetes.io", + "userAgentHeaders": expectedUserAgentHeaders(), })) - }) - It("should return correct control plane chart values", func() { - c.EXPECT().Get(ctx, cpCSIDiskConfigKey, &corev1.Secret{}).DoAndReturn(clientGet(cpCSIDiskConfig)) - c.EXPECT().Get(ctx, cpSecretKey, &corev1.Secret{}).DoAndReturn(clientGet(cpSecret)).Times(2) - - controlPlaneConfig := defaultControlPlaneConfig() - controlPlaneConfig.Storage = &stackitv1alpha1.Storage{ - CSI: &stackitv1alpha1.CSI{ - Name: string(stackitv1alpha1.OPENSTACK), + Expect(chartValues(values, stackit.PodIdentityWebhookName)).To(BeComparableTo(map[string]any{ + "enabled": false, + "replicaCount": 1, + "webhook": map[string]any{ + "tlsSecretName": stackitPodIdentityWebhookServerName, }, - } - cp.Spec.ProviderConfig.Raw = encode(controlPlaneConfig) + })) + Expect(values[openstack.STACKITALBControllerManagerName]).To(BeNil()) + }) + + It("returns OpenStack CSI values when selected", func() { + cp, cluster, providerSecret, _, diskSecret := seedReadyControlPlane(ctx, c) + cpConfig := baseControlPlaneConfig() + cpConfig.Storage.CSI.Name = string(stackitv1alpha1.OPENSTACK) + cp.Spec.ProviderConfig.Raw = encode(cpConfig) - values, err := vp.GetControlPlaneChartValues(ctx, cp, cluster, fakeSecretsManager, checksums, false) + values, err := vp.GetControlPlaneChartValues(ctx, cp, cluster, secretsManager, checksumsFor(providerSecret), false) Expect(err).NotTo(HaveOccurred()) - Expect(values).To(BeComparableTo(map[string]any{ - "global": map[string]any{ - "genericTokenKubeconfigSecretName": genericTokenKubeconfigSecretName, + + Expect(chartValues(values, openstack.CSISTACKITControllerName)).To(Equal(map[string]any{ + "enabled": false, + })) + Expect(chartValues(values, openstack.CSIControllerName)).To(BeComparableTo(map[string]any{ + "enabled": true, + "replicas": 1, + "maxEntries": 1000, + "kubernetesVersion": "1.29.0", + "podAnnotations": map[string]any{ + "checksum/secret-" + openstack.CloudProviderCSIDiskConfigName: gardenerutils.ComputeChecksum(diskSecret.Data), }, - openstack.CSISTACKITControllerName: enabledFalse, - openstack.CloudControllerManagerName: utils.MergeMaps(ccmChartValues, map[string]any{ - "enabled": false, - "userAgentHeaders": []string{domainName, tenantName, technicalID}, - "kubernetesVersion": cluster.Shoot.Spec.Kubernetes.Version, - }), - openstack.STACKITCloudControllerManagerName: utils.MergeMaps(stackitCcmChartValues, map[string]any{ - "config": map[string]any{"stackitProjectID": string(cpSecret.Data[stackit.ProjectID])}, - }), - openstack.CSIControllerName: utils.MergeMaps(enabledTrue, map[string]any{ - "replicas": 1, - "maxEntries": 1000, - "kubernetesVersion": cluster.Shoot.Spec.Kubernetes.Version, - "podAnnotations": map[string]any{ - "checksum/secret-" + openstack.CloudProviderCSIDiskConfigName: checksums[openstack.CloudProviderCSIDiskConfigName], - }, - "userAgentHeaders": []string{domainName, tenantName, technicalID}, - "csiSnapshotController": map[string]any{ - "replicas": 1, - }, - }), - stackit.PodIdentityWebhookName: stackitPodIdentityWebhookChartSeedValues, - openstack.STACKITALBControllerManagerName: empty(), + "csiSnapshotController": map[string]any{ + "replicas": 1, + }, + "userAgentHeaders": expectedUserAgentHeaders(), })) }) - It("should return correct control plane chart values", func() { - c.EXPECT().Get(ctx, cpCSIDiskConfigKey, &corev1.Secret{}).DoAndReturn(clientGet(cpCSIDiskConfig)) - c.EXPECT().Get(ctx, cpSecretKey, &corev1.Secret{}).DoAndReturn(clientGet(cpSecret)).Times(2) - - controlPlaneConfig := defaultControlPlaneConfig() - controlPlaneConfig.CloudControllerManager = &stackitv1alpha1.CloudControllerManagerConfig{ - Name: string(stackitv1alpha1.OPENSTACK), - } - cp.Spec.ProviderConfig.Raw = encode(controlPlaneConfig) + It("enables OpenStack CCM while reducing STACKIT CCM controllers", func() { + cp, cluster, providerSecret, _, _ := seedReadyControlPlane(ctx, c) + cpConfig := baseControlPlaneConfig() + cpConfig.CloudControllerManager.Name = string(stackitv1alpha1.OPENSTACK) + cp.Spec.ProviderConfig.Raw = encode(cpConfig) - values, err := vp.GetControlPlaneChartValues(ctx, cp, cluster, fakeSecretsManager, checksums, false) + values, err := vp.GetControlPlaneChartValues(ctx, cp, cluster, secretsManager, checksumsFor(providerSecret), false) Expect(err).NotTo(HaveOccurred()) - Expect(values[openstack.STACKITCloudControllerManagerName]).To(HaveKeyWithValue("enabled", true)) - Expect(values[openstack.STACKITCloudControllerManagerName]).To(HaveKeyWithValue("controllers", []string{STACKITCCMServiceLoadbalancerController})) - Expect(values[openstack.CSIControllerName]).To(HaveKeyWithValue("enabled", false)) - }) - DescribeTable("stackit ccm config ", - func(apiEndpoints *stackitv1alpha1.APIEndpoints, cpConfig *stackitv1alpha1.ControlPlaneConfig, expectedConfig map[string]any, expectedControllers []string) { - c.EXPECT().Get(ctx, cpCSIDiskConfigKey, &corev1.Secret{}).DoAndReturn(clientGet(cpCSIDiskConfig)) - c.EXPECT().Get(ctx, cpSecretKey, &corev1.Secret{}).DoAndReturn(clientGet(cpSecret)).Times(2) + Expect(chartValues(values, openstack.CloudControllerManagerName)).To(HaveKeyWithValue("enabled", true)) + Expect(chartValues(values, openstack.STACKITCloudControllerManagerName)).To(HaveKeyWithValue("controllers", []string{STACKITCCMServiceLoadbalancerController})) + Expect(chartValues(values, openstack.CSIControllerName)).To(HaveKeyWithValue("enabled", false)) + }) - controlPlaneConfig := defaultControlPlaneConfig() + DescribeTable("renders STACKIT CCM config variants", + func(apiEndpoints *stackitv1alpha1.APIEndpoints, cpConfig *stackitv1alpha1.ControlPlaneConfig, expectedControllers []string) { + cp, cluster, providerSecret, _, _ := seedReadyControlPlane(ctx, c) if cpConfig != nil { - controlPlaneConfig = cpConfig - } - cp.Spec.ProviderConfig.Raw = encode(controlPlaneConfig) - - // Create CloudProfileConfig with custom API endpoints - testCloudProfileConfig := &stackitv1alpha1.CloudProfileConfig{ - APIEndpoints: apiEndpoints, + cp.Spec.ProviderConfig.Raw = encode(cpConfig) } - testCloudProfileConfigJSON, _ := json.Marshal(testCloudProfileConfig) - testCluster := *cluster + cloudProfileConfig := baseCloudProfileConfig() + cloudProfileConfig.APIEndpoints = apiEndpoints + cluster.CloudProfile.Spec.ProviderConfig = &runtime.RawExtension{Raw: encode(cloudProfileConfig)} - cloudProfile := &gardencorev1beta1.CloudProfile{} - if cluster.CloudProfile != nil { - *cloudProfile = *cluster.CloudProfile - } - cloudProfile.Spec.ProviderConfig = &runtime.RawExtension{ - Raw: testCloudProfileConfigJSON, - } - - testCluster.CloudProfile = cloudProfile - - if expectedControllers == nil { - stackitCCMDeletion(ctx, c) - } - - vpStackitConf := NewValuesProvider(mgr, true, "kubernetes.io") - values, err := vpStackitConf.GetControlPlaneChartValues(ctx, cp, &testCluster, fakeSecretsManager, checksums, false) + values, err := vp.GetControlPlaneChartValues(ctx, cp, cluster, secretsManager, checksumsFor(providerSecret), false) Expect(err).NotTo(HaveOccurred()) - Expect(values).To(HaveKey(openstack.STACKITCloudControllerManagerName)) - if expectedConfig != nil { - Expect(values[openstack.STACKITCloudControllerManagerName]).To(HaveKeyWithValue("config", expectedConfig)) - } else { - Expect(values[openstack.STACKITCloudControllerManagerName]).To(BeNil()) - } - - if expectedControllers != nil { - Expect(values[openstack.STACKITCloudControllerManagerName]).To(HaveKeyWithValue("controllers", expectedControllers)) - } else { - Expect(values[openstack.STACKITCloudControllerManagerName]).To(BeNil()) - } + expectedConfig := expectedSTACKITCCMConfig("kubernetes.io", apiEndpoints) + stackitCCMValues := chartValues(values, openstack.STACKITCloudControllerManagerName) + Expect(stackitCCMValues).To(HaveKeyWithValue("config", BeComparableTo(expectedConfig))) + Expect(stackitCCMValues).To(HaveKeyWithValue("controllers", expectedControllers)) }, - Entry("default", - nil, - nil, - map[string]any{ - "stackitProjectID": string(cpSecret.Data[stackit.ProjectID]), - "stackitNetworkID": "network-acbd1234", - "stackitRegion": "eu01", - "extraLabels": map[string]string{ - STACKITLBClusterLabelKey: "shoot--dev--test", - // Disabled as the load balancer API is currently not accepting `/` in the label - // TODO: enable this as soon as the load balancer API supports this - // "kubernetes.io/cluster": "shoot--dev--test", - }, - "customLabelDomain": "kubernetes.io", - }, - []string{"*"}, - ), - Entry("custom LoadBalancer endpoint", - &stackitv1alpha1.APIEndpoints{ - LoadBalancer: new("https://custom-lb.stackit.cloud"), - }, + Entry("default endpoints", nil, nil, []string{"*"}), + Entry("custom load balancer endpoint", + &stackitv1alpha1.APIEndpoints{LoadBalancer: new("https://custom-lb.stackit.cloud")}, nil, - map[string]any{ - "stackitProjectID": string(cpSecret.Data[stackit.ProjectID]), - "stackitNetworkID": "network-acbd1234", - "stackitRegion": "eu01", - "extraLabels": map[string]string{ - STACKITLBClusterLabelKey: "shoot--dev--test", - // Disabled as the load balancer API is currently not accepting `/` in the label - // TODO: enable this as soon as the load balancer API supports this - // "kubernetes.io/cluster": "shoot--dev--test", - }, - "loadBalancerApiUrl": "https://custom-lb.stackit.cloud", - "customLabelDomain": "kubernetes.io", - }, []string{"*"}, ), - Entry("custom LoadBalancer and Token endpoints", + Entry("custom load balancer and token endpoint", &stackitv1alpha1.APIEndpoints{ LoadBalancer: new("https://custom-lb.stackit.cloud"), TokenEndpoint: new("https://custom-auth.stackit.cloud/token"), }, nil, - map[string]any{ - "stackitProjectID": string(cpSecret.Data[stackit.ProjectID]), - "stackitNetworkID": "network-acbd1234", - "stackitRegion": "eu01", - "extraLabels": map[string]string{ - STACKITLBClusterLabelKey: "shoot--dev--test", - // Disabled as the load balancer API is currently not accepting `/` in the label - // TODO: enable this as soon as the load balancer API supports this - // "kubernetes.io/cluster": "shoot--dev--test", - }, - "loadBalancerApiUrl": "https://custom-lb.stackit.cloud", - "tokenUrl": "https://custom-auth.stackit.cloud/token", - "customLabelDomain": "kubernetes.io", - }, []string{"*"}, ), - Entry("Cloudprofile has OpenStack CCM configured", + Entry("OpenStack CCM still uses service-lb-only controllers", nil, &stackitv1alpha1.ControlPlaneConfig{ CloudControllerManager: &stackitv1alpha1.CloudControllerManagerConfig{ Name: string(stackitv1alpha1.OPENSTACK), }, }, - map[string]any{ - "stackitProjectID": string(cpSecret.Data[stackit.ProjectID]), - "stackitNetworkID": "network-acbd1234", - "stackitRegion": "eu01", - "extraLabels": map[string]string{ - STACKITLBClusterLabelKey: "shoot--dev--test", - // Disabled as the load balancer API is currently not accepting `/` in the label - // TODO: enable this as soon as the load balancer API supports this - // "kubernetes.io/cluster": "shoot--dev--test", - }, - "customLabelDomain": "kubernetes.io", - }, []string{STACKITCCMServiceLoadbalancerController}, ), ) - DescribeTable("customLabelDomain propagation to helm charts", - func(customDomain string, expectedClusterLabelKey string) { - c.EXPECT().Get(ctx, cpCSIDiskConfigKey, &corev1.Secret{}).DoAndReturn(clientGet(cpCSIDiskConfig)) - c.EXPECT().Get(ctx, cpSecretKey, &corev1.Secret{}).DoAndReturn(clientGet(cpSecret)).Times(2) - - testCluster := *cluster - - cloudProfile := &gardencorev1beta1.CloudProfile{} - if cluster.CloudProfile != nil { - *cloudProfile = *cluster.CloudProfile - } - testCluster.CloudProfile = cloudProfile + DescribeTable("propagates custom label domains", + func(customLabelDomain string) { + vp = newTestValuesProvider(c, scheme, true, customLabelDomain) + cp, cluster, providerSecret, _, _ := seedReadyControlPlane(ctx, c) - vpCustomDomain := NewValuesProvider(mgr, true, customDomain) - values, err := vpCustomDomain.GetControlPlaneChartValues(ctx, cp, &testCluster, fakeSecretsManager, checksums, false) + values, err := vp.GetControlPlaneChartValues(ctx, cp, cluster, secretsManager, checksumsFor(providerSecret), false) Expect(err).NotTo(HaveOccurred()) - // Verify STACKIT CCM has customLabelDomain in config - stackitCCMValues, ok := values[openstack.STACKITCloudControllerManagerName].(map[string]any) - Expect(ok).To(BeTrue(), "STACKIT CCM values should be a map") - Expect(stackitCCMValues).To(HaveKey("config")) - - ccmConfig, ok := stackitCCMValues["config"].(map[string]any) - Expect(ok).To(BeTrue(), "STACKIT CCM config should be a map") - Expect(ccmConfig).To(HaveKeyWithValue("customLabelDomain", customDomain)) - - // Verify extraLabels uses the customLabelDomain for cluster label key - // Disabled as the load balancer API is currently not accepting `/` in the label - // TODO: enable this as soon as the load balancer API supports this - // extraLabels, ok := ccmConfig["extraLabels"].(map[string]string) - // Expect(ok).To(BeTrue(), "extraLabels should be a string map") - // Expect(extraLabels).To(HaveKey(expectedClusterLabelKey)) - // Expect(extraLabels[expectedClusterLabelKey]).To(Equal("shoot--dev--test")) - - // Verify STACKIT CSI Controller has customLabelDomain - stackitCSIValues, ok := values[openstack.CSISTACKITControllerName].(map[string]any) - Expect(ok).To(BeTrue(), "STACKIT CSI values should be a map") - Expect(stackitCSIValues).To(HaveKeyWithValue("customLabelDomain", customDomain)) - - // Verify OpenStack CSI Controller does NOT have customLabelDomain - // (it should be disabled when STACKIT CSI is active, but if it were enabled, it shouldn't have customLabelDomain) - openstackCSIValues, ok := values[openstack.CSIControllerName].(map[string]any) - Expect(ok).To(BeTrue(), "OpenStack CSI values should be a map") - Expect(openstackCSIValues).NotTo(HaveKey("customLabelDomain"), "OpenStack CSI should not have customLabelDomain") + stackitCCMConfig := chartValues(values, openstack.STACKITCloudControllerManagerName)["config"].(map[string]any) + Expect(stackitCCMConfig).To(HaveKeyWithValue("customLabelDomain", customLabelDomain)) + Expect(chartValues(values, openstack.CSISTACKITControllerName)).To(HaveKeyWithValue("customLabelDomain", customLabelDomain)) + Expect(chartValues(values, openstack.CSIControllerName)).NotTo(HaveKey("customLabelDomain")) }, - Entry("default kubernetes.io domain", - "kubernetes.io", - "kubernetes.io/cluster", - ), - Entry("custom ske.stackit.cloud domain", - "ske.stackit.cloud", - "ske.stackit.cloud/cluster", - ), - Entry("custom example.com domain", - "example.com", - "example.com/cluster", - ), + Entry("default kubernetes.io domain", "kubernetes.io"), + Entry("custom ske.stackit.cloud domain", "ske.stackit.cloud"), + Entry("custom example.com domain", "example.com"), ) - DescribeTable("topologyAwareRoutingEnabled value", + DescribeTable("supports topology-aware-routing compatibility combinations", func(seedSettings *gardencorev1beta1.SeedSettings, shootControlPlane *gardencorev1beta1.ControlPlane) { - c.EXPECT().Get(ctx, cpCSIDiskConfigKey, &corev1.Secret{}).DoAndReturn(clientGet(cpCSIDiskConfig)) - c.EXPECT().Get(ctx, cpSecretKey, &corev1.Secret{}).DoAndReturn(clientGet(cpSecret)).Times(2) - + cp, cluster, providerSecret, _, _ := seedReadyControlPlane(ctx, c) cluster.Seed = &gardencorev1beta1.Seed{ Spec: gardencorev1beta1.SeedSpec{ Settings: seedSettings, @@ -835,310 +748,317 @@ var _ = Describe("ValuesProvider", func() { } cluster.Shoot.Spec.ControlPlane = shootControlPlane - controlPlaneConfig := defaultControlPlaneConfig() - cp.Spec.ProviderConfig.Raw = encode(controlPlaneConfig) - - values, err := vp.GetControlPlaneChartValues(ctx, cp, cluster, fakeSecretsManager, checksums, false) + values, err := vp.GetControlPlaneChartValues(ctx, cp, cluster, secretsManager, checksumsFor(providerSecret), false) Expect(err).NotTo(HaveOccurred()) - Expect(values).To(HaveKey(openstack.CSIControllerName)) + Expect(values).To(HaveKey(openstack.CSISTACKITControllerName)) }, - - Entry("seed setting is nil, shoot control plane is not HA", + Entry("seed settings nil and shoot control plane not HA", nil, &gardencorev1beta1.ControlPlane{HighAvailability: nil}, ), - Entry("seed setting is disabled, shoot control plane is not HA", + Entry("seed setting disabled and shoot control plane not HA", &gardencorev1beta1.SeedSettings{TopologyAwareRouting: &gardencorev1beta1.SeedSettingTopologyAwareRouting{Enabled: false}}, &gardencorev1beta1.ControlPlane{HighAvailability: nil}, ), - Entry("seed setting is enabled, shoot control plane is not HA", + Entry("seed setting enabled and shoot control plane not HA", &gardencorev1beta1.SeedSettings{TopologyAwareRouting: &gardencorev1beta1.SeedSettingTopologyAwareRouting{Enabled: true}}, &gardencorev1beta1.ControlPlane{HighAvailability: nil}, ), - Entry("seed setting is nil, shoot control plane is HA with failure tolerance type 'zone'", + Entry("seed settings nil and zonal HA shoot control plane", nil, - &gardencorev1beta1.ControlPlane{HighAvailability: &gardencorev1beta1.HighAvailability{FailureTolerance: gardencorev1beta1.FailureTolerance{Type: gardencorev1beta1.FailureToleranceTypeZone}}}, + &gardencorev1beta1.ControlPlane{ + HighAvailability: &gardencorev1beta1.HighAvailability{ + FailureTolerance: gardencorev1beta1.FailureTolerance{Type: gardencorev1beta1.FailureToleranceTypeZone}, + }, + }, ), - Entry("seed setting is disabled, shoot control plane is HA with failure tolerance type 'zone'", + Entry("seed setting disabled and zonal HA shoot control plane", &gardencorev1beta1.SeedSettings{TopologyAwareRouting: &gardencorev1beta1.SeedSettingTopologyAwareRouting{Enabled: false}}, - &gardencorev1beta1.ControlPlane{HighAvailability: &gardencorev1beta1.HighAvailability{FailureTolerance: gardencorev1beta1.FailureTolerance{Type: gardencorev1beta1.FailureToleranceTypeZone}}}, + &gardencorev1beta1.ControlPlane{ + HighAvailability: &gardencorev1beta1.HighAvailability{ + FailureTolerance: gardencorev1beta1.FailureTolerance{Type: gardencorev1beta1.FailureToleranceTypeZone}, + }, + }, ), - Entry("seed setting is enabled, shoot control plane is HA with failure tolerance type 'zone'", + Entry("seed setting enabled and zonal HA shoot control plane", &gardencorev1beta1.SeedSettings{TopologyAwareRouting: &gardencorev1beta1.SeedSettingTopologyAwareRouting{Enabled: true}}, - &gardencorev1beta1.ControlPlane{HighAvailability: &gardencorev1beta1.HighAvailability{FailureTolerance: gardencorev1beta1.FailureTolerance{Type: gardencorev1beta1.FailureToleranceTypeZone}}}, + &gardencorev1beta1.ControlPlane{ + HighAvailability: &gardencorev1beta1.HighAvailability{ + FailureTolerance: gardencorev1beta1.FailureTolerance{Type: gardencorev1beta1.FailureToleranceTypeZone}, + }, + }, ), ) - It("should return correct control plane ALB chart values if loadbalancerProvider is stackit", func() { - c.EXPECT().Get(ctx, cpCSIDiskConfigKey, &corev1.Secret{}).DoAndReturn(clientGet(cpCSIDiskConfig)) - c.EXPECT().Get(ctx, cpSecretKey, &corev1.Secret{}).DoAndReturn(clientGet(cpSecret)).Times(2) + It("returns ALB controller values when enabled", func() { + cp, cluster, providerSecret, _, _ := seedReadyControlPlane(ctx, c) + cpConfig := baseControlPlaneConfig() + cpConfig.ApplicationLoadBalancer = &stackitv1alpha1.ApplicationLoadBalancerConfig{Enabled: true} + cp.Spec.ProviderConfig.Raw = encode(cpConfig) - controlPlaneConfig := defaultControlPlaneConfig() - controlPlaneConfig.ApplicationLoadBalancer = &stackitv1alpha1.ApplicationLoadBalancerConfig{ - Enabled: true, - } - cp.Spec.ProviderConfig.Raw = encode(controlPlaneConfig) + values, err := vp.GetControlPlaneChartValues(ctx, cp, cluster, secretsManager, checksumsFor(providerSecret), false) + Expect(err).NotTo(HaveOccurred()) + Expect(chartValues(values, openstack.STACKITALBControllerManagerName)).To(BeComparableTo(map[string]any{ + "enabled": true, + "replicas": 1, + "config": map[string]any{ + "region": "eu01", + "stackitProjectID": "foo", + "stackitNetworkID": "network-acbd1234", + }, + })) + }) - values, err := vp.GetControlPlaneChartValues(ctx, cp, cluster, fakeSecretsManager, checksums, false) + It("omits ALB controller values when the harness disables ALB deployment", func() { + vp = newTestValuesProvider(c, scheme, false, "kubernetes.io") + cp, cluster, providerSecret, _, _ := seedReadyControlPlane(ctx, c) + cpConfig := baseControlPlaneConfig() + cpConfig.ApplicationLoadBalancer = &stackitv1alpha1.ApplicationLoadBalancerConfig{Enabled: true} + cp.Spec.ProviderConfig.Raw = encode(cpConfig) + + values, err := vp.GetControlPlaneChartValues(ctx, cp, cluster, secretsManager, checksumsFor(providerSecret), false) Expect(err).NotTo(HaveOccurred()) - Expect(values[openstack.STACKITALBControllerManagerName]).To(Equal(stackitAlbChartValues)) + Expect(values[openstack.STACKITALBControllerManagerName]).To(BeNil()) + }) + + It("deletes legacy cleanup objects from the fake client", func() { + cp, cluster, providerSecret, _, _ := seedReadyControlPlane(ctx, c) + legacyObjects := legacyCleanupObjects() + createObjects(ctx, c, legacyObjects...) + + _, err := vp.GetControlPlaneChartValues(ctx, cp, cluster, secretsManager, checksumsFor(providerSecret), false) + Expect(err).NotTo(HaveOccurred()) + expectObjectsDeleted(ctx, c, legacyObjects...) }) }) Describe("#GetControlPlaneShootChartValues", func() { - stackitPodIdentityWebhookChartShootValues := map[string]any{ - "enabled": false, - "webhook": map[string]any{ - "caBundle": []byte("fake-ca-cert"), - "url": fmt.Sprintf("https://stackit-pod-identity-webhook.%s:443/mutate--v1-pod", namespace), - }, - } + It("returns OpenStack shoot chart values and deletes unused STACKIT CSI control-plane objects", func() { + cp, cluster := seedReadyShoot(ctx, c) + cpConfig := baseControlPlaneConfig() + cpConfig.Storage.CSI.Name = string(stackitv1alpha1.OPENSTACK) + cp.Spec.ProviderConfig.Raw = encode(cpConfig) + unusedObjects := seedUnusedControlPlaneCSIObjects(ctx, c, openstack.CSISTACKITControllerName) + + values, err := vp.GetControlPlaneShootChartValues(ctx, cp, cluster, secretsManager, map[string]string{}) + Expect(err).NotTo(HaveOccurred()) - BeforeEach(func() { - By("creating secrets managed outside of this package for whose secretsmanager.Get() will be called") - Expect(fakeClient.Create(context.TODO(), &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "ca-provider-openstack-controlplane", Namespace: namespace}, Data: map[string][]byte{secretutils.DataKeyCertificateBundle: []byte("fake-ca-cert")}})).To(Succeed()) - Expect(fakeClient.Create(context.TODO(), &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "cloud-controller-manager-server", Namespace: namespace}})).To(Succeed()) + Expect(chartValues(values, openstack.CloudControllerManagerName)).To(Equal(map[string]any{"enabled": true})) + Expect(chartValues(values, openstack.CSINodeName)).To(BeComparableTo(map[string]any{ + "enabled": true, + "rescanBlockStorageOnResize": true, + "nodeVolumeAttachLimit": new(int32(25)), + "userAgentHeaders": expectedUserAgentHeaders(), + })) + Expect(chartValues(values, openstack.CSISTACKITNodeName)).To(Equal(map[string]any{"enabled": false})) + Expect(chartValues(values, stackit.PodIdentityWebhookName)).To(BeComparableTo(map[string]any{ + "enabled": false, + "webhook": map[string]any{ + "caBundle": []byte("fake-ca-cert"), + "url": fmt.Sprintf("https://%s.%s:443/mutate--v1-pod", stackit.PodIdentityWebhookName, namespace), + }, + })) + expectObjectsDeleted(ctx, c, unusedObjects...) }) - Context("shoot control plane chart values", func() { - It("should return correct shoot control plane chart when ca is secret found", func() { - // Refactoring led to retrieving it three times at a lower level - // This is the vp.getCredentials() call - c.EXPECT().Get(ctx, cpSecretKey, &corev1.Secret{}).DoAndReturn(clientGet(cpSecret)).Times(2) + It("returns STACKIT CSI shoot chart values and deletes unused OpenStack CSI control-plane objects", func() { + cp, cluster := seedReadyShoot(ctx, c) + unusedObjects := seedUnusedControlPlaneCSIObjects(ctx, c, openstack.CSIControllerName) - expectCSICleanupinControlPlane(ctx, c, openstack.CSIControllerName) + values, err := vp.GetControlPlaneShootChartValues(ctx, cp, cluster, secretsManager, map[string]string{}) + Expect(err).NotTo(HaveOccurred()) - values, err := vp.GetControlPlaneShootChartValues(ctx, cp, cluster, fakeSecretsManager, map[string]string{}) - Expect(err).NotTo(HaveOccurred()) - Expect(values).To(Equal(map[string]any{ - openstack.CloudControllerManagerName: enabledTrue, - openstack.CSISTACKITNodeName: utils.MergeMaps(enabledTrue, map[string]any{ - "rescanBlockStorageOnResize": rescanBlockStorageOnResize, - "userAgentHeaders": []string{domainName, tenantName, technicalID}, - }), - openstack.CSINodeName: enabledFalse, - stackit.PodIdentityWebhookName: stackitPodIdentityWebhookChartShootValues, - })) - }) - - It("should return correct shoot control plane chart if CSI STACKIT is enabled", func() { - c.EXPECT().Get(ctx, cpSecretKey, &corev1.Secret{}).DoAndReturn(clientGet(cpSecret)).Times(2) - - expectCSICleanupinControlPlane(ctx, c, openstack.CSIControllerName) - - cpStackit := defaultControlPlaneWithSTACKIT() - values, err := vp.GetControlPlaneShootChartValues(ctx, cpStackit, cluster, fakeSecretsManager, map[string]string{}) - Expect(err).NotTo(HaveOccurred()) - Expect(values).To(Equal(map[string]any{ - openstack.CloudControllerManagerName: enabledTrue, - openstack.CSISTACKITControllerName: utils.MergeMaps(enabledTrue, map[string]any{ - "rescanBlockStorageOnResize": rescanBlockStorageOnResize, - "userAgentHeaders": []string{domainName, tenantName, technicalID}, - }), - openstack.CSINodeName: enabledFalse, - stackit.PodIdentityWebhookName: stackitPodIdentityWebhookChartShootValues, - })) - }) + Expect(chartValues(values, openstack.CloudControllerManagerName)).To(Equal(map[string]any{"enabled": true})) + Expect(chartValues(values, openstack.CSISTACKITNodeName)).To(BeComparableTo(map[string]any{ + "enabled": true, + "rescanBlockStorageOnResize": true, + "userAgentHeaders": expectedUserAgentHeaders(), + })) + Expect(chartValues(values, openstack.CSINodeName)).To(Equal(map[string]any{"enabled": false})) + Expect(chartValues(values, stackit.PodIdentityWebhookName)).To(BeComparableTo(map[string]any{ + "enabled": false, + "webhook": map[string]any{ + "caBundle": []byte("fake-ca-cert"), + "url": fmt.Sprintf("https://%s.%s:443/mutate--v1-pod", stackit.PodIdentityWebhookName, namespace), + }, + })) + expectObjectsDeleted(ctx, c, unusedObjects...) }) }) Describe("#GetStorageClassesChartValues", func() { - It("should return correct storage class chart values", func() { - values, err := vp.GetStorageClassesChartValues(ctx, cp, cluster) + It("returns the default storage classes with the OpenStack provisioner", func() { + values, err := vp.GetStorageClassesChartValues(ctx, baseControlPlane(), baseCluster()) Expect(err).NotTo(HaveOccurred()) - Expect(values["storageclasses"]).To(HaveLen(2)) - Expect(values["storageclasses"].([]map[string]any)[0]["provisioner"]).To(Equal(openstack.CSIStorageProvisioner)) - Expect(values["storageclasses"].([]map[string]any)[1]["provisioner"]).To(Equal(openstack.CSIStorageProvisioner)) + + storageClasses, ok := values["storageclasses"].([]map[string]any) + Expect(ok).To(BeTrue()) + Expect(storageClasses).To(HaveLen(2)) + Expect(storageClasses[0]).To(HaveKeyWithValue("name", "default")) + Expect(storageClasses[0]).To(HaveKeyWithValue("default", true)) + Expect(storageClasses[0]).To(HaveKeyWithValue("provisioner", openstack.CSIStorageProvisioner)) + Expect(storageClasses[1]).To(HaveKeyWithValue("name", "default-class")) + Expect(storageClasses[1]).To(HaveKeyWithValue("provisioner", openstack.CSIStorageProvisioner)) }) }) Describe("#checkEmergencyLoadBalancerAccess", func() { - secretNamespacedName := types.NamespacedName{Name: LoadBalancerEmergencyAccessSecretName, Namespace: namespace} + secretKey := client.ObjectKey{Name: LoadBalancerEmergencyAccessSecretName, Namespace: namespace} - Context("emergency access disabled", func() { - It("should not return an error if the secret does not exist", func() { - c.EXPECT().Get(ctx, secretNamespacedName, &corev1.Secret{}).Return( - errors.NewNotFound(schema.GroupResource{Resource: "secret"}, LoadBalancerEmergencyAccessSecretName)) - - apiURL, apiToken, err := vp.(*valuesProvider).checkEmergencyLoadBalancerAccess(ctx, secretNamespacedName) - Expect(apiURL).To(BeEmpty()) - Expect(apiToken).To(BeEmpty()) - Expect(err).ToNot(HaveOccurred()) - }) + It("returns empty values when the secret is missing", func() { + apiURL, apiToken, err := vp.checkEmergencyLoadBalancerAccess(ctx, secretKey) + Expect(err).NotTo(HaveOccurred()) + Expect(apiURL).To(BeEmpty()) + Expect(apiToken).To(BeEmpty()) + }) - It("should return any error except NotFound from the get call", func() { - expectedError := fmt.Errorf("something went wrong") - c.EXPECT().Get(ctx, secretNamespacedName, &corev1.Secret{}).Return(expectedError) + It("returns non-NotFound client errors from the fake interceptor", func() { + expectedErr := fmt.Errorf("something went wrong") + interceptedClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithInterceptorFuncs(interceptor.Funcs{ + Get: func(ctx context.Context, cl client.WithWatch, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + if key == secretKey { + return expectedErr + } + return cl.Get(ctx, key, obj, opts...) + }, + }). + Build() + interceptedProvider := newTestValuesProvider(interceptedClient, scheme, true, "kubernetes.io") - apiURL, apiToken, err := vp.(*valuesProvider).checkEmergencyLoadBalancerAccess(ctx, secretNamespacedName) - Expect(apiURL).To(BeEmpty()) - Expect(apiToken).To(BeEmpty()) - Expect(err).To(Equal(expectedError)) - }) + apiURL, apiToken, err := interceptedProvider.checkEmergencyLoadBalancerAccess(ctx, secretKey) + Expect(err).To(MatchError(expectedErr)) + Expect(apiURL).To(BeEmpty()) + Expect(apiToken).To(BeEmpty()) }) - Context("emergency access enabled", func() { - It("should return non-empty apiUrl and apiToken", func() { - emergencySecret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: LoadBalancerEmergencyAccessSecretName, - Namespace: namespace, - }, - Data: map[string][]byte{ - LoadBalancerEmergencyAccessAPIURLKey: []byte("foo"), - LoadBalancerEmergencyAccessAPITokenKey: []byte("bar"), - }, - } - c.EXPECT().Get(ctx, secretNamespacedName, &corev1.Secret{}).DoAndReturn(clientGet(emergencySecret)) + It("returns decoded emergency access values for a valid secret", func() { + createObjects(ctx, c, emergencyLBSecret()) - apiURL, apiToken, err := vp.(*valuesProvider).checkEmergencyLoadBalancerAccess(ctx, secretNamespacedName) - Expect(err).ToNot(HaveOccurred()) - Expect(apiURL).To(Equal("foo")) - Expect(apiToken).To(Equal("bar")) - }) + apiURL, apiToken, err := vp.checkEmergencyLoadBalancerAccess(ctx, secretKey) + Expect(err).NotTo(HaveOccurred()) + Expect(apiURL).To(Equal("foo")) + Expect(apiToken).To(Equal("bar")) }) }) - DescribeTable("#decodeLoadBalancerAPIEmergencySecret", func(url, token *string, errExpected error) { - secret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: LoadBalancerEmergencyAccessSecretName, - Namespace: namespace, - }, - Data: map[string][]byte{}, - } + DescribeTable("#decodeLoadBalancerAPIEmergencySecret", + func(url, token *string, expectedErr error) { + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: LoadBalancerEmergencyAccessSecretName, + Namespace: namespace, + }, + Data: map[string][]byte{}, + } - if url != nil { - secret.Data[LoadBalancerEmergencyAccessAPIURLKey] = []byte(*url) - } - if token != nil { - secret.Data[LoadBalancerEmergencyAccessAPITokenKey] = []byte(*token) - } + if url != nil { + secret.Data[LoadBalancerEmergencyAccessAPIURLKey] = []byte(*url) + } + if token != nil { + secret.Data[LoadBalancerEmergencyAccessAPITokenKey] = []byte(*token) + } - apiURL, apiToken, err := decodeLoadBalancerAPIEmergencySecret(secret) + apiURL, apiToken, err := decodeLoadBalancerAPIEmergencySecret(secret) + if expectedErr != nil { + Expect(err).To(MatchError(expectedErr)) + Expect(apiURL).To(BeEmpty()) + Expect(apiToken).To(BeEmpty()) + return + } - if errExpected != nil { - Expect(err).To(HaveOccurred()) - Expect(err).To(Equal(errExpected)) - Expect(apiURL).To(BeEmpty()) - Expect(apiToken).To(BeEmpty()) - } else { - Expect(err).ToNot(HaveOccurred()) + Expect(err).NotTo(HaveOccurred()) Expect(apiURL).To(Equal(*url)) Expect(apiToken).To(Equal(*token)) - } - }, - - Entry("missing url", nil, new("token"), fmt.Errorf("missing or empty secret key %s", LoadBalancerEmergencyAccessAPIURLKey)), - Entry("empty url", new(""), new("token"), fmt.Errorf("missing or empty secret key %s", LoadBalancerEmergencyAccessAPIURLKey)), + }, + Entry("missing URL", nil, new("token"), fmt.Errorf("missing or empty secret key %s", LoadBalancerEmergencyAccessAPIURLKey)), + Entry("empty URL", new(""), new("token"), fmt.Errorf("missing or empty secret key %s", LoadBalancerEmergencyAccessAPIURLKey)), Entry("missing token", new("url"), nil, fmt.Errorf("missing or empty secret key %s", LoadBalancerEmergencyAccessAPITokenKey)), Entry("empty token", new("url"), new(""), fmt.Errorf("missing or empty secret key %s", LoadBalancerEmergencyAccessAPITokenKey)), Entry("valid secret", new("url"), new("token"), nil), ) - Describe("#shouldEnablePodIdentityWebhook", func() { - It("should return false when feature gate is disabled", func() { - defer testutils.WithFeatureGate(feature.MutableGate, feature.EnableSTACKITWorkloadIdentity, false)() - Expect(shouldEnablePodIdentityWebhook(cluster)).To(BeFalse()) - }) + DescribeTable("#shouldEnablePodIdentityWebhook", + func(featureGateEnabled bool, configureCluster func(*extensionscontroller.Cluster), expected bool) { + DeferCleanup(testutils.WithFeatureGate(feature.MutableGate, feature.EnableSTACKITWorkloadIdentity, featureGateEnabled)) - Context("feature gate enabled", func() { - It("should return false when shoot annotations are nil and no custom issuer is set", func() { - defer testutils.WithFeatureGate(feature.MutableGate, feature.EnableSTACKITWorkloadIdentity, true)() + cluster := baseCluster() + configureCluster(cluster) + Expect(shouldEnablePodIdentityWebhook(cluster)).To(Equal(expected)) + }, + Entry("returns false when the feature gate is disabled", + false, + func(cluster *extensionscontroller.Cluster) {}, + false, + ), + Entry("returns false without annotations and without a custom issuer", + true, + func(cluster *extensionscontroller.Cluster) { cluster.Shoot.Annotations = nil cluster.Shoot.Spec.Kubernetes.KubeAPIServer = nil - Expect(shouldEnablePodIdentityWebhook(cluster)).To(BeFalse()) - }) - - It("should return true when shoot annotations are nil but custom issuer is set", func() { - defer testutils.WithFeatureGate(feature.MutableGate, feature.EnableSTACKITWorkloadIdentity, true)() + }, + false, + ), + Entry("returns true without annotations when a custom issuer is set", + true, + func(cluster *extensionscontroller.Cluster) { cluster.Shoot.Annotations = nil cluster.Shoot.Spec.Kubernetes.KubeAPIServer = &gardencorev1beta1.KubeAPIServerConfig{ ServiceAccountConfig: &gardencorev1beta1.ServiceAccountConfig{ Issuer: new("foo"), }, } - Expect(shouldEnablePodIdentityWebhook(cluster)).To(BeTrue()) - }) - - It("should return false when issuer annotation is missing and no custom issuer is set", func() { - defer testutils.WithFeatureGate(feature.MutableGate, feature.EnableSTACKITWorkloadIdentity, true)() + }, + true, + ), + Entry("returns false when the issuer annotation is missing and there is no custom issuer", + true, + func(cluster *extensionscontroller.Cluster) { cluster.Shoot.Annotations = map[string]string{} cluster.Shoot.Spec.Kubernetes.KubeAPIServer = nil - Expect(shouldEnablePodIdentityWebhook(cluster)).To(BeFalse()) - }) - - It("should return false when issuer annotation is not managed", func() { - defer testutils.WithFeatureGate(feature.MutableGate, feature.EnableSTACKITWorkloadIdentity, true)() + }, + false, + ), + Entry("returns false when the issuer annotation is not managed", + true, + func(cluster *extensionscontroller.Cluster) { cluster.Shoot.Annotations = map[string]string{ v1beta1constants.AnnotationAuthenticationIssuer: "foo", } - Expect(shouldEnablePodIdentityWebhook(cluster)).To(BeFalse()) - }) - - It("should return true when issuer annotation is managed", func() { - defer testutils.WithFeatureGate(feature.MutableGate, feature.EnableSTACKITWorkloadIdentity, true)() + }, + false, + ), + Entry("returns true when the issuer annotation is managed", + true, + func(cluster *extensionscontroller.Cluster) { cluster.Shoot.Annotations = map[string]string{ v1beta1constants.AnnotationAuthenticationIssuer: v1beta1constants.AnnotationAuthenticationIssuerManaged, } - Expect(shouldEnablePodIdentityWebhook(cluster)).To(BeTrue()) - }) - - It("should return false when KubeAPIServer ServiceAccountConfig Issuer is explicitly nil", func() { - defer testutils.WithFeatureGate(feature.MutableGate, feature.EnableSTACKITWorkloadIdentity, true)() + }, + true, + ), + Entry("returns false when the service account issuer is explicitly nil", + true, + func(cluster *extensionscontroller.Cluster) { cluster.Shoot.Annotations = map[string]string{} cluster.Shoot.Spec.Kubernetes.KubeAPIServer = &gardencorev1beta1.KubeAPIServerConfig{ - ServiceAccountConfig: &gardencorev1beta1.ServiceAccountConfig{ - Issuer: nil, - }, + ServiceAccountConfig: &gardencorev1beta1.ServiceAccountConfig{}, } - Expect(shouldEnablePodIdentityWebhook(cluster)).To(BeFalse()) - }) - - It("should return true when KubeAPIServer ServiceAccountConfig Issuer is set", func() { - defer testutils.WithFeatureGate(feature.MutableGate, feature.EnableSTACKITWorkloadIdentity, true)() + }, + false, + ), + Entry("returns true when the service account issuer is set", + true, + func(cluster *extensionscontroller.Cluster) { cluster.Shoot.Annotations = map[string]string{} cluster.Shoot.Spec.Kubernetes.KubeAPIServer = &gardencorev1beta1.KubeAPIServerConfig{ ServiceAccountConfig: &gardencorev1beta1.ServiceAccountConfig{ Issuer: new("foo"), }, } - Expect(shouldEnablePodIdentityWebhook(cluster)).To(BeTrue()) - }) - }) - }) + }, + true, + ), + ) }) - -func expectCSICleanupinControlPlane(ctx context.Context, c *mockclient.MockClient, subChartName string) { - for _, subchart := range controlPlaneChart.SubCharts { - if subchart.Name == subChartName { - for _, obj := range subchart.Objects { - objToDelete := obj.Type - objToDelete.SetNamespace(namespace) - objToDelete.SetName(obj.Name) - - c.EXPECT().Delete(ctx, objToDelete) - } - } - } -} - -func encode(obj runtime.Object) []byte { - data, _ := json.Marshal(obj) - return data -} - -func clientGet(result runtime.Object) any { - return func(ctx context.Context, key client.ObjectKey, obj runtime.Object, _ ...client.GetOption) error { - if _, ok := obj.(*corev1.Secret); ok { - *obj.(*corev1.Secret) = *result.(*corev1.Secret) - } - return nil - } -} - -func stackitCCMDeletion(ctx context.Context, c *mockclient.MockClient) { - c.EXPECT().Delete(ctx, &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: openstack.STACKITCloudControllerManagerName, Namespace: namespace}}) - c.EXPECT().Delete(ctx, &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: openstack.STACKITCloudControllerManagerName, Namespace: namespace}}) - c.EXPECT().Delete(ctx, &vpaautoscalingv1.VerticalPodAutoscaler{ObjectMeta: metav1.ObjectMeta{Name: openstack.STACKITCloudControllerManagerName + "-vpa", Namespace: namespace}}) -} diff --git a/pkg/mock/controller-runtime/client/mocks.go b/pkg/mock/controller-runtime/client/mocks.go deleted file mode 100644 index b06636bd..00000000 --- a/pkg/mock/controller-runtime/client/mocks.go +++ /dev/null @@ -1,702 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: sigs.k8s.io/controller-runtime/pkg/client (interfaces: Client,StatusWriter,Reader,Writer,SubResourceClient) -// -// Generated by this command: -// -// mockgen -destination ./pkg/mock/controller-runtime/client/mocks.go -package client sigs.k8s.io/controller-runtime/pkg/client Client,StatusWriter,Reader,Writer,SubResourceClient -// - -// Package client is a generated GoMock package. -package client - -import ( - context "context" - reflect "reflect" - - gomock "go.uber.org/mock/gomock" - meta "k8s.io/apimachinery/pkg/api/meta" - runtime "k8s.io/apimachinery/pkg/runtime" - schema "k8s.io/apimachinery/pkg/runtime/schema" - client "sigs.k8s.io/controller-runtime/pkg/client" -) - -// MockClient is a mock of Client interface. -type MockClient struct { - ctrl *gomock.Controller - recorder *MockClientMockRecorder - isgomock struct{} -} - -// MockClientMockRecorder is the mock recorder for MockClient. -type MockClientMockRecorder struct { - mock *MockClient -} - -// NewMockClient creates a new mock instance. -func NewMockClient(ctrl *gomock.Controller) *MockClient { - mock := &MockClient{ctrl: ctrl} - mock.recorder = &MockClientMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockClient) EXPECT() *MockClientMockRecorder { - return m.recorder -} - -// Apply mocks base method. -func (m *MockClient) Apply(ctx context.Context, obj runtime.ApplyConfiguration, opts ...client.ApplyOption) error { - m.ctrl.T.Helper() - varargs := []any{ctx, obj} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "Apply", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// Apply indicates an expected call of Apply. -func (mr *MockClientMockRecorder) Apply(ctx, obj any, opts ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{ctx, obj}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Apply", reflect.TypeOf((*MockClient)(nil).Apply), varargs...) -} - -// Create mocks base method. -func (m *MockClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { - m.ctrl.T.Helper() - varargs := []any{ctx, obj} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "Create", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// Create indicates an expected call of Create. -func (mr *MockClientMockRecorder) Create(ctx, obj any, opts ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{ctx, obj}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockClient)(nil).Create), varargs...) -} - -// Delete mocks base method. -func (m *MockClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { - m.ctrl.T.Helper() - varargs := []any{ctx, obj} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "Delete", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// Delete indicates an expected call of Delete. -func (mr *MockClientMockRecorder) Delete(ctx, obj any, opts ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{ctx, obj}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockClient)(nil).Delete), varargs...) -} - -// DeleteAllOf mocks base method. -func (m *MockClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error { - m.ctrl.T.Helper() - varargs := []any{ctx, obj} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "DeleteAllOf", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// DeleteAllOf indicates an expected call of DeleteAllOf. -func (mr *MockClientMockRecorder) DeleteAllOf(ctx, obj any, opts ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{ctx, obj}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllOf", reflect.TypeOf((*MockClient)(nil).DeleteAllOf), varargs...) -} - -// Get mocks base method. -func (m *MockClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { - m.ctrl.T.Helper() - varargs := []any{ctx, key, obj} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "Get", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// Get indicates an expected call of Get. -func (mr *MockClientMockRecorder) Get(ctx, key, obj any, opts ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{ctx, key, obj}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockClient)(nil).Get), varargs...) -} - -// GroupVersionKindFor mocks base method. -func (m *MockClient) GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GroupVersionKindFor", obj) - ret0, _ := ret[0].(schema.GroupVersionKind) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GroupVersionKindFor indicates an expected call of GroupVersionKindFor. -func (mr *MockClientMockRecorder) GroupVersionKindFor(obj any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GroupVersionKindFor", reflect.TypeOf((*MockClient)(nil).GroupVersionKindFor), obj) -} - -// IsObjectNamespaced mocks base method. -func (m *MockClient) IsObjectNamespaced(obj runtime.Object) (bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IsObjectNamespaced", obj) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// IsObjectNamespaced indicates an expected call of IsObjectNamespaced. -func (mr *MockClientMockRecorder) IsObjectNamespaced(obj any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsObjectNamespaced", reflect.TypeOf((*MockClient)(nil).IsObjectNamespaced), obj) -} - -// List mocks base method. -func (m *MockClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { - m.ctrl.T.Helper() - varargs := []any{ctx, list} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "List", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// List indicates an expected call of List. -func (mr *MockClientMockRecorder) List(ctx, list any, opts ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{ctx, list}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockClient)(nil).List), varargs...) -} - -// Patch mocks base method. -func (m *MockClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { - m.ctrl.T.Helper() - varargs := []any{ctx, obj, patch} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "Patch", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// Patch indicates an expected call of Patch. -func (mr *MockClientMockRecorder) Patch(ctx, obj, patch any, opts ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{ctx, obj, patch}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Patch", reflect.TypeOf((*MockClient)(nil).Patch), varargs...) -} - -// RESTMapper mocks base method. -func (m *MockClient) RESTMapper() meta.RESTMapper { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "RESTMapper") - ret0, _ := ret[0].(meta.RESTMapper) - return ret0 -} - -// RESTMapper indicates an expected call of RESTMapper. -func (mr *MockClientMockRecorder) RESTMapper() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RESTMapper", reflect.TypeOf((*MockClient)(nil).RESTMapper)) -} - -// Scheme mocks base method. -func (m *MockClient) Scheme() *runtime.Scheme { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Scheme") - ret0, _ := ret[0].(*runtime.Scheme) - return ret0 -} - -// Scheme indicates an expected call of Scheme. -func (mr *MockClientMockRecorder) Scheme() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Scheme", reflect.TypeOf((*MockClient)(nil).Scheme)) -} - -// Status mocks base method. -func (m *MockClient) Status() client.SubResourceWriter { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Status") - ret0, _ := ret[0].(client.SubResourceWriter) - return ret0 -} - -// Status indicates an expected call of Status. -func (mr *MockClientMockRecorder) Status() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockClient)(nil).Status)) -} - -// SubResource mocks base method. -func (m *MockClient) SubResource(subResource string) client.SubResourceClient { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SubResource", subResource) - ret0, _ := ret[0].(client.SubResourceClient) - return ret0 -} - -// SubResource indicates an expected call of SubResource. -func (mr *MockClientMockRecorder) SubResource(subResource any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubResource", reflect.TypeOf((*MockClient)(nil).SubResource), subResource) -} - -// Update mocks base method. -func (m *MockClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - m.ctrl.T.Helper() - varargs := []any{ctx, obj} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "Update", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// Update indicates an expected call of Update. -func (mr *MockClientMockRecorder) Update(ctx, obj any, opts ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{ctx, obj}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockClient)(nil).Update), varargs...) -} - -// MockStatusWriter is a mock of StatusWriter interface. -type MockStatusWriter struct { - ctrl *gomock.Controller - recorder *MockStatusWriterMockRecorder - isgomock struct{} -} - -// MockStatusWriterMockRecorder is the mock recorder for MockStatusWriter. -type MockStatusWriterMockRecorder struct { - mock *MockStatusWriter -} - -// NewMockStatusWriter creates a new mock instance. -func NewMockStatusWriter(ctrl *gomock.Controller) *MockStatusWriter { - mock := &MockStatusWriter{ctrl: ctrl} - mock.recorder = &MockStatusWriterMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockStatusWriter) EXPECT() *MockStatusWriterMockRecorder { - return m.recorder -} - -// Apply mocks base method. -func (m *MockStatusWriter) Apply(ctx context.Context, obj runtime.ApplyConfiguration, opts ...client.SubResourceApplyOption) error { - m.ctrl.T.Helper() - varargs := []any{ctx, obj} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "Apply", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// Apply indicates an expected call of Apply. -func (mr *MockStatusWriterMockRecorder) Apply(ctx, obj any, opts ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{ctx, obj}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Apply", reflect.TypeOf((*MockStatusWriter)(nil).Apply), varargs...) -} - -// Create mocks base method. -func (m *MockStatusWriter) Create(ctx context.Context, obj, subResource client.Object, opts ...client.SubResourceCreateOption) error { - m.ctrl.T.Helper() - varargs := []any{ctx, obj, subResource} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "Create", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// Create indicates an expected call of Create. -func (mr *MockStatusWriterMockRecorder) Create(ctx, obj, subResource any, opts ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{ctx, obj, subResource}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockStatusWriter)(nil).Create), varargs...) -} - -// Patch mocks base method. -func (m *MockStatusWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) error { - m.ctrl.T.Helper() - varargs := []any{ctx, obj, patch} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "Patch", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// Patch indicates an expected call of Patch. -func (mr *MockStatusWriterMockRecorder) Patch(ctx, obj, patch any, opts ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{ctx, obj, patch}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Patch", reflect.TypeOf((*MockStatusWriter)(nil).Patch), varargs...) -} - -// Update mocks base method. -func (m *MockStatusWriter) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { - m.ctrl.T.Helper() - varargs := []any{ctx, obj} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "Update", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// Update indicates an expected call of Update. -func (mr *MockStatusWriterMockRecorder) Update(ctx, obj any, opts ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{ctx, obj}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockStatusWriter)(nil).Update), varargs...) -} - -// MockReader is a mock of Reader interface. -type MockReader struct { - ctrl *gomock.Controller - recorder *MockReaderMockRecorder - isgomock struct{} -} - -// MockReaderMockRecorder is the mock recorder for MockReader. -type MockReaderMockRecorder struct { - mock *MockReader -} - -// NewMockReader creates a new mock instance. -func NewMockReader(ctrl *gomock.Controller) *MockReader { - mock := &MockReader{ctrl: ctrl} - mock.recorder = &MockReaderMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockReader) EXPECT() *MockReaderMockRecorder { - return m.recorder -} - -// Get mocks base method. -func (m *MockReader) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { - m.ctrl.T.Helper() - varargs := []any{ctx, key, obj} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "Get", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// Get indicates an expected call of Get. -func (mr *MockReaderMockRecorder) Get(ctx, key, obj any, opts ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{ctx, key, obj}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockReader)(nil).Get), varargs...) -} - -// List mocks base method. -func (m *MockReader) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { - m.ctrl.T.Helper() - varargs := []any{ctx, list} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "List", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// List indicates an expected call of List. -func (mr *MockReaderMockRecorder) List(ctx, list any, opts ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{ctx, list}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockReader)(nil).List), varargs...) -} - -// MockWriter is a mock of Writer interface. -type MockWriter struct { - ctrl *gomock.Controller - recorder *MockWriterMockRecorder - isgomock struct{} -} - -// MockWriterMockRecorder is the mock recorder for MockWriter. -type MockWriterMockRecorder struct { - mock *MockWriter -} - -// NewMockWriter creates a new mock instance. -func NewMockWriter(ctrl *gomock.Controller) *MockWriter { - mock := &MockWriter{ctrl: ctrl} - mock.recorder = &MockWriterMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockWriter) EXPECT() *MockWriterMockRecorder { - return m.recorder -} - -// Apply mocks base method. -func (m *MockWriter) Apply(ctx context.Context, obj runtime.ApplyConfiguration, opts ...client.ApplyOption) error { - m.ctrl.T.Helper() - varargs := []any{ctx, obj} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "Apply", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// Apply indicates an expected call of Apply. -func (mr *MockWriterMockRecorder) Apply(ctx, obj any, opts ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{ctx, obj}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Apply", reflect.TypeOf((*MockWriter)(nil).Apply), varargs...) -} - -// Create mocks base method. -func (m *MockWriter) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { - m.ctrl.T.Helper() - varargs := []any{ctx, obj} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "Create", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// Create indicates an expected call of Create. -func (mr *MockWriterMockRecorder) Create(ctx, obj any, opts ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{ctx, obj}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockWriter)(nil).Create), varargs...) -} - -// Delete mocks base method. -func (m *MockWriter) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { - m.ctrl.T.Helper() - varargs := []any{ctx, obj} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "Delete", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// Delete indicates an expected call of Delete. -func (mr *MockWriterMockRecorder) Delete(ctx, obj any, opts ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{ctx, obj}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockWriter)(nil).Delete), varargs...) -} - -// DeleteAllOf mocks base method. -func (m *MockWriter) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error { - m.ctrl.T.Helper() - varargs := []any{ctx, obj} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "DeleteAllOf", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// DeleteAllOf indicates an expected call of DeleteAllOf. -func (mr *MockWriterMockRecorder) DeleteAllOf(ctx, obj any, opts ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{ctx, obj}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllOf", reflect.TypeOf((*MockWriter)(nil).DeleteAllOf), varargs...) -} - -// Patch mocks base method. -func (m *MockWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { - m.ctrl.T.Helper() - varargs := []any{ctx, obj, patch} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "Patch", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// Patch indicates an expected call of Patch. -func (mr *MockWriterMockRecorder) Patch(ctx, obj, patch any, opts ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{ctx, obj, patch}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Patch", reflect.TypeOf((*MockWriter)(nil).Patch), varargs...) -} - -// Update mocks base method. -func (m *MockWriter) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - m.ctrl.T.Helper() - varargs := []any{ctx, obj} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "Update", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// Update indicates an expected call of Update. -func (mr *MockWriterMockRecorder) Update(ctx, obj any, opts ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{ctx, obj}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockWriter)(nil).Update), varargs...) -} - -// MockSubResourceClient is a mock of SubResourceClient interface. -type MockSubResourceClient struct { - ctrl *gomock.Controller - recorder *MockSubResourceClientMockRecorder - isgomock struct{} -} - -// MockSubResourceClientMockRecorder is the mock recorder for MockSubResourceClient. -type MockSubResourceClientMockRecorder struct { - mock *MockSubResourceClient -} - -// NewMockSubResourceClient creates a new mock instance. -func NewMockSubResourceClient(ctrl *gomock.Controller) *MockSubResourceClient { - mock := &MockSubResourceClient{ctrl: ctrl} - mock.recorder = &MockSubResourceClientMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockSubResourceClient) EXPECT() *MockSubResourceClientMockRecorder { - return m.recorder -} - -// Apply mocks base method. -func (m *MockSubResourceClient) Apply(ctx context.Context, obj runtime.ApplyConfiguration, opts ...client.SubResourceApplyOption) error { - m.ctrl.T.Helper() - varargs := []any{ctx, obj} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "Apply", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// Apply indicates an expected call of Apply. -func (mr *MockSubResourceClientMockRecorder) Apply(ctx, obj any, opts ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{ctx, obj}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Apply", reflect.TypeOf((*MockSubResourceClient)(nil).Apply), varargs...) -} - -// Create mocks base method. -func (m *MockSubResourceClient) Create(ctx context.Context, obj, subResource client.Object, opts ...client.SubResourceCreateOption) error { - m.ctrl.T.Helper() - varargs := []any{ctx, obj, subResource} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "Create", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// Create indicates an expected call of Create. -func (mr *MockSubResourceClientMockRecorder) Create(ctx, obj, subResource any, opts ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{ctx, obj, subResource}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockSubResourceClient)(nil).Create), varargs...) -} - -// Get mocks base method. -func (m *MockSubResourceClient) Get(ctx context.Context, obj, subResource client.Object, opts ...client.SubResourceGetOption) error { - m.ctrl.T.Helper() - varargs := []any{ctx, obj, subResource} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "Get", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// Get indicates an expected call of Get. -func (mr *MockSubResourceClientMockRecorder) Get(ctx, obj, subResource any, opts ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{ctx, obj, subResource}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockSubResourceClient)(nil).Get), varargs...) -} - -// Patch mocks base method. -func (m *MockSubResourceClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) error { - m.ctrl.T.Helper() - varargs := []any{ctx, obj, patch} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "Patch", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// Patch indicates an expected call of Patch. -func (mr *MockSubResourceClientMockRecorder) Patch(ctx, obj, patch any, opts ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{ctx, obj, patch}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Patch", reflect.TypeOf((*MockSubResourceClient)(nil).Patch), varargs...) -} - -// Update mocks base method. -func (m *MockSubResourceClient) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { - m.ctrl.T.Helper() - varargs := []any{ctx, obj} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "Update", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// Update indicates an expected call of Update. -func (mr *MockSubResourceClientMockRecorder) Update(ctx, obj any, opts ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{ctx, obj}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockSubResourceClient)(nil).Update), varargs...) -}