From 14cee44d6e4a8e07d38672fd29c630df04c00388 Mon Sep 17 00:00:00 2001 From: enxebre Date: Wed, 27 May 2026 14:19:34 +0200 Subject: [PATCH] fix(hcco): OCPBUGS-86573: [release-4.20] add CP pull-secret watches for in-place propagation Add watches on the pull-secret in the control plane namespace so that changes are propagated in-place without requiring a rollout. This includes setting up a global pull-secret controller and ensuring resources are updated when the pull-secret changes. Co-Authored-By: Claude Opus 4.6 --- api/hypershift/v1beta1/hostedcluster_types.go | 9 +- .../AAA_ungated.yaml | 9 +- .../AutoNodeKarpenter.yaml | 9 +- .../ClusterVersionOperatorConfiguration.yaml | 9 +- .../DynamicResourceAllocation.yaml | 9 +- .../ExternalOIDC.yaml | 9 +- ...ernalOIDCWithUIDAndExtraClaimMappings.yaml | 9 +- .../ImageStreamImportMode.yaml | 9 +- .../KMSEncryptionProvider.yaml | 9 +- .../NetworkDiagnosticsConfig.yaml | 9 +- .../OpenStack.yaml | 9 +- .../hostedclusters-CustomNoUpgrade.crd.yaml | 9 +- .../hostedclusters-Default.crd.yaml | 9 +- ...stedclusters-TechPreviewNoUpgrade.crd.yaml | 9 +- .../controllers/globalps/setup.go | 40 ++++++--- .../controllers/globalps/setup_test.go | 89 +++++++++++++++++++ .../controllers/resources/resources.go | 16 ++++ .../controllers/resources/resources_test.go | 39 ++++++++ docs/content/reference/api.md | 18 ++-- .../hypershift/v1beta1/hostedcluster_types.go | 9 +- 20 files changed, 257 insertions(+), 80 deletions(-) create mode 100644 control-plane-operator/hostedclusterconfigoperator/controllers/globalps/setup_test.go diff --git a/api/hypershift/v1beta1/hostedcluster_types.go b/api/hypershift/v1beta1/hostedcluster_types.go index 41c831dcbbd..dfaf052fd5f 100644 --- a/api/hypershift/v1beta1/hostedcluster_types.go +++ b/api/hypershift/v1beta1/hostedcluster_types.go @@ -611,10 +611,11 @@ type HostedClusterSpec struct { // pullSecret is a local reference to a Secret that must have a ".dockerconfigjson" key whose content must be a valid Openshift pull secret JSON. // If the reference is set but none of the above requirements are met, the HostedCluster will enter a degraded state. // TODO(alberto): Signal this in a condition. - // This pull secret will be part of every payload generated by the controllers for any NodePool of the HostedCluster - // and it will be injected into the container runtime of all NodePools. - // Changing this value will trigger a rollout for all existing NodePools in the cluster. - // Changing the content of the secret inplace will not trigger a rollout and might result in unpredictable behaviour. + // This pull secret is included in NodePool ignition/bootstrap payloads and applied to the container runtime when nodes provision. + // Changing this value will trigger a rollout for all existing NodePools in the cluster (for both replace and inplace upgrade types). + // Updating the referenced Secret's data in place (without changing this reference) does not trigger that rollout. + // In AWS and Azure NodePools using the Replace upgrade strategy, the Secret's data in place changes + // will still propagate the updated credentials down to the guest cluster and kubelet config. // +required // +rollout // TODO(alberto): have our own local reference type to include our opinions and avoid transparent changes. diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AAA_ungated.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AAA_ungated.yaml index 0a36b581089..71a84de7b23 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AAA_ungated.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AAA_ungated.yaml @@ -4644,10 +4644,11 @@ spec: description: |- pullSecret is a local reference to a Secret that must have a ".dockerconfigjson" key whose content must be a valid Openshift pull secret JSON. If the reference is set but none of the above requirements are met, the HostedCluster will enter a degraded state. - This pull secret will be part of every payload generated by the controllers for any NodePool of the HostedCluster - and it will be injected into the container runtime of all NodePools. - Changing this value will trigger a rollout for all existing NodePools in the cluster. - Changing the content of the secret inplace will not trigger a rollout and might result in unpredictable behaviour. + This pull secret is included in NodePool ignition/bootstrap payloads and applied to the container runtime when nodes provision. + Changing this value will trigger a rollout for all existing NodePools in the cluster (for both replace and inplace upgrade types). + Updating the referenced Secret's data in place (without changing this reference) does not trigger that rollout. + In AWS and Azure NodePools using the Replace upgrade strategy, the Secret's data in place changes + will still propagate the updated credentials down to the guest cluster and kubelet config. properties: name: default: "" diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AutoNodeKarpenter.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AutoNodeKarpenter.yaml index 8b9bd499806..dd24338fb50 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AutoNodeKarpenter.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AutoNodeKarpenter.yaml @@ -4674,10 +4674,11 @@ spec: description: |- pullSecret is a local reference to a Secret that must have a ".dockerconfigjson" key whose content must be a valid Openshift pull secret JSON. If the reference is set but none of the above requirements are met, the HostedCluster will enter a degraded state. - This pull secret will be part of every payload generated by the controllers for any NodePool of the HostedCluster - and it will be injected into the container runtime of all NodePools. - Changing this value will trigger a rollout for all existing NodePools in the cluster. - Changing the content of the secret inplace will not trigger a rollout and might result in unpredictable behaviour. + This pull secret is included in NodePool ignition/bootstrap payloads and applied to the container runtime when nodes provision. + Changing this value will trigger a rollout for all existing NodePools in the cluster (for both replace and inplace upgrade types). + Updating the referenced Secret's data in place (without changing this reference) does not trigger that rollout. + In AWS and Azure NodePools using the Replace upgrade strategy, the Secret's data in place changes + will still propagate the updated credentials down to the guest cluster and kubelet config. properties: name: default: "" diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterVersionOperatorConfiguration.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterVersionOperatorConfiguration.yaml index a0a44d4d269..3ce3ec6bb6c 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterVersionOperatorConfiguration.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterVersionOperatorConfiguration.yaml @@ -4647,10 +4647,11 @@ spec: description: |- pullSecret is a local reference to a Secret that must have a ".dockerconfigjson" key whose content must be a valid Openshift pull secret JSON. If the reference is set but none of the above requirements are met, the HostedCluster will enter a degraded state. - This pull secret will be part of every payload generated by the controllers for any NodePool of the HostedCluster - and it will be injected into the container runtime of all NodePools. - Changing this value will trigger a rollout for all existing NodePools in the cluster. - Changing the content of the secret inplace will not trigger a rollout and might result in unpredictable behaviour. + This pull secret is included in NodePool ignition/bootstrap payloads and applied to the container runtime when nodes provision. + Changing this value will trigger a rollout for all existing NodePools in the cluster (for both replace and inplace upgrade types). + Updating the referenced Secret's data in place (without changing this reference) does not trigger that rollout. + In AWS and Azure NodePools using the Replace upgrade strategy, the Secret's data in place changes + will still propagate the updated credentials down to the guest cluster and kubelet config. properties: name: default: "" diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/DynamicResourceAllocation.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/DynamicResourceAllocation.yaml index 28b8d3b4f70..55272732075 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/DynamicResourceAllocation.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/DynamicResourceAllocation.yaml @@ -4648,10 +4648,11 @@ spec: description: |- pullSecret is a local reference to a Secret that must have a ".dockerconfigjson" key whose content must be a valid Openshift pull secret JSON. If the reference is set but none of the above requirements are met, the HostedCluster will enter a degraded state. - This pull secret will be part of every payload generated by the controllers for any NodePool of the HostedCluster - and it will be injected into the container runtime of all NodePools. - Changing this value will trigger a rollout for all existing NodePools in the cluster. - Changing the content of the secret inplace will not trigger a rollout and might result in unpredictable behaviour. + This pull secret is included in NodePool ignition/bootstrap payloads and applied to the container runtime when nodes provision. + Changing this value will trigger a rollout for all existing NodePools in the cluster (for both replace and inplace upgrade types). + Updating the referenced Secret's data in place (without changing this reference) does not trigger that rollout. + In AWS and Azure NodePools using the Replace upgrade strategy, the Secret's data in place changes + will still propagate the updated credentials down to the guest cluster and kubelet config. properties: name: default: "" diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDC.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDC.yaml index 763bb647826..7f1d3862b72 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDC.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDC.yaml @@ -4963,10 +4963,11 @@ spec: description: |- pullSecret is a local reference to a Secret that must have a ".dockerconfigjson" key whose content must be a valid Openshift pull secret JSON. If the reference is set but none of the above requirements are met, the HostedCluster will enter a degraded state. - This pull secret will be part of every payload generated by the controllers for any NodePool of the HostedCluster - and it will be injected into the container runtime of all NodePools. - Changing this value will trigger a rollout for all existing NodePools in the cluster. - Changing the content of the secret inplace will not trigger a rollout and might result in unpredictable behaviour. + This pull secret is included in NodePool ignition/bootstrap payloads and applied to the container runtime when nodes provision. + Changing this value will trigger a rollout for all existing NodePools in the cluster (for both replace and inplace upgrade types). + Updating the referenced Secret's data in place (without changing this reference) does not trigger that rollout. + In AWS and Azure NodePools using the Replace upgrade strategy, the Secret's data in place changes + will still propagate the updated credentials down to the guest cluster and kubelet config. properties: name: default: "" diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUIDAndExtraClaimMappings.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUIDAndExtraClaimMappings.yaml index 1acf0fd2990..c57abe7c851 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUIDAndExtraClaimMappings.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUIDAndExtraClaimMappings.yaml @@ -5117,10 +5117,11 @@ spec: description: |- pullSecret is a local reference to a Secret that must have a ".dockerconfigjson" key whose content must be a valid Openshift pull secret JSON. If the reference is set but none of the above requirements are met, the HostedCluster will enter a degraded state. - This pull secret will be part of every payload generated by the controllers for any NodePool of the HostedCluster - and it will be injected into the container runtime of all NodePools. - Changing this value will trigger a rollout for all existing NodePools in the cluster. - Changing the content of the secret inplace will not trigger a rollout and might result in unpredictable behaviour. + This pull secret is included in NodePool ignition/bootstrap payloads and applied to the container runtime when nodes provision. + Changing this value will trigger a rollout for all existing NodePools in the cluster (for both replace and inplace upgrade types). + Updating the referenced Secret's data in place (without changing this reference) does not trigger that rollout. + In AWS and Azure NodePools using the Replace upgrade strategy, the Secret's data in place changes + will still propagate the updated credentials down to the guest cluster and kubelet config. properties: name: default: "" diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ImageStreamImportMode.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ImageStreamImportMode.yaml index 4517d9260dc..3b00295bc63 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ImageStreamImportMode.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ImageStreamImportMode.yaml @@ -4645,10 +4645,11 @@ spec: description: |- pullSecret is a local reference to a Secret that must have a ".dockerconfigjson" key whose content must be a valid Openshift pull secret JSON. If the reference is set but none of the above requirements are met, the HostedCluster will enter a degraded state. - This pull secret will be part of every payload generated by the controllers for any NodePool of the HostedCluster - and it will be injected into the container runtime of all NodePools. - Changing this value will trigger a rollout for all existing NodePools in the cluster. - Changing the content of the secret inplace will not trigger a rollout and might result in unpredictable behaviour. + This pull secret is included in NodePool ignition/bootstrap payloads and applied to the container runtime when nodes provision. + Changing this value will trigger a rollout for all existing NodePools in the cluster (for both replace and inplace upgrade types). + Updating the referenced Secret's data in place (without changing this reference) does not trigger that rollout. + In AWS and Azure NodePools using the Replace upgrade strategy, the Secret's data in place changes + will still propagate the updated credentials down to the guest cluster and kubelet config. properties: name: default: "" diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/KMSEncryptionProvider.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/KMSEncryptionProvider.yaml index eb7bd1108b6..fc84f57bbab 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/KMSEncryptionProvider.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/KMSEncryptionProvider.yaml @@ -4703,10 +4703,11 @@ spec: description: |- pullSecret is a local reference to a Secret that must have a ".dockerconfigjson" key whose content must be a valid Openshift pull secret JSON. If the reference is set but none of the above requirements are met, the HostedCluster will enter a degraded state. - This pull secret will be part of every payload generated by the controllers for any NodePool of the HostedCluster - and it will be injected into the container runtime of all NodePools. - Changing this value will trigger a rollout for all existing NodePools in the cluster. - Changing the content of the secret inplace will not trigger a rollout and might result in unpredictable behaviour. + This pull secret is included in NodePool ignition/bootstrap payloads and applied to the container runtime when nodes provision. + Changing this value will trigger a rollout for all existing NodePools in the cluster (for both replace and inplace upgrade types). + Updating the referenced Secret's data in place (without changing this reference) does not trigger that rollout. + In AWS and Azure NodePools using the Replace upgrade strategy, the Secret's data in place changes + will still propagate the updated credentials down to the guest cluster and kubelet config. properties: name: default: "" diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/NetworkDiagnosticsConfig.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/NetworkDiagnosticsConfig.yaml index da6a87fd9ef..10544990643 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/NetworkDiagnosticsConfig.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/NetworkDiagnosticsConfig.yaml @@ -4779,10 +4779,11 @@ spec: description: |- pullSecret is a local reference to a Secret that must have a ".dockerconfigjson" key whose content must be a valid Openshift pull secret JSON. If the reference is set but none of the above requirements are met, the HostedCluster will enter a degraded state. - This pull secret will be part of every payload generated by the controllers for any NodePool of the HostedCluster - and it will be injected into the container runtime of all NodePools. - Changing this value will trigger a rollout for all existing NodePools in the cluster. - Changing the content of the secret inplace will not trigger a rollout and might result in unpredictable behaviour. + This pull secret is included in NodePool ignition/bootstrap payloads and applied to the container runtime when nodes provision. + Changing this value will trigger a rollout for all existing NodePools in the cluster (for both replace and inplace upgrade types). + Updating the referenced Secret's data in place (without changing this reference) does not trigger that rollout. + In AWS and Azure NodePools using the Replace upgrade strategy, the Secret's data in place changes + will still propagate the updated credentials down to the guest cluster and kubelet config. properties: name: default: "" diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/OpenStack.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/OpenStack.yaml index b5ac63dfc38..c88fceca3bd 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/OpenStack.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/OpenStack.yaml @@ -5177,10 +5177,11 @@ spec: description: |- pullSecret is a local reference to a Secret that must have a ".dockerconfigjson" key whose content must be a valid Openshift pull secret JSON. If the reference is set but none of the above requirements are met, the HostedCluster will enter a degraded state. - This pull secret will be part of every payload generated by the controllers for any NodePool of the HostedCluster - and it will be injected into the container runtime of all NodePools. - Changing this value will trigger a rollout for all existing NodePools in the cluster. - Changing the content of the secret inplace will not trigger a rollout and might result in unpredictable behaviour. + This pull secret is included in NodePool ignition/bootstrap payloads and applied to the container runtime when nodes provision. + Changing this value will trigger a rollout for all existing NodePools in the cluster (for both replace and inplace upgrade types). + Updating the referenced Secret's data in place (without changing this reference) does not trigger that rollout. + In AWS and Azure NodePools using the Replace upgrade strategy, the Secret's data in place changes + will still propagate the updated credentials down to the guest cluster and kubelet config. properties: name: default: "" diff --git a/cmd/install/assets/hypershift-operator/zz_generated.crd-manifests/hostedclusters-CustomNoUpgrade.crd.yaml b/cmd/install/assets/hypershift-operator/zz_generated.crd-manifests/hostedclusters-CustomNoUpgrade.crd.yaml index d5d23bf22db..e463b835fac 100644 --- a/cmd/install/assets/hypershift-operator/zz_generated.crd-manifests/hostedclusters-CustomNoUpgrade.crd.yaml +++ b/cmd/install/assets/hypershift-operator/zz_generated.crd-manifests/hostedclusters-CustomNoUpgrade.crd.yaml @@ -6004,10 +6004,11 @@ spec: description: |- pullSecret is a local reference to a Secret that must have a ".dockerconfigjson" key whose content must be a valid Openshift pull secret JSON. If the reference is set but none of the above requirements are met, the HostedCluster will enter a degraded state. - This pull secret will be part of every payload generated by the controllers for any NodePool of the HostedCluster - and it will be injected into the container runtime of all NodePools. - Changing this value will trigger a rollout for all existing NodePools in the cluster. - Changing the content of the secret inplace will not trigger a rollout and might result in unpredictable behaviour. + This pull secret is included in NodePool ignition/bootstrap payloads and applied to the container runtime when nodes provision. + Changing this value will trigger a rollout for all existing NodePools in the cluster (for both replace and inplace upgrade types). + Updating the referenced Secret's data in place (without changing this reference) does not trigger that rollout. + In AWS and Azure NodePools using the Replace upgrade strategy, the Secret's data in place changes + will still propagate the updated credentials down to the guest cluster and kubelet config. properties: name: default: "" diff --git a/cmd/install/assets/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Default.crd.yaml b/cmd/install/assets/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Default.crd.yaml index f0943ad4401..e1f6e073ecc 100644 --- a/cmd/install/assets/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Default.crd.yaml +++ b/cmd/install/assets/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Default.crd.yaml @@ -5306,10 +5306,11 @@ spec: description: |- pullSecret is a local reference to a Secret that must have a ".dockerconfigjson" key whose content must be a valid Openshift pull secret JSON. If the reference is set but none of the above requirements are met, the HostedCluster will enter a degraded state. - This pull secret will be part of every payload generated by the controllers for any NodePool of the HostedCluster - and it will be injected into the container runtime of all NodePools. - Changing this value will trigger a rollout for all existing NodePools in the cluster. - Changing the content of the secret inplace will not trigger a rollout and might result in unpredictable behaviour. + This pull secret is included in NodePool ignition/bootstrap payloads and applied to the container runtime when nodes provision. + Changing this value will trigger a rollout for all existing NodePools in the cluster (for both replace and inplace upgrade types). + Updating the referenced Secret's data in place (without changing this reference) does not trigger that rollout. + In AWS and Azure NodePools using the Replace upgrade strategy, the Secret's data in place changes + will still propagate the updated credentials down to the guest cluster and kubelet config. properties: name: default: "" diff --git a/cmd/install/assets/hypershift-operator/zz_generated.crd-manifests/hostedclusters-TechPreviewNoUpgrade.crd.yaml b/cmd/install/assets/hypershift-operator/zz_generated.crd-manifests/hostedclusters-TechPreviewNoUpgrade.crd.yaml index da8c72adfbf..4cb996802f5 100644 --- a/cmd/install/assets/hypershift-operator/zz_generated.crd-manifests/hostedclusters-TechPreviewNoUpgrade.crd.yaml +++ b/cmd/install/assets/hypershift-operator/zz_generated.crd-manifests/hostedclusters-TechPreviewNoUpgrade.crd.yaml @@ -5915,10 +5915,11 @@ spec: description: |- pullSecret is a local reference to a Secret that must have a ".dockerconfigjson" key whose content must be a valid Openshift pull secret JSON. If the reference is set but none of the above requirements are met, the HostedCluster will enter a degraded state. - This pull secret will be part of every payload generated by the controllers for any NodePool of the HostedCluster - and it will be injected into the container runtime of all NodePools. - Changing this value will trigger a rollout for all existing NodePools in the cluster. - Changing the content of the secret inplace will not trigger a rollout and might result in unpredictable behaviour. + This pull secret is included in NodePool ignition/bootstrap payloads and applied to the container runtime when nodes provision. + Changing this value will trigger a rollout for all existing NodePools in the cluster (for both replace and inplace upgrade types). + Updating the referenced Secret's data in place (without changing this reference) does not trigger that rollout. + In AWS and Azure NodePools using the Replace upgrade strategy, the Secret's data in place changes + will still propagate the updated credentials down to the guest cluster and kubelet config. properties: name: default: "" diff --git a/control-plane-operator/hostedclusterconfigoperator/controllers/globalps/setup.go b/control-plane-operator/hostedclusterconfigoperator/controllers/globalps/setup.go index 59a8b8fb213..e664aed6bc6 100644 --- a/control-plane-operator/hostedclusterconfigoperator/controllers/globalps/setup.go +++ b/control-plane-operator/hostedclusterconfigoperator/controllers/globalps/setup.go @@ -5,6 +5,7 @@ import ( "fmt" "os" + "github.com/openshift/hypershift/control-plane-operator/hostedclusterconfigoperator/controllers/resources/manifests" "github.com/openshift/hypershift/control-plane-operator/hostedclusterconfigoperator/operator" corev1 "k8s.io/api/core/v1" @@ -17,15 +18,12 @@ import ( "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/predicate" - crreconcile "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" ) func Setup(ctx context.Context, opts *operator.HostedClusterConfigOperatorConfig) error { - // Create a predicate for the pull-secret - secretPredicate := predicate.NewPredicateFuncs(func(o crclient.Object) bool { - return o.GetNamespace() == "kube-system" - }) + secretPredicate := predicate.NewPredicateFuncs(kubeSystemSecretPredicateFunc) // Create a cache for the kube-system namespace kubeSystemCache, err := cache.New(opts.Manager.GetConfig(), cache.Options{ @@ -131,23 +129,18 @@ func Setup(ctx context.Context, opts *operator.HostedClusterConfigOperatorConfig // Watch for nodes - when nodes are created, we need to reconcile global pull secret if err := c.Watch(&source.Informer{ Informer: nodeInformer, - Handler: handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, o crclient.Object) []crreconcile.Request { - // Trigger reconciliation for node creation using the node's name for better observability - // The reconciler ignores the NamespacedName but this helps with logging and debugging - return []crreconcile.Request{{NamespacedName: types.NamespacedName{Name: o.GetName(), Namespace: ""}}} + Handler: handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, o crclient.Object) []reconcile.Request { + return []reconcile.Request{{NamespacedName: types.NamespacedName{Name: o.GetName(), Namespace: ""}}} }), Predicates: []predicate.Predicate{ predicate.Funcs{ CreateFunc: func(e event.CreateEvent) bool { - // Only reconcile when new nodes are created return true }, UpdateFunc: func(e event.UpdateEvent) bool { - // Ignore node updates return false }, DeleteFunc: func(e event.DeleteEvent) bool { - // Ignore node deletions return false }, }, @@ -156,5 +149,28 @@ func Setup(ctx context.Context, opts *operator.HostedClusterConfigOperatorConfig return fmt.Errorf("failed to watch nodes: %w", err) } + // Watch the CP namespace pull-secret so in-place updates to HostedCluster.spec.pullSecret + // promptly reconcile kube-system/original-pull-secret (and global-pull-secret) in the guest. + cpPullSecret := manifests.PullSecret(opts.Namespace) + cpPullSecretPredicate := predicate.NewPredicateFuncs(namespacedNamePredicateFunc(cpPullSecret.Namespace, cpPullSecret.Name)) + cpEventHandler := handler.EnqueueRequestsFromMapFunc(staticReconcileMapper) + if err := c.Watch(source.Kind[crclient.Object](opts.CPCluster.GetCache(), &corev1.Secret{}, cpEventHandler, cpPullSecretPredicate)); err != nil { + return fmt.Errorf("failed to watch control plane pull secret: %w", err) + } + return nil } + +func kubeSystemSecretPredicateFunc(o crclient.Object) bool { + return o.GetNamespace() == "kube-system" +} + +func namespacedNamePredicateFunc(namespace, name string) func(crclient.Object) bool { + return func(o crclient.Object) bool { + return o.GetNamespace() == namespace && o.GetName() == name + } +} + +func staticReconcileMapper(_ context.Context, _ crclient.Object) []reconcile.Request { + return []reconcile.Request{{}} +} diff --git a/control-plane-operator/hostedclusterconfigoperator/controllers/globalps/setup_test.go b/control-plane-operator/hostedclusterconfigoperator/controllers/globalps/setup_test.go new file mode 100644 index 00000000000..b471cce2632 --- /dev/null +++ b/control-plane-operator/hostedclusterconfigoperator/controllers/globalps/setup_test.go @@ -0,0 +1,89 @@ +package globalps + +import ( + "context" + "testing" + + . "github.com/onsi/gomega" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func Test_kubeSystemSecretPredicateFunc(t *testing.T) { + tests := []struct { + name string + object *corev1.Secret + want bool + }{ + { + name: "When secret is in kube-system it should return true", + object: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Namespace: "kube-system", Name: "any-secret"}, + }, + want: true, + }, + { + name: "When secret is in a different namespace it should return false", + object: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Namespace: "openshift-config", Name: "pull-secret"}, + }, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + g.Expect(kubeSystemSecretPredicateFunc(tt.object)).To(Equal(tt.want)) + }) + } +} + +func Test_namespacedNamePredicateFunc(t *testing.T) { + predicate := namespacedNamePredicateFunc("my-hcp-namespace", "pull-secret") + + tests := []struct { + name string + object *corev1.Secret + want bool + }{ + { + name: "When namespace and name match it should return true", + object: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Namespace: "my-hcp-namespace", Name: "pull-secret"}, + }, + want: true, + }, + { + name: "When namespace differs it should return false", + object: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Namespace: "other-namespace", Name: "pull-secret"}, + }, + want: false, + }, + { + name: "When name differs it should return false", + object: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Namespace: "my-hcp-namespace", Name: "other-secret"}, + }, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + g.Expect(predicate(tt.object)).To(Equal(tt.want)) + }) + } +} + +func Test_staticReconcileMapper(t *testing.T) { + t.Run("When called it should return a single empty reconcile request", func(t *testing.T) { + g := NewWithT(t) + requests := staticReconcileMapper(context.Background(), &corev1.Secret{}) + g.Expect(requests).To(HaveLen(1)) + g.Expect(requests[0].NamespacedName.String()).To(Equal("/")) + }) +} diff --git a/control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources.go b/control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources.go index 24dc39ef2bf..415306f99c5 100644 --- a/control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources.go +++ b/control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources.go @@ -275,9 +275,25 @@ func Setup(ctx context.Context, opts *operator.HostedClusterConfigOperatorConfig return fmt.Errorf("failed to watch ConfigMap: %w", err) } + // Watch HostedControlPlane namespace pull-secret on the control plane cluster so guest pull secrets + // (openshift-config, openshift) reconcile promptly when the hypershift-operator + // syncs in-place updates from HostedCluster.spec.pullSecret. + // The globalps controller has its own CP pull-secret watch for kube-system/original-pull-secret. + cpPullSecret := manifests.PullSecret(opts.Namespace) + cpPullSecretPredicate := predicate.NewPredicateFuncs(namespacedNamePredicateFunc(cpPullSecret.Namespace, cpPullSecret.Name)) + if err := c.Watch(source.Kind[client.Object](opts.CPCluster.GetCache(), &corev1.Secret{}, eventHandler(), cpPullSecretPredicate)); err != nil { + return fmt.Errorf("failed to watch control plane pull secret: %w", err) + } + return nil } +func namespacedNamePredicateFunc(namespace, name string) func(client.Object) bool { + return func(o client.Object) bool { + return o.GetNamespace() == namespace && o.GetName() == name + } +} + func (r *reconciler) Reconcile(ctx context.Context, _ ctrl.Request) (ctrl.Result, error) { log := ctrl.LoggerFrom(ctx) log.Info("Reconciling") diff --git a/control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go b/control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go index 242a66f6421..26328f4b7b9 100644 --- a/control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go +++ b/control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources_test.go @@ -2183,3 +2183,42 @@ func TestReconcileAuthOIDC(t *testing.T) { }) } } + +func Test_namespacedNamePredicateFunc(t *testing.T) { + predicate := namespacedNamePredicateFunc("my-hcp-namespace", "pull-secret") + + tests := []struct { + name string + object client.Object + want bool + }{ + { + name: "When namespace and name match it should return true", + object: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Namespace: "my-hcp-namespace", Name: "pull-secret"}, + }, + want: true, + }, + { + name: "When namespace differs it should return false", + object: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Namespace: "other-namespace", Name: "pull-secret"}, + }, + want: false, + }, + { + name: "When name differs it should return false", + object: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Namespace: "my-hcp-namespace", Name: "other-secret"}, + }, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + g.Expect(predicate(tt.object)).To(Equal(tt.want)) + }) + } +} diff --git a/docs/content/reference/api.md b/docs/content/reference/api.md index 8fb0cfdb495..6d6652cdff5 100644 --- a/docs/content/reference/api.md +++ b/docs/content/reference/api.md @@ -438,10 +438,11 @@ Kubernetes core/v1.LocalObjectReference

pullSecret is a local reference to a Secret that must have a “.dockerconfigjson” key whose content must be a valid Openshift pull secret JSON. If the reference is set but none of the above requirements are met, the HostedCluster will enter a degraded state. TODO(alberto): Signal this in a condition. -This pull secret will be part of every payload generated by the controllers for any NodePool of the HostedCluster -and it will be injected into the container runtime of all NodePools. -Changing this value will trigger a rollout for all existing NodePools in the cluster. -Changing the content of the secret inplace will not trigger a rollout and might result in unpredictable behaviour. +This pull secret is included in NodePool ignition/bootstrap payloads and applied to the container runtime when nodes provision. +Changing this value will trigger a rollout for all existing NodePools in the cluster (for both replace and inplace upgrade types). +Updating the referenced Secret’s data in place (without changing this reference) does not trigger that rollout. +In AWS and Azure NodePools using the Replace upgrade strategy, the Secret’s data in place changes +will still propagate the updated credentials down to the guest cluster and kubelet config. TODO(alberto): have our own local reference type to include our opinions and avoid transparent changes.

@@ -5905,10 +5906,11 @@ Kubernetes core/v1.LocalObjectReference

pullSecret is a local reference to a Secret that must have a “.dockerconfigjson” key whose content must be a valid Openshift pull secret JSON. If the reference is set but none of the above requirements are met, the HostedCluster will enter a degraded state. TODO(alberto): Signal this in a condition. -This pull secret will be part of every payload generated by the controllers for any NodePool of the HostedCluster -and it will be injected into the container runtime of all NodePools. -Changing this value will trigger a rollout for all existing NodePools in the cluster. -Changing the content of the secret inplace will not trigger a rollout and might result in unpredictable behaviour. +This pull secret is included in NodePool ignition/bootstrap payloads and applied to the container runtime when nodes provision. +Changing this value will trigger a rollout for all existing NodePools in the cluster (for both replace and inplace upgrade types). +Updating the referenced Secret’s data in place (without changing this reference) does not trigger that rollout. +In AWS and Azure NodePools using the Replace upgrade strategy, the Secret’s data in place changes +will still propagate the updated credentials down to the guest cluster and kubelet config. TODO(alberto): have our own local reference type to include our opinions and avoid transparent changes.

diff --git a/vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/hostedcluster_types.go b/vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/hostedcluster_types.go index 41c831dcbbd..dfaf052fd5f 100644 --- a/vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/hostedcluster_types.go +++ b/vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/hostedcluster_types.go @@ -611,10 +611,11 @@ type HostedClusterSpec struct { // pullSecret is a local reference to a Secret that must have a ".dockerconfigjson" key whose content must be a valid Openshift pull secret JSON. // If the reference is set but none of the above requirements are met, the HostedCluster will enter a degraded state. // TODO(alberto): Signal this in a condition. - // This pull secret will be part of every payload generated by the controllers for any NodePool of the HostedCluster - // and it will be injected into the container runtime of all NodePools. - // Changing this value will trigger a rollout for all existing NodePools in the cluster. - // Changing the content of the secret inplace will not trigger a rollout and might result in unpredictable behaviour. + // This pull secret is included in NodePool ignition/bootstrap payloads and applied to the container runtime when nodes provision. + // Changing this value will trigger a rollout for all existing NodePools in the cluster (for both replace and inplace upgrade types). + // Updating the referenced Secret's data in place (without changing this reference) does not trigger that rollout. + // In AWS and Azure NodePools using the Replace upgrade strategy, the Secret's data in place changes + // will still propagate the updated credentials down to the guest cluster and kubelet config. // +required // +rollout // TODO(alberto): have our own local reference type to include our opinions and avoid transparent changes.