From a5c22a2bfdb2904a800b8e9e1df46feff05a0d40 Mon Sep 17 00:00:00 2001 From: Randy Bruno Piverger Date: Thu, 14 May 2026 18:53:10 -0700 Subject: [PATCH] :seedling: Add CertRotationController support for PlacementDebugServer TLS (#1494) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add service-CA certificate support for PlacementDebugServer When the PlacementDebugServer feature gate is enabled, inject a serving-cert annotation into the placement service and mount the resulting TLS secret into the debug-server container. On OpenShift, the service-serving-cert controller creates a CA-signed certificate automatically. On non-OpenShift, optional: true allows the pod to start and library-go falls back to self-signed certificates. Signed-off-by: Randy Bruno Piverger <21374229+Randy424@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 * Rename fields to PlacementAnnotations and PlacementServingCertSecret Scope field names to Placement per review feedback, since these are only used for the placement service and extending to other services would require separate fields. Signed-off-by: Randy Bruno Piverger <21374229+Randy424@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 * Replace OCP annotation with CertRotationController for PlacementDebugServer TLS Replaces the OpenShift-specific serving-cert-secret-name annotation with the OCM-native CertRotationController to provision the PlacementDebugServer's TLS serving certificate. Follows the existing GRPC conditional target pattern: the placement-debug-serving-cert target is added/removed based on the PlacementDebugServer feature gate. Signed-off-by: Randy Bruno Piverger <21374229+Randy424@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 * Fix misleading error messages and import ordering Correct error messages in feature-disabled cleanup paths to accurately state the operation (secret deletion with feature disabled) instead of implying a deleted ClusterManager. Also move ocmfeature import into the open-cluster-management.io group where it belongs. Signed-off-by: Randy Bruno Piverger <21374229+Randy424@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 * Fix RBAC and gofmt for PlacementDebugServer cert rotation Add placement-debug-serving-cert to the cluster-manager ClusterRole resourceNames allowlist and fix gofmt alignment in two files. Root cause of E2E failures: the certRotationController attempts to delete the placement-debug-serving-cert secret when the feature gate is disabled (the default). The operator ClusterRole restricts secret delete/get/update/patch to an explicit resourceNames list. Because placement-debug-serving-cert was not in that list, the delete call returned 403 Forbidden — not 404 NotFound. The error handler in syncOne() only ignores IsNotFound, so 403 caused an early return before the signing CA and ca-bundle-configmap were ever created. The clusterManagerController.sync() blocks at line 312 waiting for ca-bundle-configmap to appear, so ObservedGeneration was never set, and all four E2E suites timed out in BeforeSuite after 150 seconds. The gofmt failures were cosmetic: extra alignment spaces in the PlacementDebugServingCertSecret/PlacementDebugService const block and the PlacementDebugServerEnabled/PlacementServingCertSecret struct fields. Signed-off-by: Randy Bruno Piverger <21374229+Randy424@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 * retrigger CI Signed-off-by: Randy Bruno Piverger <21374229+Randy424@users.noreply.github.com> * retrigger CI Signed-off-by: Randy Bruno Piverger <21374229+Randy424@users.noreply.github.com> --------- Signed-off-by: Randy Bruno Piverger <21374229+Randy424@users.noreply.github.com> Co-authored-by: Randy Bruno Piverger <21374229+Randy424@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 --- .../templates/cluster_role.yaml | 1 + .../config/rbac/cluster_role.yaml | 1 + .../management/placement/deployment.yaml | 12 ++ manifests/config.go | 1 + pkg/operator/helpers/helpers.go | 12 ++ pkg/operator/helpers/queuekey.go | 3 + .../certrotation_controller.go | 39 +++- .../certrotation_controller_test.go | 189 ++++++++++++++++-- .../clustermanager_controller.go | 3 + .../operators/clustermanager/options.go | 13 +- 10 files changed, 252 insertions(+), 22 deletions(-) diff --git a/deploy/cluster-manager/chart/cluster-manager/templates/cluster_role.yaml b/deploy/cluster-manager/chart/cluster-manager/templates/cluster_role.yaml index 297f8f07c..5d95b7607 100644 --- a/deploy/cluster-manager/chart/cluster-manager/templates/cluster_role.yaml +++ b/deploy/cluster-manager/chart/cluster-manager/templates/cluster_role.yaml @@ -36,6 +36,7 @@ rules: - "work-driver-config" - "open-cluster-management-image-pull-credentials" - "grpc-server-serving-cert" + - "placement-debug-serving-cert" - "cluster-import-config" - apiGroups: [""] resources: ["secrets"] diff --git a/deploy/cluster-manager/config/rbac/cluster_role.yaml b/deploy/cluster-manager/config/rbac/cluster_role.yaml index 8f2026691..5932af19b 100644 --- a/deploy/cluster-manager/config/rbac/cluster_role.yaml +++ b/deploy/cluster-manager/config/rbac/cluster_role.yaml @@ -38,6 +38,7 @@ rules: - "work-driver-config" - "open-cluster-management-image-pull-credentials" - "grpc-server-serving-cert" + - "placement-debug-serving-cert" - "cluster-import-config" - apiGroups: [""] resources: ["secrets"] diff --git a/manifests/cluster-manager/management/placement/deployment.yaml b/manifests/cluster-manager/management/placement/deployment.yaml index a5f4d1fa5..2de7b02fb 100644 --- a/manifests/cluster-manager/management/placement/deployment.yaml +++ b/manifests/cluster-manager/management/placement/deployment.yaml @@ -171,10 +171,22 @@ spec: volumeMounts: - name: tmpdir mountPath: /tmp + {{- if .PlacementServingCertSecret }} + - name: serving-cert + mountPath: /var/run/secrets/serving-cert + readOnly: true + {{- end }} {{ end }} volumes: - name: tmpdir emptyDir: { } + {{- if .PlacementServingCertSecret }} + - name: serving-cert + secret: + secretName: {{ .PlacementServingCertSecret }} + defaultMode: 420 + optional: true + {{- end }} {{ if .HostedMode }} - name: kubeconfig secret: diff --git a/manifests/config.go b/manifests/config.go index 4fcedf7b4..254872003 100644 --- a/manifests/config.go +++ b/manifests/config.go @@ -14,6 +14,7 @@ type HubConfig struct { WorkAPIServiceCABundle string PlacementImage string PlacementDebugServerEnabled bool + PlacementServingCertSecret string AddonAPIServiceCABundle string Replica int32 HostedMode bool diff --git a/pkg/operator/helpers/helpers.go b/pkg/operator/helpers/helpers.go index 7dbb2f0bf..d2d20c31f 100644 --- a/pkg/operator/helpers/helpers.go +++ b/pkg/operator/helpers/helpers.go @@ -39,6 +39,7 @@ import ( apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" apiregistrationclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1" + ocmfeature "open-cluster-management.io/api/feature" operatorapiv1 "open-cluster-management.io/api/operator/v1" "open-cluster-management.io/sdk-go/pkg/basecontroller/events" @@ -915,6 +916,17 @@ func AddLabelsToYaml(objData []byte, cmLabels map[string]string) ([]byte, error) return modifiedYAML, nil } +func PlacementDebugServerEnabled(cm *operatorapiv1.ClusterManager) bool { + if cm.Spec.PlacementConfiguration == nil { + return false + } + return FeatureGateEnabled( + cm.Spec.PlacementConfiguration.FeatureGates, + ocmfeature.DefaultHubPlacementFeatureGates, + ocmfeature.PlacementDebugServer, + ) +} + func GRPCAuthEnabled(cm *operatorapiv1.ClusterManager) bool { if cm.Spec.RegistrationConfiguration == nil { return false diff --git a/pkg/operator/helpers/queuekey.go b/pkg/operator/helpers/queuekey.go index 86e112319..2c04534be 100644 --- a/pkg/operator/helpers/queuekey.go +++ b/pkg/operator/helpers/queuekey.go @@ -48,6 +48,9 @@ const ( CaBundleConfigmap = "ca-bundle-configmap" GRPCServerSecret = "grpc-server-serving-cert" //#nosec G101 + + PlacementDebugServingCertSecret = "placement-debug-serving-cert" //#nosec G101 + PlacementDebugService = "cluster-manager-placement" ) func ClusterManagerNamespace(clustermanagername string, mode operatorapiv1.InstallMode) string { diff --git a/pkg/operator/operators/clustermanager/controllers/certrotationcontroller/certrotation_controller.go b/pkg/operator/operators/clustermanager/controllers/certrotationcontroller/certrotation_controller.go index aa5e40527..83d60449a 100644 --- a/pkg/operator/operators/clustermanager/controllers/certrotationcontroller/certrotation_controller.go +++ b/pkg/operator/operators/clustermanager/controllers/certrotationcontroller/certrotation_controller.go @@ -82,7 +82,8 @@ func NewCertRotationController( secretInformers[helpers.RegistrationWebhookSecret].Informer(), secretInformers[helpers.WorkWebhookSecret].Informer(), secretInformers[helpers.AddonWebhookSecret].Informer(), - secretInformers[helpers.GRPCServerSecret].Informer()). + secretInformers[helpers.GRPCServerSecret].Informer(), + secretInformers[helpers.PlacementDebugServingCertSecret].Informer()). ToController("CertRotationController") } @@ -178,6 +179,12 @@ func (c certRotationController) syncOne(ctx context.Context, clustermanager *ope return fmt.Errorf("clean up deleted cluster-manager, deleting grpc server secret failed, err:%s", err.Error()) } + // delete placement debug serving secret + err = c.kubeClient.CoreV1().Secrets(clustermanagerNamespace).Delete(ctx, helpers.PlacementDebugServingCertSecret, metav1.DeleteOptions{}) + if err != nil && !errors.IsNotFound(err) { + return fmt.Errorf("clean up deleted cluster-manager, deleting placement debug serving secret failed, err:%s", err.Error()) + } + delete(c.rotationMap, clustermanagerName) } return nil @@ -191,6 +198,18 @@ func (c certRotationController) syncOne(ctx context.Context, clustermanager *ope return err } + // delete the placement debug serving secret if the feature is disabled + if !helpers.PlacementDebugServerEnabled(clustermanager) { + if _, ok := c.rotationMap[clustermanager.Name]; ok { + delete(c.rotationMap[clustermanager.Name].targetRotations, helpers.PlacementDebugServingCertSecret) + } + + err = c.kubeClient.CoreV1().Secrets(clustermanagerNamespace).Delete(ctx, helpers.PlacementDebugServingCertSecret, metav1.DeleteOptions{}) + if err != nil && !errors.IsNotFound(err) { + return fmt.Errorf("failed to delete secret %q for cluster-manager %q: PlacementDebugServer feature disabled, err: %v", helpers.PlacementDebugServingCertSecret, clustermanager.Name, err) + } + } + // delete the grpc serving secret if the grpc auth is disabled if !helpers.GRPCAuthEnabled(clustermanager) { if _, ok := c.rotationMap[clustermanager.Name]; ok { @@ -199,7 +218,7 @@ func (c certRotationController) syncOne(ctx context.Context, clustermanager *ope err = c.kubeClient.CoreV1().Secrets(clustermanagerNamespace).Delete(ctx, helpers.GRPCServerSecret, metav1.DeleteOptions{}) if err != nil && !errors.IsNotFound(err) { - return fmt.Errorf("clean up deleted cluster-manager, deleting grpc server secret failed, err:%s", err.Error()) + return fmt.Errorf("failed to delete secret %q for cluster-manager %q: GRPCAuth feature disabled, err: %v", helpers.GRPCServerSecret, clustermanager.Name, err) } } @@ -282,6 +301,22 @@ func (c certRotationController) syncOne(ctx context.Context, clustermanager *ope } } + if helpers.PlacementDebugServerEnabled(clustermanager) { + placementServiceName := fmt.Sprintf("%s-placement", clustermanager.Name) + hostNames := []string{fmt.Sprintf("%s.%s.svc", placementServiceName, clustermanagerNamespace)} + + if _, ok := cmRotations.targetRotations[helpers.PlacementDebugServingCertSecret]; !ok { + c.rotationMap[clustermanagerName].targetRotations[helpers.PlacementDebugServingCertSecret] = certrotation.TargetRotation{ + Namespace: clustermanagerNamespace, + Name: helpers.PlacementDebugServingCertSecret, + Validity: TargetCertValidity, + HostNames: hostNames, + Lister: c.secretInformers[helpers.PlacementDebugServingCertSecret].Lister(), + Client: c.kubeClient.CoreV1(), + } + } + } + // reconcile cert/key pair for signer signingCertKeyPair, err := cmRotations.signingRotation.EnsureSigningCertKeyPair() if err != nil { diff --git a/pkg/operator/operators/clustermanager/controllers/certrotationcontroller/certrotation_controller_test.go b/pkg/operator/operators/clustermanager/controllers/certrotationcontroller/certrotation_controller_test.go index 23e5402eb..25769991e 100644 --- a/pkg/operator/operators/clustermanager/controllers/certrotationcontroller/certrotation_controller_test.go +++ b/pkg/operator/operators/clustermanager/controllers/certrotationcontroller/certrotation_controller_test.go @@ -172,11 +172,12 @@ func TestCertRotation(t *testing.T) { } secretInformers := map[string]corev1informers.SecretInformer{ - helpers.SignerSecret: newOnTermInformer(helpers.SignerSecret).Core().V1().Secrets(), - helpers.RegistrationWebhookSecret: newOnTermInformer(helpers.RegistrationWebhookSecret).Core().V1().Secrets(), - helpers.WorkWebhookSecret: newOnTermInformer(helpers.WorkWebhookSecret).Core().V1().Secrets(), - helpers.AddonWebhookSecret: newOnTermInformer(helpers.AddonWebhookSecret).Core().V1().Secrets(), - helpers.GRPCServerSecret: newOnTermInformer(helpers.GRPCServerSecret).Core().V1().Secrets(), + helpers.SignerSecret: newOnTermInformer(helpers.SignerSecret).Core().V1().Secrets(), + helpers.RegistrationWebhookSecret: newOnTermInformer(helpers.RegistrationWebhookSecret).Core().V1().Secrets(), + helpers.WorkWebhookSecret: newOnTermInformer(helpers.WorkWebhookSecret).Core().V1().Secrets(), + helpers.AddonWebhookSecret: newOnTermInformer(helpers.AddonWebhookSecret).Core().V1().Secrets(), + helpers.GRPCServerSecret: newOnTermInformer(helpers.GRPCServerSecret).Core().V1().Secrets(), + helpers.PlacementDebugServingCertSecret: newOnTermInformer(helpers.PlacementDebugServingCertSecret).Core().V1().Secrets(), } configmapInformer := newOnTermInformer(helpers.CaBundleConfigmap).Core().V1().ConfigMaps() @@ -316,11 +317,12 @@ func TestCertRotationGRPCAuth(t *testing.T) { } secretInformers := map[string]corev1informers.SecretInformer{ - helpers.SignerSecret: newOnTermInformer(helpers.SignerSecret).Core().V1().Secrets(), - helpers.RegistrationWebhookSecret: newOnTermInformer(helpers.RegistrationWebhookSecret).Core().V1().Secrets(), - helpers.WorkWebhookSecret: newOnTermInformer(helpers.WorkWebhookSecret).Core().V1().Secrets(), - helpers.AddonWebhookSecret: newOnTermInformer(helpers.AddonWebhookSecret).Core().V1().Secrets(), - helpers.GRPCServerSecret: newOnTermInformer(helpers.GRPCServerSecret).Core().V1().Secrets(), + helpers.SignerSecret: newOnTermInformer(helpers.SignerSecret).Core().V1().Secrets(), + helpers.RegistrationWebhookSecret: newOnTermInformer(helpers.RegistrationWebhookSecret).Core().V1().Secrets(), + helpers.WorkWebhookSecret: newOnTermInformer(helpers.WorkWebhookSecret).Core().V1().Secrets(), + helpers.AddonWebhookSecret: newOnTermInformer(helpers.AddonWebhookSecret).Core().V1().Secrets(), + helpers.GRPCServerSecret: newOnTermInformer(helpers.GRPCServerSecret).Core().V1().Secrets(), + helpers.PlacementDebugServingCertSecret: newOnTermInformer(helpers.PlacementDebugServingCertSecret).Core().V1().Secrets(), } configmapInformer := newOnTermInformer(helpers.CaBundleConfigmap).Core().V1().ConfigMaps() @@ -672,11 +674,12 @@ func TestCertRotationGRPCServerHostNames(t *testing.T) { } secretInformers := map[string]corev1informers.SecretInformer{ - helpers.SignerSecret: newOnTermInformer(helpers.SignerSecret).Core().V1().Secrets(), - helpers.RegistrationWebhookSecret: newOnTermInformer(helpers.RegistrationWebhookSecret).Core().V1().Secrets(), - helpers.WorkWebhookSecret: newOnTermInformer(helpers.WorkWebhookSecret).Core().V1().Secrets(), - helpers.AddonWebhookSecret: newOnTermInformer(helpers.AddonWebhookSecret).Core().V1().Secrets(), - helpers.GRPCServerSecret: newOnTermInformer(helpers.GRPCServerSecret).Core().V1().Secrets(), + helpers.SignerSecret: newOnTermInformer(helpers.SignerSecret).Core().V1().Secrets(), + helpers.RegistrationWebhookSecret: newOnTermInformer(helpers.RegistrationWebhookSecret).Core().V1().Secrets(), + helpers.WorkWebhookSecret: newOnTermInformer(helpers.WorkWebhookSecret).Core().V1().Secrets(), + helpers.AddonWebhookSecret: newOnTermInformer(helpers.AddonWebhookSecret).Core().V1().Secrets(), + helpers.GRPCServerSecret: newOnTermInformer(helpers.GRPCServerSecret).Core().V1().Secrets(), + helpers.PlacementDebugServingCertSecret: newOnTermInformer(helpers.PlacementDebugServingCertSecret).Core().V1().Secrets(), } configmapInformer := newOnTermInformer(helpers.CaBundleConfigmap).Core().V1().ConfigMaps() @@ -716,6 +719,162 @@ func TestCertRotationGRPCServerHostNames(t *testing.T) { } } +func TestCertRotationPlacementDebug(t *testing.T) { + namespace := helpers.ClusterManagerNamespace(testClusterManagerNameDefault, operatorapiv1.InstallModeDefault) + + cases := []struct { + name string + clusterManager *operatorapiv1.ClusterManager + updatedClusterManager *operatorapiv1.ClusterManager + existingObjects []runtime.Object + validate func(t *testing.T, kubeClient kubernetes.Interface, controller *certRotationController) + }{ + { + name: "Enable PlacementDebugServer", + clusterManager: func() *operatorapiv1.ClusterManager { + cm := newClusterManager(testClusterManagerNameDefault, operatorapiv1.InstallModeDefault) + cm.Spec.PlacementConfiguration = nil + return cm + }(), + updatedClusterManager: func() *operatorapiv1.ClusterManager { + cm := newClusterManager(testClusterManagerNameDefault, operatorapiv1.InstallModeDefault) + cm.Spec.PlacementConfiguration = &operatorapiv1.PlacementConfiguration{ + FeatureGates: []operatorapiv1.FeatureGate{ + { + Feature: "PlacementDebugServer", + Mode: operatorapiv1.FeatureGateModeTypeEnable, + }, + }, + } + return cm + }(), + existingObjects: []runtime.Object{ + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + }, + }, + validate: func(t *testing.T, kubeClient kubernetes.Interface, controller *certRotationController) { + secret, err := kubeClient.CoreV1().Secrets(namespace).Get(context.Background(), helpers.PlacementDebugServingCertSecret, metav1.GetOptions{}) + if err != nil { + t.Fatalf("expected placement debug serving cert secret to be created after update, but got error: %v", err) + } + + if _, ok := secret.Data["tls.crt"]; !ok { + t.Fatalf("expected tls.crt in secret data") + } + if _, ok := secret.Data["tls.key"]; !ok { + t.Fatalf("expected tls.key in secret data") + } + + cmRotations, ok := controller.rotationMap[testClusterManagerNameDefault] + if !ok { + t.Fatalf("expected rotations to exist in map") + } + if _, ok := cmRotations.targetRotations[helpers.PlacementDebugServingCertSecret]; !ok { + t.Fatalf("expected placement debug serving cert rotation to be added after update, %v", cmRotations) + } + }, + }, + { + name: "Disable PlacementDebugServer", + clusterManager: func() *operatorapiv1.ClusterManager { + cm := newClusterManager(testClusterManagerNameDefault, operatorapiv1.InstallModeDefault) + cm.Spec.PlacementConfiguration = &operatorapiv1.PlacementConfiguration{ + FeatureGates: []operatorapiv1.FeatureGate{ + { + Feature: "PlacementDebugServer", + Mode: operatorapiv1.FeatureGateModeTypeEnable, + }, + }, + } + return cm + }(), + updatedClusterManager: func() *operatorapiv1.ClusterManager { + cm := newClusterManager(testClusterManagerNameDefault, operatorapiv1.InstallModeDefault) + cm.Spec.PlacementConfiguration = nil + return cm + }(), + existingObjects: []runtime.Object{ + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + }, + }, + validate: func(t *testing.T, kubeClient kubernetes.Interface, controller *certRotationController) { + _, err := kubeClient.CoreV1().Secrets(namespace).Get(context.Background(), helpers.PlacementDebugServingCertSecret, metav1.GetOptions{}) + if !errors.IsNotFound(err) { + t.Fatalf("expected placement debug serving cert secret to be deleted after update, but got error: %v", err) + } + + cmRotations, ok := controller.rotationMap[testClusterManagerNameDefault] + if !ok { + t.Fatalf("expected rotations to exist in map") + } + if _, ok := cmRotations.targetRotations[helpers.PlacementDebugServingCertSecret]; ok { + t.Fatalf("expected placement debug serving cert rotation to be removed after update, %v", cmRotations) + } + }, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + kubeClient := fakekube.NewSimpleClientset(c.existingObjects...) + + newOnTermInformer := func(name string) kubeinformers.SharedInformerFactory { + return kubeinformers.NewSharedInformerFactoryWithOptions(kubeClient, 5*time.Minute, + kubeinformers.WithTweakListOptions(func(options *metav1.ListOptions) { + options.FieldSelector = fields.OneTermEqualSelector("metadata.name", name).String() + })) + } + + secretInformers := map[string]corev1informers.SecretInformer{ + helpers.SignerSecret: newOnTermInformer(helpers.SignerSecret).Core().V1().Secrets(), + helpers.RegistrationWebhookSecret: newOnTermInformer(helpers.RegistrationWebhookSecret).Core().V1().Secrets(), + helpers.WorkWebhookSecret: newOnTermInformer(helpers.WorkWebhookSecret).Core().V1().Secrets(), + helpers.AddonWebhookSecret: newOnTermInformer(helpers.AddonWebhookSecret).Core().V1().Secrets(), + helpers.GRPCServerSecret: newOnTermInformer(helpers.GRPCServerSecret).Core().V1().Secrets(), + helpers.PlacementDebugServingCertSecret: newOnTermInformer(helpers.PlacementDebugServingCertSecret).Core().V1().Secrets(), + } + + configmapInformer := newOnTermInformer(helpers.CaBundleConfigmap).Core().V1().ConfigMaps() + + operatorClient := fakeoperatorclient.NewSimpleClientset(c.clusterManager) + operatorInformers := operatorinformers.NewSharedInformerFactory(operatorClient, 5*time.Minute) + clusterManagerStore := operatorInformers.Operator().V1().ClusterManagers().Informer().GetStore() + if err := clusterManagerStore.Add(c.clusterManager); err != nil { + t.Fatal(err) + } + + syncContext := testingcommon.NewFakeSyncContext(t, testClusterManagerNameDefault) + + controller := &certRotationController{ + rotationMap: make(map[string]rotations), + kubeClient: kubeClient, + secretInformers: secretInformers, + configMapInformer: configmapInformer, + clusterManagerLister: operatorInformers.Operator().V1().ClusterManagers().Lister(), + } + + if err := controller.sync(context.TODO(), syncContext, testClusterManagerNameDefault); err != nil { + t.Fatal(err) + } + + if err := clusterManagerStore.Update(c.updatedClusterManager); err != nil { + t.Fatal(err) + } + if err := controller.sync(context.TODO(), syncContext, testClusterManagerNameDefault); err != nil { + t.Fatal(err) + } + + c.validate(t, kubeClient, controller) + }) + } +} + func assertResourcesExistAndValid(t *testing.T, kubeClient kubernetes.Interface, namespace string) { configmap, err := kubeClient.CoreV1().ConfigMaps(namespace).Get(context.Background(), "ca-bundle-configmap", metav1.GetOptions{}) if err != nil { diff --git a/pkg/operator/operators/clustermanager/controllers/clustermanagercontroller/clustermanager_controller.go b/pkg/operator/operators/clustermanager/controllers/clustermanagercontroller/clustermanager_controller.go index 4a8795832..dafb38fab 100644 --- a/pkg/operator/operators/clustermanager/controllers/clustermanagercontroller/clustermanager_controller.go +++ b/pkg/operator/operators/clustermanager/controllers/clustermanagercontroller/clustermanager_controller.go @@ -227,6 +227,9 @@ func (n *clusterManagerController) sync(ctx context.Context, controllerContext f } _, placementFeatureMsgs = helpers.ConvertToFeatureGateFlags("Placement", placementFeatureGates, ocmfeature.DefaultHubPlacementFeatureGates) config.PlacementDebugServerEnabled = helpers.FeatureGateEnabled(placementFeatureGates, ocmfeature.DefaultHubPlacementFeatureGates, ocmfeature.PlacementDebugServer) + if config.PlacementDebugServerEnabled { + config.PlacementServingCertSecret = helpers.PlacementDebugServingCertSecret + } featureGateCondition := helpers.BuildFeatureCondition(registrationFeatureMsgs, workFeatureMsgs, addonFeatureMsgs, placementFeatureMsgs) diff --git a/pkg/operator/operators/clustermanager/options.go b/pkg/operator/operators/clustermanager/options.go index 1b03a8b84..1000d66e9 100644 --- a/pkg/operator/operators/clustermanager/options.go +++ b/pkg/operator/operators/clustermanager/options.go @@ -60,6 +60,7 @@ func (o *Options) RunClusterManagerOperator(ctx context.Context, controllerConte workSecretInformer := newOneTermInformer(helpers.WorkWebhookSecret) addonSecretInformer := newOneTermInformer(helpers.AddonWebhookSecret) grpcServerSecretInformer := newOneTermInformer(helpers.GRPCServerSecret) + placementDebugSecretInformer := newOneTermInformer(helpers.PlacementDebugServingCertSecret) configmapInformer := newOneTermInformer(helpers.CaBundleConfigmap) deploymentInformer := informers.NewSharedInformerFactoryWithOptions(kubeClient, 5*time.Minute, @@ -76,11 +77,12 @@ func (o *Options) RunClusterManagerOperator(ctx context.Context, controllerConte })) secretInformers := map[string]corev1informers.SecretInformer{ - helpers.SignerSecret: signerSecretInformer.Core().V1().Secrets(), - helpers.RegistrationWebhookSecret: registrationSecretInformer.Core().V1().Secrets(), - helpers.WorkWebhookSecret: workSecretInformer.Core().V1().Secrets(), - helpers.AddonWebhookSecret: addonSecretInformer.Core().V1().Secrets(), - helpers.GRPCServerSecret: grpcServerSecretInformer.Core().V1().Secrets(), + helpers.SignerSecret: signerSecretInformer.Core().V1().Secrets(), + helpers.RegistrationWebhookSecret: registrationSecretInformer.Core().V1().Secrets(), + helpers.WorkWebhookSecret: workSecretInformer.Core().V1().Secrets(), + helpers.AddonWebhookSecret: addonSecretInformer.Core().V1().Secrets(), + helpers.GRPCServerSecret: grpcServerSecretInformer.Core().V1().Secrets(), + helpers.PlacementDebugServingCertSecret: placementDebugSecretInformer.Core().V1().Secrets(), } // Build operator client and informer @@ -153,6 +155,7 @@ func (o *Options) RunClusterManagerOperator(ctx context.Context, controllerConte go workSecretInformer.Start(ctx.Done()) go addonSecretInformer.Start(ctx.Done()) go grpcServerSecretInformer.Start(ctx.Done()) + go placementDebugSecretInformer.Start(ctx.Done()) go configmapInformer.Start(ctx.Done()) go clusterManagerController.Run(ctx, 1) go statusController.Run(ctx, 1)