diff --git a/test/extended/tls/tls_observed_config.go b/test/extended/tls/tls_observed_config.go index 0ff25a526326..c9cff81cd996 100644 --- a/test/extended/tls/tls_observed_config.go +++ b/test/extended/tls/tls_observed_config.go @@ -8,6 +8,7 @@ import ( "io" "math/rand" "net" + "os" "os/exec" "strings" "time" @@ -42,330 +43,165 @@ const ( injectTLSAnnotation = "config.openshift.io/inject-tls" ) -// tlsTarget describes a namespace/service that must honor the cluster APIServer -// TLS profile. Each target gets its own Ginkgo It block so failures are -// reported per-namespace, following the same pattern as the ROFS tests. -type tlsTarget struct { - // namespace is the OpenShift namespace that contains the operator workload. - namespace string - // deploymentName is the name of the Deployment to inspect for TLS env vars. - // If empty, the env-var check is skipped and only wire-level TLS is tested. - deploymentName string - // tlsMinVersionEnvVar is the environment variable name that carries the - // minimum TLS version (e.g. "REGISTRY_HTTP_TLS_MINVERSION"). - // If empty, the env-var check is skipped. - tlsMinVersionEnvVar string - // cipherSuitesEnvVar is the environment variable name that carries the - // comma-separated list of cipher suites (e.g. "OPENSHIFT_REGISTRY_HTTP_TLS_CIPHERSUITES"). - // If empty, the cipher suite env-var check is skipped. - cipherSuitesEnvVar string - // serviceName is the Kubernetes Service name used for wire-level TLS - // testing via oc port-forward. If empty, the wire-level test is skipped. - serviceName string - // servicePort is the port the TLS service listens on. - servicePort string - // operatorConfigGVR is the GroupVersionResource of the operator config - // resource that contains ObservedConfig (e.g. imageregistries). - // If zero, the ObservedConfig check is skipped. - operatorConfigGVR schema.GroupVersionResource - // operatorConfigName is the name of the operator config resource (e.g. "cluster"). +// ─── Narrow target types ─────────────────────────────────────────────────── +// Each type carries only the fields its test function actually reads, +// making it immediately clear what data a test depends on. + +// observedConfigTarget identifies an operator whose spec.observedConfig +// must contain servingInfo with minTLSVersion and cipherSuites. +type observedConfigTarget struct { + namespace string + operatorConfigGVR schema.GroupVersionResource operatorConfigName string - // clusterOperatorName is the ClusterOperator name to wait for during - // stabilization (e.g. "image-registry", "openshift-controller-manager"). - // If empty, stability check is skipped. - clusterOperatorName string - // configMapName is the name of the ConfigMap that CVO injects TLS config into - // via the config.openshift.io/inject-tls annotation. - // If empty, the ConfigMap check is skipped. - configMapName string - // configMapNamespace is the namespace where the ConfigMap is located. - configMapNamespace string - // configMapKey is the key within the ConfigMap that contains the TLS config - configMapKey string - // controlPlane indicates this target runs in the control plane. On - // HyperShift (external control plane topology), these workloads run on the - // management cluster and are not accessible from the hosted guest cluster. - // Tests for control-plane targets are skipped on HyperShift. - controlPlane bool + controlPlane bool } -// targets is the unified list of OpenShift namespaces and services that should -// propagate the cluster APIServer TLS profile. Each entry can participate in -// multiple test categories (ObservedConfig, ConfigMap injection, env vars, -// wire-level TLS) depending on which fields are populated. The test loops -// filter by checking for non-empty fields, so secondary entries (e.g. an -// extra port on the same operator) can set only serviceName/servicePort for -// wire-level coverage while leaving operatorConfigGVR/configMapName empty to -// avoid duplicate checks already handled by the primary entry. -var targets = []tlsTarget{ - { - namespace: "openshift-image-registry", - deploymentName: "image-registry", - tlsMinVersionEnvVar: "REGISTRY_HTTP_TLS_MINVERSION", - cipherSuitesEnvVar: "OPENSHIFT_REGISTRY_HTTP_TLS_CIPHERSUITES", - serviceName: "image-registry", - servicePort: "5000", - operatorConfigGVR: schema.GroupVersionResource{ - Group: "imageregistry.operator.openshift.io", - Version: "v1", - Resource: "configs", - }, - operatorConfigName: "cluster", - clusterOperatorName: "image-registry", - // CVO injects TLS config into this ConfigMap via config.openshift.io/inject-tls annotation. - // PR 1297 (cluster-image-registry-operator) adds this annotation. - configMapName: "image-registry-operator-config", - configMapNamespace: "openshift-image-registry", - configMapKey: "config.yaml", - }, - // image-registry-operator metrics service on port 60000. - // PR 1297 (cluster-image-registry-operator, IR-350) makes the metrics - // server TLS configuration file-based, complying with global TLS profile. - { - namespace: "openshift-image-registry", - deploymentName: "", // Operator deployment, not image-registry deployment - // No TLS env vars — metrics server reads TLS from config files. - tlsMinVersionEnvVar: "", - cipherSuitesEnvVar: "", - serviceName: "image-registry-operator", - servicePort: "60000", - // ObservedConfig and ConfigMap are already verified by the primary - // image-registry entry above; this entry only adds wire-level TLS - // coverage for the operator metrics port. - operatorConfigGVR: schema.GroupVersionResource{}, - operatorConfigName: "", - clusterOperatorName: "image-registry", - configMapName: "", - configMapKey: "config.yaml", - controlPlane: true, - }, - // openshift-controller-manager propagates TLS config via ConfigMap - // (ObservedConfig → config.yaml), NOT via env vars. So we skip the - // env-var check but still verify ObservedConfig and wire-level TLS. - // PR 412 (cluster-openshift-controller-manager-operator) adds inject-tls annotation. - { - namespace: "openshift-controller-manager", - deploymentName: "controller-manager", - // No TLS env vars — controller-manager reads TLS from its config file. - tlsMinVersionEnvVar: "", - cipherSuitesEnvVar: "", - serviceName: "controller-manager", - servicePort: "443", - operatorConfigGVR: schema.GroupVersionResource{ - Group: "operator.openshift.io", - Version: "v1", - Resource: "openshiftcontrollermanagers", - }, - operatorConfigName: "cluster", - clusterOperatorName: "openshift-controller-manager", - // CVO injects TLS config into this ConfigMap (in the operator namespace). - configMapName: "openshift-controller-manager-operator-config", - configMapNamespace: "openshift-controller-manager-operator", - configMapKey: "config.yaml", - controlPlane: true, - }, - // kube-apiserver is a static pod managed by cluster-kube-apiserver-operator. - // PR 2032/2059 added TLS security profile propagation to its ObservedConfig. - // It reads TLS config from its config files, not env vars. - { - namespace: "openshift-kube-apiserver", - deploymentName: "", // Static pod, not a deployment - // No TLS env vars — kube-apiserver reads TLS from its config files. - tlsMinVersionEnvVar: "", - cipherSuitesEnvVar: "", - serviceName: "apiserver", - servicePort: "443", - operatorConfigGVR: schema.GroupVersionResource{ - Group: "operator.openshift.io", - Version: "v1", - Resource: "kubeapiservers", - }, - operatorConfigName: "cluster", - clusterOperatorName: "kube-apiserver", - // CVO injects TLS config into this ConfigMap in the operator namespace. - configMapName: "kube-apiserver-operator-config", - configMapNamespace: "openshift-kube-apiserver-operator", - configMapKey: "config.yaml", - controlPlane: true, - }, - // kube-apiserver's check-endpoints port (17697) on the apiserver service. - // PR 2032 (cluster-kube-apiserver-operator) ensures this port complies - // with the global TLS security profile. - { - namespace: "openshift-kube-apiserver", - deploymentName: "", // Static pod, not a deployment - // No TLS env vars — kube-apiserver reads TLS from config files. - tlsMinVersionEnvVar: "", - cipherSuitesEnvVar: "", - serviceName: "apiserver", - servicePort: "17697", - // ObservedConfig and ConfigMap are already verified by the primary - // kube-apiserver:443 entry above; this entry only adds wire-level - // TLS coverage for the check-endpoints port. - operatorConfigGVR: schema.GroupVersionResource{}, - operatorConfigName: "", - clusterOperatorName: "kube-apiserver", - controlPlane: true, - }, - // openshift-apiserver main API endpoint. - // PR 662 (cluster-openshift-apiserver-operator) adds inject-tls annotation. - { - namespace: "openshift-apiserver", - deploymentName: "apiserver", - // No TLS env vars — apiserver reads TLS from config files. - tlsMinVersionEnvVar: "", - cipherSuitesEnvVar: "", - serviceName: "api", - servicePort: "443", - operatorConfigGVR: schema.GroupVersionResource{ - Group: "operator.openshift.io", - Version: "v1", - Resource: "openshiftapiservers", - }, - operatorConfigName: "cluster", - clusterOperatorName: "openshift-apiserver", - // CVO injects TLS config into this ConfigMap in the operator namespace. - configMapName: "openshift-apiserver-operator-config", - configMapNamespace: "openshift-apiserver-operator", - configMapKey: "config.yaml", - controlPlane: true, - }, - // openshift-apiserver's check-endpoints service on port 17698. - // PR 657 (cluster-openshift-apiserver-operator, CNTRLPLANE-2619) ensures - // this port complies with the global TLS security profile. - { - namespace: "openshift-apiserver", - deploymentName: "", // check-endpoints uses same deployment - // No TLS env vars — reads TLS from config files. - tlsMinVersionEnvVar: "", - cipherSuitesEnvVar: "", - serviceName: "check-endpoints", - servicePort: "17698", - // ObservedConfig and ConfigMap are already verified by the primary - // openshift-apiserver:443 entry above; this entry only adds - // wire-level TLS coverage for the check-endpoints port. - operatorConfigGVR: schema.GroupVersionResource{}, - operatorConfigName: "", - clusterOperatorName: "openshift-apiserver", - controlPlane: true, - }, - // cluster-version-operator (CVO). - // PR 1322 enables CVO to INJECT TLS config into OTHER operators' ConfigMaps - // (those annotated with config.openshift.io/inject-tls: "true"). - // NOTE: CVO's own metrics endpoint (port 9099) does NOT currently respect - // the cluster-wide TLS profile - it always accepts TLS 1.2. This is expected - // behavior for now; the PR scope is ConfigMap injection, not CVO's own endpoint. - // Therefore we skip wire-level TLS tests for CVO (serviceName is empty). - { - namespace: "openshift-cluster-version", - deploymentName: "cluster-version-operator", - // No TLS env vars — CVO reads TLS from config files. - tlsMinVersionEnvVar: "", - cipherSuitesEnvVar: "", - // Skip wire-level TLS test: CVO metrics endpoint doesn't follow cluster TLS profile. - serviceName: "", - servicePort: "", - operatorConfigGVR: schema.GroupVersionResource{}, // CVO manages itself - operatorConfigName: "", - // CVO does not have a ClusterOperator for itself - it manages all other operators. - // Skip stability check; deployment rollout wait is sufficient. - clusterOperatorName: "", - // CVO does not use a ConfigMap with inject-tls annotation. - // It reads TLS config directly from the cluster config. - configMapName: "", - configMapKey: "config.yaml", - }, - // etcd is a static pod managed by cluster-etcd-operator. - // PR 1556 (cluster-etcd-operator) adds TLS security profile propagation. - { - namespace: "openshift-etcd", - deploymentName: "", // Static pod, not a deployment - // No TLS env vars — etcd reads TLS from its config files. - tlsMinVersionEnvVar: "", - cipherSuitesEnvVar: "", - serviceName: "etcd", - servicePort: "2379", - operatorConfigGVR: schema.GroupVersionResource{ - Group: "operator.openshift.io", - Version: "v1", - Resource: "etcds", - }, - operatorConfigName: "cluster", - clusterOperatorName: "etcd", - // CVO injects TLS config into this ConfigMap in the operator namespace. - configMapName: "etcd-operator-config", - configMapNamespace: "openshift-etcd-operator", - configMapKey: "config.yaml", - controlPlane: true, - }, - // kube-controller-manager is a static pod managed by cluster-kube-controller-manager-operator. - // PR 915 (cluster-kube-controller-manager-operator) adds TLS security profile propagation. - { - namespace: "openshift-kube-controller-manager", - deploymentName: "", // Static pod, not a deployment - // No TLS env vars — kube-controller-manager reads TLS from its config files. - tlsMinVersionEnvVar: "", - cipherSuitesEnvVar: "", - serviceName: "kube-controller-manager", - servicePort: "443", - operatorConfigGVR: schema.GroupVersionResource{ - Group: "operator.openshift.io", - Version: "v1", - Resource: "kubecontrollermanagers", - }, - operatorConfigName: "cluster", - clusterOperatorName: "kube-controller-manager", - // CVO injects TLS config into this ConfigMap in the operator namespace. - configMapName: "kube-controller-manager-operator-config", - configMapNamespace: "openshift-kube-controller-manager-operator", - configMapKey: "config.yaml", - controlPlane: true, - }, - // kube-scheduler is a static pod managed by cluster-kube-scheduler-operator. - // PR 617 (cluster-kube-scheduler-operator) adds TLS security profile propagation. - { - namespace: "openshift-kube-scheduler", - deploymentName: "", // Static pod, not a deployment - // No TLS env vars — kube-scheduler reads TLS from its config files. - tlsMinVersionEnvVar: "", - cipherSuitesEnvVar: "", - serviceName: "scheduler", - servicePort: "443", - operatorConfigGVR: schema.GroupVersionResource{ - Group: "operator.openshift.io", - Version: "v1", - Resource: "kubeschedulers", - }, - operatorConfigName: "cluster", - clusterOperatorName: "kube-scheduler", - // CVO injects TLS config into this ConfigMap in the operator namespace. - configMapName: "openshift-kube-scheduler-operator-config", - configMapNamespace: "openshift-kube-scheduler-operator", - configMapKey: "config.yaml", - controlPlane: true, - }, - // cluster-samples-operator metrics service on port 60000. - // PR 684 (cluster-samples-operator, CNTRLPLANE-3176) migrates the metrics - // server to config-driven TLS using GenericControllerConfig, complying - // with the global TLS security profile. - { - namespace: "openshift-cluster-samples-operator", - deploymentName: "cluster-samples-operator", - // No TLS env vars — metrics server reads TLS from config file. - tlsMinVersionEnvVar: "", - cipherSuitesEnvVar: "", - serviceName: "metrics", - servicePort: "60000", - // cluster-samples-operator does not have an ObservedConfig resource. - operatorConfigGVR: schema.GroupVersionResource{}, - operatorConfigName: "", - clusterOperatorName: "openshift-samples", - // CVO injects TLS config into this ConfigMap via config.openshift.io/inject-tls annotation. - configMapName: "samples-operator-config", - configMapNamespace: "openshift-cluster-samples-operator", - configMapKey: "config.yaml", - }, - // Add more namespaces/services as they adopt the TLS config sync pattern. +// configMapTarget identifies a ConfigMap that CVO injects TLS config into. +type configMapTarget struct { + namespace string // workload namespace (used in test names) + configMapName string + configMapNamespace string // namespace where the ConfigMap lives + configMapKey string // data key within the ConfigMap + controlPlane bool +} + +// deploymentEnvVarTarget identifies a Deployment whose containers must +// have TLS-related environment variables matching the cluster profile. +type deploymentEnvVarTarget struct { + namespace string + deploymentName string + tlsMinVersionEnvVar string + cipherSuitesEnvVar string + controlPlane bool +} + +// serviceTarget identifies a Service endpoint that must enforce the +// cluster TLS profile at the wire level. +type serviceTarget struct { + namespace string + serviceName string + servicePort string + deploymentName string // for waiting on rollout before probing + controlPlane bool +} + +// deploymentRolloutTarget identifies a Deployment that must complete +// rollout after a TLS profile change. +type deploymentRolloutTarget struct { + namespace string + deploymentName string +} + +// ─── Typed target lists ──────────────────────────────────────────────────── +// Each list contains exactly the entries relevant to one test category. +// Entries are derived from `targets` but only carry the fields the test uses. + +var observedConfigTargets = []observedConfigTarget{ + {namespace: "openshift-image-registry", operatorConfigGVR: schema.GroupVersionResource{Group: "imageregistry.operator.openshift.io", Version: "v1", Resource: "configs"}, operatorConfigName: "cluster"}, + {namespace: "openshift-controller-manager", operatorConfigGVR: schema.GroupVersionResource{Group: "operator.openshift.io", Version: "v1", Resource: "openshiftcontrollermanagers"}, operatorConfigName: "cluster", controlPlane: true}, + {namespace: "openshift-kube-apiserver", operatorConfigGVR: schema.GroupVersionResource{Group: "operator.openshift.io", Version: "v1", Resource: "kubeapiservers"}, operatorConfigName: "cluster", controlPlane: true}, + {namespace: "openshift-apiserver", operatorConfigGVR: schema.GroupVersionResource{Group: "operator.openshift.io", Version: "v1", Resource: "openshiftapiservers"}, operatorConfigName: "cluster", controlPlane: true}, + {namespace: "openshift-etcd", operatorConfigGVR: schema.GroupVersionResource{Group: "operator.openshift.io", Version: "v1", Resource: "etcds"}, operatorConfigName: "cluster", controlPlane: true}, + {namespace: "openshift-kube-controller-manager", operatorConfigGVR: schema.GroupVersionResource{Group: "operator.openshift.io", Version: "v1", Resource: "kubecontrollermanagers"}, operatorConfigName: "cluster", controlPlane: true}, + {namespace: "openshift-kube-scheduler", operatorConfigGVR: schema.GroupVersionResource{Group: "operator.openshift.io", Version: "v1", Resource: "kubeschedulers"}, operatorConfigName: "cluster", controlPlane: true}, +} + +var configMapTargets = []configMapTarget{ + {namespace: "openshift-image-registry", configMapName: "image-registry-operator-config", configMapNamespace: "openshift-image-registry", configMapKey: "config.yaml"}, + {namespace: "openshift-controller-manager", configMapName: "openshift-controller-manager-operator-config", configMapNamespace: "openshift-controller-manager-operator", configMapKey: "config.yaml"}, + {namespace: "openshift-kube-apiserver", configMapName: "kube-apiserver-operator-config", configMapNamespace: "openshift-kube-apiserver-operator", configMapKey: "config.yaml"}, + {namespace: "openshift-apiserver", configMapName: "openshift-apiserver-operator-config", configMapNamespace: "openshift-apiserver-operator", configMapKey: "config.yaml"}, + {namespace: "openshift-etcd", configMapName: "etcd-operator-config", configMapNamespace: "openshift-etcd-operator", configMapKey: "config.yaml", controlPlane: true}, + {namespace: "openshift-kube-controller-manager", configMapName: "kube-controller-manager-operator-config", configMapNamespace: "openshift-kube-controller-manager-operator", configMapKey: "config.yaml"}, + {namespace: "openshift-kube-scheduler", configMapName: "openshift-kube-scheduler-operator-config", configMapNamespace: "openshift-kube-scheduler-operator", configMapKey: "config.yaml"}, + {namespace: "openshift-cluster-samples-operator", configMapName: "samples-operator-config", configMapNamespace: "openshift-cluster-samples-operator", configMapKey: "config.yaml"}, +} + +var deploymentEnvVarTargets = []deploymentEnvVarTarget{ + {namespace: "openshift-image-registry", deploymentName: "image-registry", tlsMinVersionEnvVar: "REGISTRY_HTTP_TLS_MINVERSION", cipherSuitesEnvVar: "OPENSHIFT_REGISTRY_HTTP_TLS_CIPHERSUITES"}, +} + +var serviceTargets = []serviceTarget{ + {namespace: "openshift-image-registry", serviceName: "image-registry", servicePort: "5000", deploymentName: "image-registry"}, + {namespace: "openshift-image-registry", serviceName: "image-registry-operator", servicePort: "60000", controlPlane: true}, + {namespace: "openshift-controller-manager", serviceName: "controller-manager", servicePort: "443", deploymentName: "controller-manager", controlPlane: true}, + {namespace: "openshift-kube-apiserver", serviceName: "apiserver", servicePort: "443", controlPlane: true}, + {namespace: "openshift-kube-apiserver", serviceName: "apiserver", servicePort: "17697", controlPlane: true}, + {namespace: "openshift-apiserver", serviceName: "api", servicePort: "443", deploymentName: "apiserver", controlPlane: true}, + {namespace: "openshift-apiserver", serviceName: "check-endpoints", servicePort: "17698", controlPlane: true}, + {namespace: "openshift-etcd", serviceName: "etcd", servicePort: "2379", controlPlane: true}, + {namespace: "openshift-kube-controller-manager", serviceName: "kube-controller-manager", servicePort: "443", controlPlane: true}, + {namespace: "openshift-kube-scheduler", serviceName: "scheduler", servicePort: "443", controlPlane: true}, + {namespace: "openshift-cluster-samples-operator", serviceName: "metrics", servicePort: "60000", deploymentName: "cluster-samples-operator"}, +} + +// clusterOperatorNames is the deduplicated list of ClusterOperator names. +var clusterOperatorNames = []string{ + "image-registry", + "openshift-controller-manager", + "kube-apiserver", + "openshift-apiserver", + "etcd", + "kube-controller-manager", + "kube-scheduler", + "openshift-samples", +} + +var deploymentRolloutTargets = []deploymentRolloutTarget{ + {namespace: "openshift-image-registry", deploymentName: "image-registry"}, + {namespace: "openshift-controller-manager", deploymentName: "controller-manager"}, + {namespace: "openshift-apiserver", deploymentName: "apiserver"}, + {namespace: "openshift-cluster-version", deploymentName: "cluster-version-operator"}, + {namespace: "openshift-cluster-samples-operator", deploymentName: "cluster-samples-operator"}, +} + +// ─── Guest-side filters for HyperShift ───────────────────────────────────── + +func guestSideObservedConfigTargets() []observedConfigTarget { + var result []observedConfigTarget + for _, t := range observedConfigTargets { + if !t.controlPlane { + result = append(result, t) + } + } + return result +} + +func guestSideConfigMapTargets() []configMapTarget { + var result []configMapTarget + for _, t := range configMapTargets { + if !t.controlPlane { + result = append(result, t) + } + } + return result +} + +func guestSideDeploymentEnvVarTargets() []deploymentEnvVarTarget { + var result []deploymentEnvVarTarget + for _, t := range deploymentEnvVarTargets { + if !t.controlPlane { + result = append(result, t) + } + } + return result +} + +func guestSideServiceTargets() []serviceTarget { + var result []serviceTarget + for _, t := range serviceTargets { + if !t.controlPlane { + result = append(result, t) + } + } + return result +} + +// guestSideDeploymentRolloutTargets returns all deployment rollout targets. +// deploymentRolloutTarget has no controlPlane field because all rollout +// targets are accessible from the guest cluster. +func guestSideDeploymentRolloutTargets() []deploymentRolloutTarget { + return deploymentRolloutTargets } // ── read-only tests ──────────────────────────────────────────── @@ -391,12 +227,8 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Suite }) // ── Per-namespace ObservedConfig verification ─────────────────────── - for _, target := range targets { + for _, target := range observedConfigTargets { target := target - if target.operatorConfigGVR.Resource == "" || target.operatorConfigName == "" { - continue - } - g.It(fmt.Sprintf("should populate ObservedConfig with TLS settings - %s", target.namespace), func() { if isHyperShiftCluster && target.controlPlane { g.Skip(fmt.Sprintf("Skipping control-plane target %s on HyperShift (runs on management cluster)", target.namespace)) @@ -406,12 +238,8 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Suite } // ── Per-namespace ConfigMap TLS injection verification ────────────── - for _, target := range targets { + for _, target := range configMapTargets { target := target - if target.configMapName == "" { - continue - } - g.It(fmt.Sprintf("should have TLS config injected into ConfigMap - %s", target.namespace), func() { if isHyperShiftCluster && target.controlPlane { g.Skip(fmt.Sprintf("Skipping control-plane target %s on HyperShift (runs on management cluster)", target.namespace)) @@ -421,12 +249,8 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Suite } // ── Per-namespace TLS env-var verification ────────────────────────── - for _, target := range targets { + for _, target := range deploymentEnvVarTargets { target := target - if target.deploymentName == "" || target.tlsMinVersionEnvVar == "" { - continue - } - g.It(fmt.Sprintf("should propagate TLS config to deployment env vars - %s", target.namespace), func() { if isHyperShiftCluster && target.controlPlane { g.Skip(fmt.Sprintf("Skipping control-plane target %s on HyperShift (runs on management cluster)", target.namespace)) @@ -436,12 +260,8 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Suite } // ── Per-namespace wire-level TLS verification ─────────────────────── - for _, target := range targets { + for _, target := range serviceTargets { target := target - if target.serviceName == "" || target.servicePort == "" { - continue - } - g.It(fmt.Sprintf("should enforce TLS version at the wire level - %s:%s", target.namespace, target.servicePort), func() { if isHyperShiftCluster && target.controlPlane { g.Skip(fmt.Sprintf("Skipping control-plane target %s:%s on HyperShift (runs on management cluster)", target.namespace, target.servicePort)) @@ -462,13 +282,34 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Disru var isHyperShiftCluster bool - // HyperShift management cluster state, populated in BeforeEach when - // running on a HyperShift guest cluster. + // Pre-compute guest-side target lists so the filter functions are + // called once rather than on every config-change verification. + guestObservedCfg := guestSideObservedConfigTargets() + guestCMs := guestSideConfigMapTargets() + guestEnvVars := guestSideDeploymentEnvVarTargets() + guestSvcs := guestSideServiceTargets() + guestRollouts := guestSideDeploymentRolloutTargets() + + // HyperShift management cluster state, lazily populated by + // setupHyperShiftManagement. Only config-change tests need this; + // annotation/servingInfo restoration tests work without it. var mgmtOC *exutil.CLI var hcpNamespace string var hostedClusterName string var hostedClusterNS string + setupHyperShiftManagement := func() { + if os.Getenv("HYPERSHIFT_MANAGEMENT_CLUSTER_KUBECONFIG") == "" || os.Getenv("HYPERSHIFT_MANAGEMENT_CLUSTER_NAMESPACE") == "" { + g.Skip("HYPERSHIFT_MANAGEMENT_CLUSTER_KUBECONFIG and HYPERSHIFT_MANAGEMENT_CLUSTER_NAMESPACE is not set for config-change tests on HyperShift") + } + mgmtOC = exutil.NewHypershiftManagementCLI("tls-mgmt") + var err error + _, hcpNamespace, err = exutil.GetHypershiftManagementClusterConfigAndNamespace() + o.Expect(err).NotTo(o.HaveOccurred()) + hostedClusterName, hostedClusterNS = discoverHostedCluster(mgmtOC, hcpNamespace) + e2e.Logf("HyperShift: HC=%s/%s, HCP NS=%s", hostedClusterNS, hostedClusterName, hcpNamespace) + } + g.BeforeEach(func() { isMicroShift, err := exutil.IsMicroShiftCluster(oc.AdminKubeClient()) o.Expect(err).NotTo(o.HaveOccurred()) @@ -479,22 +320,11 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Disru isHS, err := exutil.IsHypershift(ctx, oc.AdminConfigClient()) o.Expect(err).NotTo(o.HaveOccurred()) isHyperShiftCluster = isHS - - if isHyperShiftCluster { - mgmtOC = exutil.NewHypershiftManagementCLI("tls-mgmt") - _, hcpNamespace, err = exutil.GetHypershiftManagementClusterConfigAndNamespace() - o.Expect(err).NotTo(o.HaveOccurred()) - hostedClusterName, hostedClusterNS = discoverHostedCluster(mgmtOC, hcpNamespace) - e2e.Logf("HyperShift: HC=%s/%s, HCP NS=%s", hostedClusterNS, hostedClusterName, hcpNamespace) - } }) // ── ConfigMap annotation restoration tests ──────────────────────────── - for _, target := range targets { + for _, target := range configMapTargets { target := target - if target.configMapName == "" { - continue - } g.It(fmt.Sprintf("should restore inject-tls annotation after deletion - %s", target.namespace), func() { if isHyperShiftCluster && target.controlPlane { @@ -535,6 +365,7 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Disru defer configChangeCancel() if isHyperShiftCluster { + setupHyperShiftManagement() // ── HyperShift flow: patch HostedCluster, wait for HCP pods ── modernPatch := `{"spec":{"configuration":{"apiServer":{"tlsSecurityProfile":{"modern":{},"type":"Modern"}}}}}` resetPatch := `{"spec":{"configuration":{"apiServer":null}}}` @@ -557,12 +388,10 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Disru e2e.Logf("DeferCleanup: restoring HostedCluster TLS profile to default") setTLSProfileOnHyperShift(mgmtOC, hostedClusterName, hostedClusterNS, resetPatch) waitForHCPPods(mgmtOC, hcpNamespace, 8*time.Minute) - waitForGuestOperatorsAfterTLSChange(oc, cleanupCtx, "restore") + waitForGuestOperatorsAfterTLSChange(oc, cleanupCtx, "restore", guestRollouts) e2e.Logf("DeferCleanup: HostedCluster TLS profile restored") }) - guestTargets := guestSideTargets() - // Phase 1: Modern g.By("patching HostedCluster with Modern TLS profile") setTLSProfileOnHyperShift(mgmtOC, hostedClusterName, hostedClusterNS, modernPatch) @@ -570,19 +399,16 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Disru g.By("waiting for HCP pods and guest operators to stabilize") waitForHCPPods(mgmtOC, hcpNamespace, 8*time.Minute) - waitForGuestOperatorsAfterTLSChange(oc, configChangeCtx, "Modern") + waitForGuestOperatorsAfterTLSChange(oc, configChangeCtx, "Modern", guestRollouts) g.By("verifying guest-side ObservedConfig reflects Modern profile") - verifyObservedConfigForTargets(oc, configChangeCtx, "VersionTLS13", "Modern", guestTargets) + verifyObservedConfigForTargets(oc, configChangeCtx, "VersionTLS13", "Modern", guestObservedCfg) g.By("verifying guest-side ConfigMaps reflect Modern profile") - verifyConfigMapsForTargets(oc, configChangeCtx, "VersionTLS13", "Modern", guestTargets) + verifyConfigMapsForTargets(oc, configChangeCtx, "VersionTLS13", "Modern", guestCMs) g.By("verifying HCP ConfigMaps reflect Modern profile") verifyHCPConfigMaps(mgmtOC, hcpNamespace, "VersionTLS13", "Modern") - for _, t := range guestTargets { - if t.deploymentName == "" || t.tlsMinVersionEnvVar == "" { - continue - } + for _, t := range guestEnvVars { g.By(fmt.Sprintf("verifying %s in %s/%s reflects Modern profile", t.tlsMinVersionEnvVar, t.namespace, t.deploymentName)) deployment, err := oc.AdminKubeClient().AppsV1().Deployments(t.namespace).Get( @@ -596,10 +422,7 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Disru tlsShouldWork := &tls.Config{MinVersion: tls.VersionTLS13, MaxVersion: tls.VersionTLS13, InsecureSkipVerify: true} tlsShouldNotWork := &tls.Config{MinVersion: tls.VersionTLS12, MaxVersion: tls.VersionTLS12, InsecureSkipVerify: true} - for _, t := range guestTargets { - if t.serviceName == "" || t.servicePort == "" { - continue - } + for _, t := range guestSvcs { g.By(fmt.Sprintf("wire-level TLS check: svc/%s in %s (expecting Modern = TLS 1.3 only)", t.serviceName, t.namespace)) err = forwardPortAndExecute(t.serviceName, t.namespace, t.servicePort, func(localPort int) error { return checkTLSConnection(localPort, tlsShouldWork, tlsShouldNotWork, t) }) @@ -669,10 +492,7 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Disru waitForAllOperatorsAfterTLSChange(oc, configChangeCtx, "Modern") // 5. Verify env vars reflect Modern profile (VersionTLS13). - for _, t := range targets { - if t.deploymentName == "" || t.tlsMinVersionEnvVar == "" { - continue - } + for _, t := range deploymentEnvVarTargets { g.By(fmt.Sprintf("verifying %s in %s/%s reflects Modern profile", t.tlsMinVersionEnvVar, t.namespace, t.deploymentName)) deployment, err := oc.AdminKubeClient().AppsV1().Deployments(t.namespace).Get( @@ -713,10 +533,7 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Disru tlsShouldWork := &tls.Config{MinVersion: tls.VersionTLS13, MaxVersion: tls.VersionTLS13, InsecureSkipVerify: true} tlsShouldNotWork := &tls.Config{MinVersion: tls.VersionTLS12, MaxVersion: tls.VersionTLS12, InsecureSkipVerify: true} - for _, t := range targets { - if t.serviceName == "" || t.servicePort == "" { - continue - } + for _, t := range serviceTargets { g.By(fmt.Sprintf("wire-level TLS check: svc/%s in %s (expecting Modern = TLS 1.3 only)", t.serviceName, t.namespace)) err = forwardPortAndExecute(t.serviceName, t.namespace, t.servicePort, @@ -760,6 +577,7 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Disru } if isHyperShiftCluster { + setupHyperShiftManagement() // ── HyperShift flow: patch HostedCluster with Custom TLS ── customPatch := fmt.Sprintf( `{"spec":{"configuration":{"apiServer":{"tlsSecurityProfile":{"type":"Custom","custom":{"ciphers":["%s"],"minTLSVersion":"VersionTLS12"}}}}}}`, @@ -771,32 +589,27 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Disru e2e.Logf("DeferCleanup: restoring HostedCluster TLS profile to default") setTLSProfileOnHyperShift(mgmtOC, hostedClusterName, hostedClusterNS, resetPatch) waitForHCPPods(mgmtOC, hcpNamespace, 8*time.Minute) - waitForGuestOperatorsAfterTLSChange(oc, cleanupCtx, "restore") + waitForGuestOperatorsAfterTLSChange(oc, cleanupCtx, "restore", guestRollouts) e2e.Logf("DeferCleanup: HostedCluster TLS profile restored") }) - guestTargets := guestSideTargets() - g.By("patching HostedCluster with Custom TLS profile") setTLSProfileOnHyperShift(mgmtOC, hostedClusterName, hostedClusterNS, customPatch) e2e.Logf("HostedCluster TLS profile patched to Custom (minTLSVersion=TLS12, ciphers=%d)", len(customCiphers)) g.By("waiting for HCP pods and guest operators to stabilize") waitForHCPPods(mgmtOC, hcpNamespace, 8*time.Minute) - waitForGuestOperatorsAfterTLSChange(oc, configChangeCtx, "Custom") + waitForGuestOperatorsAfterTLSChange(oc, configChangeCtx, "Custom", guestRollouts) g.By("verifying guest-side ObservedConfig reflects Custom profile") - verifyObservedConfigForTargets(oc, configChangeCtx, "VersionTLS12", "Custom", guestTargets) + verifyObservedConfigForTargets(oc, configChangeCtx, "VersionTLS12", "Custom", guestObservedCfg) g.By("verifying guest-side ConfigMaps reflect Custom profile") - verifyConfigMapsForTargets(oc, configChangeCtx, "VersionTLS12", "Custom", guestTargets) + verifyConfigMapsForTargets(oc, configChangeCtx, "VersionTLS12", "Custom", guestCMs) g.By("verifying HCP ConfigMaps reflect Custom profile") verifyHCPConfigMaps(mgmtOC, hcpNamespace, "VersionTLS12", "Custom") g.By("verifying wire-level TLS for Custom profile (TLS 1.2) on guest targets") - for _, t := range guestTargets { - if t.serviceName == "" || t.servicePort == "" { - continue - } + for _, t := range guestSvcs { shouldWork := &tls.Config{InsecureSkipVerify: true, MinVersion: tls.VersionTLS12} shouldNotWork := &tls.Config{InsecureSkipVerify: true, MinVersion: tls.VersionTLS10, MaxVersion: tls.VersionTLS11} err := forwardPortAndExecute(t.serviceName, t.namespace, t.servicePort, func(localPort int) error { @@ -876,10 +689,7 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Disru // 6. Verify ConfigMaps reflect Custom profile (VersionTLS12). g.By("verifying ConfigMaps reflect Custom profile (VersionTLS12)") - for _, t := range targets { - if t.configMapName == "" { - continue - } + for _, t := range configMapTargets { cm, err := oc.AdminKubeClient().CoreV1().ConfigMaps(t.configMapNamespace).Get(configChangeCtx, t.configMapName, metav1.GetOptions{}) if err != nil { e2e.Logf("SKIP: ConfigMap %s/%s not found: %v", t.configMapNamespace, t.configMapName, err) @@ -904,10 +714,7 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Disru // 7. Wire-level TLS verification for Custom profile. // Custom profile with TLS 1.2 should accept TLS 1.2 and reject TLS 1.1. g.By("verifying wire-level TLS for Custom profile (TLS 1.2)") - for _, t := range targets { - if t.serviceName == "" || t.servicePort == "" { - continue - } + for _, t := range serviceTargets { g.By(fmt.Sprintf("wire-level TLS check: svc/%s in %s (expecting Custom = TLS 1.2+)", t.serviceName, t.namespace)) @@ -942,7 +749,7 @@ var _ = g.Describe("[sig-api-machinery][Feature:TLSObservedConfig][Serial][Disru // This validates that the config observer controller (from library-go) is // correctly watching the APIServer resource and writing the TLS config // into the operator's ObservedConfig. -func testObservedConfig(oc *exutil.CLI, ctx context.Context, t tlsTarget) { +func testObservedConfig(oc *exutil.CLI, ctx context.Context, t observedConfigTarget) { g.By(fmt.Sprintf("getting operator config %s/%s via dynamic client", t.operatorConfigGVR.Resource, t.operatorConfigName)) @@ -1065,7 +872,7 @@ func waitForAnnotation(oc *exutil.CLI, ctx context.Context, namespace, name, ann // into the operator's ConfigMap via the config.openshift.io/inject-tls annotation. // This validates that CVO is reading the APIServer TLS profile and injecting // the minTLSVersion and cipherSuites into the ConfigMap's servingInfo section. -func testConfigMapTLSInjection(oc *exutil.CLI, ctx context.Context, t tlsTarget) { +func testConfigMapTLSInjection(oc *exutil.CLI, ctx context.Context, t configMapTarget) { validateNamespace(oc, ctx, t.configMapNamespace) cm := getConfigMap(oc, ctx, t.configMapNamespace, t.configMapName) @@ -1139,7 +946,7 @@ func testConfigMapTLSInjection(oc *exutil.CLI, ctx context.Context, t tlsTarget) // testAnnotationRestorationAfterDeletion verifies that if the inject-tls annotation // is deleted from the ConfigMap, the operator restores it. -func testAnnotationRestorationAfterDeletion(oc *exutil.CLI, ctx context.Context, t tlsTarget) { +func testAnnotationRestorationAfterDeletion(oc *exutil.CLI, ctx context.Context, t configMapTarget) { validateNamespace(oc, ctx, t.configMapNamespace) // Get the original ConfigMap and verify annotation exists. @@ -1159,7 +966,7 @@ func testAnnotationRestorationAfterDeletion(oc *exutil.CLI, ctx context.Context, // testAnnotationRestorationWhenFalse verifies that if the inject-tls annotation // is set to "false", the operator restores it to "true". -func testAnnotationRestorationWhenFalse(oc *exutil.CLI, ctx context.Context, t tlsTarget) { +func testAnnotationRestorationWhenFalse(oc *exutil.CLI, ctx context.Context, t configMapTarget) { validateNamespace(oc, ctx, t.configMapNamespace) // Get the original ConfigMap. @@ -1179,7 +986,7 @@ func testAnnotationRestorationWhenFalse(oc *exutil.CLI, ctx context.Context, t t // testServingInfoRestorationAfterRemoval verifies that if the servingInfo section // is removed from the ConfigMap, the operator restores it with correct TLS settings. -func testServingInfoRestorationAfterRemoval(oc *exutil.CLI, ctx context.Context, t tlsTarget) { +func testServingInfoRestorationAfterRemoval(oc *exutil.CLI, ctx context.Context, t configMapTarget) { validateNamespace(oc, ctx, t.configMapNamespace) // Get the original ConfigMap and verify servingInfo exists. @@ -1265,7 +1072,7 @@ func testServingInfoRestorationAfterRemoval(oc *exutil.CLI, ctx context.Context, // testServingInfoRestorationAfterModification verifies that if the servingInfo // minTLSVersion is modified to an incorrect value, the operator restores it. -func testServingInfoRestorationAfterModification(oc *exutil.CLI, ctx context.Context, t tlsTarget) { +func testServingInfoRestorationAfterModification(oc *exutil.CLI, ctx context.Context, t configMapTarget) { validateNamespace(oc, ctx, t.configMapNamespace) // Get the expected TLS version from the cluster profile. @@ -1335,7 +1142,7 @@ func testServingInfoRestorationAfterModification(oc *exutil.CLI, ctx context.Con // testDeploymentTLSEnvVars verifies that the deployment in the given namespace // has TLS environment variables that match the expected TLS profile. -func testDeploymentTLSEnvVars(oc *exutil.CLI, ctx context.Context, t tlsTarget) { +func testDeploymentTLSEnvVars(oc *exutil.CLI, ctx context.Context, t deploymentEnvVarTarget) { g.By("getting cluster APIServer TLS profile") expectedMinVersion := getExpectedMinTLSVersion(oc, ctx) e2e.Logf("Expected minTLSVersion from cluster profile: %s", expectedMinVersion) @@ -1392,7 +1199,7 @@ func testDeploymentTLSEnvVars(oc *exutil.CLI, ctx context.Context, t tlsTarget) // testWireLevelTLS verifies that the service endpoint in the given namespace // enforces the TLS version from the cluster APIServer profile using // oc port-forward for connectivity. -func testWireLevelTLS(oc *exutil.CLI, ctx context.Context, t tlsTarget) { +func testWireLevelTLS(oc *exutil.CLI, ctx context.Context, t serviceTarget) { g.By("getting cluster APIServer TLS profile") config, err := oc.AdminConfigClient().ConfigV1().APIServers().Get(ctx, "cluster", metav1.GetOptions{}) o.Expect(err).NotTo(o.HaveOccurred()) @@ -1451,17 +1258,14 @@ func testWireLevelTLS(oc *exutil.CLI, ctx context.Context, t tlsTarget) { // config has its ObservedConfig servingInfo.minTLSVersion matching the // expected version after a profile switch. func verifyObservedConfigAfterSwitch(oc *exutil.CLI, ctx context.Context, expectedVersion, profileLabel string) { - verifyObservedConfigForTargets(oc, ctx, expectedVersion, profileLabel, targets) + verifyObservedConfigForTargets(oc, ctx, expectedVersion, profileLabel, observedConfigTargets) } // verifyObservedConfigForTargets checks a specific list of targets for // ObservedConfig correctness after a TLS profile switch. -func verifyObservedConfigForTargets(oc *exutil.CLI, ctx context.Context, expectedVersion, profileLabel string, targetList []tlsTarget) { +func verifyObservedConfigForTargets(oc *exutil.CLI, ctx context.Context, expectedVersion, profileLabel string, targetList []observedConfigTarget) { dynClient := oc.AdminDynamicClient() for _, t := range targetList { - if t.operatorConfigGVR.Resource == "" || t.operatorConfigName == "" { - continue - } resource, err := dynClient.Resource(t.operatorConfigGVR).Get(ctx, t.operatorConfigName, metav1.GetOptions{}) o.Expect(err).NotTo(o.HaveOccurred(), fmt.Sprintf("failed to get operator config %s/%s after %s switch", @@ -1489,16 +1293,13 @@ func verifyObservedConfigForTargets(oc *exutil.CLI, ctx context.Context, expecte // verifyConfigMapsAfterSwitch checks that every target with a ConfigMap has // the expected minTLSVersion in its servingInfo after a profile switch. func verifyConfigMapsAfterSwitch(oc *exutil.CLI, ctx context.Context, expectedVersion, profileLabel string) { - verifyConfigMapsForTargets(oc, ctx, expectedVersion, profileLabel, targets) + verifyConfigMapsForTargets(oc, ctx, expectedVersion, profileLabel, configMapTargets) } // verifyConfigMapsForTargets checks a specific list of targets for // ConfigMap TLS injection correctness after a TLS profile switch. -func verifyConfigMapsForTargets(oc *exutil.CLI, ctx context.Context, expectedVersion, profileLabel string, targetList []tlsTarget) { +func verifyConfigMapsForTargets(oc *exutil.CLI, ctx context.Context, expectedVersion, profileLabel string, targetList []configMapTarget) { for _, t := range targetList { - if t.configMapName == "" { - continue - } cm, err := oc.AdminKubeClient().CoreV1().ConfigMaps(t.configMapNamespace).Get(ctx, t.configMapName, metav1.GetOptions{}) if err != nil { e2e.Logf("SKIP: ConfigMap %s/%s not found: %v", t.configMapNamespace, t.configMapName, err) @@ -1515,22 +1316,6 @@ func verifyConfigMapsForTargets(oc *exutil.CLI, ctx context.Context, expectedVer } } -// targetClusterOperators returns the deduplicated list of ClusterOperator -// names from the global targets list. Used when the config-change test needs -// to wait for all target operators to stabilize. -func targetClusterOperators() []string { - seen := map[string]bool{} - var result []string - for _, t := range targets { - if t.clusterOperatorName == "" || seen[t.clusterOperatorName] { - continue - } - seen[t.clusterOperatorName] = true - result = append(result, t.clusterOperatorName) - } - return result -} - // getExpectedMinTLSVersion returns the expected minTLSVersion string // (e.g. "VersionTLS12", "VersionTLS13") based on the cluster APIServer profile. func getExpectedMinTLSVersion(oc *exutil.CLI, ctx context.Context) string { @@ -1666,7 +1451,7 @@ func tlsVersionName(version uint16) string { // checkTLSConnection verifies that a local-forwarded port accepts the expected // TLS version and rejects the one that should not work. // Tests both IPv4 (127.0.0.1) and IPv6 ([::1]) localhost addresses when available. -func checkTLSConnection(localPort int, shouldWork, shouldNotWork *tls.Config, t tlsTarget) error { +func checkTLSConnection(localPort int, shouldWork, shouldNotWork *tls.Config, t serviceTarget) error { // Test both IPv4 and IPv6 localhost addresses. // On IPv6 clusters, we want to verify TLS works on both address families. hosts := []string{ @@ -1858,15 +1643,12 @@ func waitForAllOperatorsAfterTLSChange(oc *exutil.CLI, ctx context.Context, prof time.Sleep(30 * time.Second) e2e.Logf("Waiting for all ClusterOperators to stabilize after %s profile change", profileLabel) - for _, co := range targetClusterOperators() { + for _, co := range clusterOperatorNames { e2e.Logf("Waiting for ClusterOperator %s to stabilize after %s switch", co, profileLabel) waitForClusterOperatorStable(oc, ctx, co) } - for _, t := range targets { - if t.deploymentName == "" { - continue - } + for _, t := range deploymentRolloutTargets { e2e.Logf("Waiting for deployment %s/%s to complete rollout after %s switch", t.namespace, t.deploymentName, profileLabel) deployment, err := oc.AdminKubeClient().AppsV1().Deployments(t.namespace).Get(ctx, t.deploymentName, metav1.GetOptions{}) o.Expect(err).NotTo(o.HaveOccurred()) @@ -1881,33 +1663,6 @@ func waitForAllOperatorsAfterTLSChange(oc *exutil.CLI, ctx context.Context, prof // ─── HyperShift helpers ──────────────────────────────────────────────────── -// guestSideTargets returns the targets that run on the guest cluster (not the -// management cluster control plane). Used on HyperShift to skip CP targets. -func guestSideTargets() []tlsTarget { - var result []tlsTarget - for _, t := range targets { - if !t.controlPlane { - result = append(result, t) - } - } - return result -} - -// guestSideClusterOperators returns the deduplicated ClusterOperator names -// from guest-side targets only. -func guestSideClusterOperators() []string { - seen := map[string]bool{} - var result []string - for _, t := range guestSideTargets() { - if t.clusterOperatorName == "" || seen[t.clusterOperatorName] { - continue - } - seen[t.clusterOperatorName] = true - result = append(result, t.clusterOperatorName) - } - return result -} - // discoverHostedCluster finds the HostedCluster name and namespace on the // management cluster that corresponds to the given hosted control plane // namespace (hcpNS). The HCP namespace follows the convention {hcNS}-{hcName}. @@ -2003,17 +1758,14 @@ func waitForHCPAppReady(mgmtCLI *exutil.CLI, appLabel, hcpNS string, timeout tim // waitForGuestOperatorsAfterTLSChange waits for guest-side ClusterOperators // and Deployments to stabilize after a TLS profile change on HyperShift. -func waitForGuestOperatorsAfterTLSChange(oc *exutil.CLI, ctx context.Context, profileLabel string) { +func waitForGuestOperatorsAfterTLSChange(oc *exutil.CLI, ctx context.Context, profileLabel string, rollouts []deploymentRolloutTarget) { e2e.Logf("Waiting for guest-side ClusterOperators to stabilize after %s profile change", profileLabel) - for _, co := range guestSideClusterOperators() { + for _, co := range clusterOperatorNames { e2e.Logf("Waiting for ClusterOperator %s to stabilize after %s switch", co, profileLabel) waitForClusterOperatorStable(oc, ctx, co) } - for _, t := range guestSideTargets() { - if t.deploymentName == "" { - continue - } + for _, t := range rollouts { e2e.Logf("Waiting for deployment %s/%s to complete rollout after %s switch", t.namespace, t.deploymentName, profileLabel) deployment, err := oc.AdminKubeClient().AppsV1().Deployments(t.namespace).Get(ctx, t.deploymentName, metav1.GetOptions{}) o.Expect(err).NotTo(o.HaveOccurred())