From fc75cca0f560422ab42092bf0f653f2e6260df47 Mon Sep 17 00:00:00 2001 From: Szepesi Tibor Date: Fri, 8 May 2026 19:33:44 +0200 Subject: [PATCH] fix(HCCO): gate Route watch on management cluster capability The hosted-cluster-config-operator unconditionally watches route.openshift.io/v1 Routes against the management cluster to react to hostname changes on the metrics-proxy Route. On management clusters that do not expose the Routes API (e.g. non-OpenShift management clusters) this watch fails during controller setup and prevents HCCO from starting. Detect the management cluster Route capability using the existing capabilities.DetectManagementClusterCapabilities helper and only register the watch when route.openshift.io is registered. This mirrors the pattern already used in other parts of the code. --- .../hostedclusterconfigoperator/cmd.go | 58 ++++++++++++------- .../controllers/resources/resources.go | 8 ++- .../operator/config.go | 47 +++++++-------- 3 files changed, 67 insertions(+), 46 deletions(-) diff --git a/control-plane-operator/hostedclusterconfigoperator/cmd.go b/control-plane-operator/hostedclusterconfigoperator/cmd.go index d5a34ea14fb..614ea707fd5 100644 --- a/control-plane-operator/hostedclusterconfigoperator/cmd.go +++ b/control-plane-operator/hostedclusterconfigoperator/cmd.go @@ -33,12 +33,14 @@ import ( "github.com/openshift/hypershift/control-plane-operator/hostedclusterconfigoperator/controllers/spotremediation" "github.com/openshift/hypershift/control-plane-operator/hostedclusterconfigoperator/operator" hyperapi "github.com/openshift/hypershift/support/api" + "github.com/openshift/hypershift/support/capabilities" "github.com/openshift/hypershift/support/labelenforcingclient" "github.com/openshift/hypershift/support/releaseinfo" "github.com/openshift/hypershift/support/supportedversion" "github.com/openshift/hypershift/support/upsert" "github.com/openshift/hypershift/support/util" + "k8s.io/client-go/discovery" "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" @@ -222,6 +224,19 @@ func (o *HostedClusterConfigOperator) Run(ctx context.Context) error { } cfg := operator.CfgFromFile(o.TargetKubeconfig) cpConfig := ctrl.GetConfigOrDie() + + // Detect the management cluster capabilities once at startup so controllers can + // gate optional behavior (e.g. watching route.openshift.io Routes) without each + // having to build its own discovery client. + cpDiscoveryClient, err := discovery.NewDiscoveryClientForConfig(cpConfig) + if err != nil { + return fmt.Errorf("failed to create management cluster discovery client: %w", err) + } + mgmtClusterCaps, err := capabilities.DetectManagementClusterCapabilities(cpDiscoveryClient) + if err != nil { + return fmt.Errorf("failed to detect management cluster capabilities: %w", err) + } + mgr := operator.Mgr(ctx, cfg, cpConfig, o.Namespace, o.HostedControlPlaneName) mgr.GetLogger().Info("Starting hosted-cluster-config-operator", "version", supportedversion.String()) cpCluster, err := cluster.New(cpConfig, func(opt *cluster.Options) { @@ -302,27 +317,28 @@ func (o *HostedClusterConfigOperator) Run(ctx context.Context) error { APIClient: apiReadingClient, }, - Config: cpConfig, - TargetConfig: cfg, - KubevirtInfraConfig: kubevirtInfraConfig, - Manager: mgr, - Namespace: o.Namespace, - HCPName: o.HostedControlPlaneName, - InitialCA: string(o.initialCA), - ClusterSignerCA: string(o.clusterSignerCA), - ControllerFuncs: controllersToRun, - Versions: versions, - PlatformType: hyperv1.PlatformType(o.platformType), - CPCluster: cpCluster, - Logger: ctrl.Log.WithName("hypershift-operator"), - ReleaseProvider: releaseProvider, - KonnectivityAddress: o.KonnectivityAddress, - KonnectivityPort: o.KonnectivityPort, - OAuthAddress: o.OAuthAddress, - OAuthPort: o.OAuthPort, - OperateOnReleaseImage: os.Getenv("OPERATE_ON_RELEASE_IMAGE"), - EnableCIDebugOutput: o.enableCIDebugOutput, - ImageMetaDataProvider: imageMetaDataProvider, + Config: cpConfig, + TargetConfig: cfg, + KubevirtInfraConfig: kubevirtInfraConfig, + Manager: mgr, + Namespace: o.Namespace, + HCPName: o.HostedControlPlaneName, + InitialCA: string(o.initialCA), + ClusterSignerCA: string(o.clusterSignerCA), + ControllerFuncs: controllersToRun, + Versions: versions, + PlatformType: hyperv1.PlatformType(o.platformType), + CPCluster: cpCluster, + Logger: ctrl.Log.WithName("hypershift-operator"), + ReleaseProvider: releaseProvider, + KonnectivityAddress: o.KonnectivityAddress, + KonnectivityPort: o.KonnectivityPort, + OAuthAddress: o.OAuthAddress, + OAuthPort: o.OAuthPort, + OperateOnReleaseImage: os.Getenv("OPERATE_ON_RELEASE_IMAGE"), + EnableCIDebugOutput: o.enableCIDebugOutput, + ImageMetaDataProvider: imageMetaDataProvider, + ManagementClusterCapabilities: mgmtClusterCaps, } configmetrics.Register(mgr.GetCache()) return operatorConfig.Start(ctx) diff --git a/control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources.go b/control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources.go index 374fef72417..a1d45d2e6e9 100644 --- a/control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources.go +++ b/control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources.go @@ -307,8 +307,12 @@ func Setup(ctx context.Context, opts *operator.HostedClusterConfigOperatorConfig } // Watch metrics-proxy Route on the control plane cluster for hostname changes. - if err := c.Watch(source.Kind[client.Object](opts.CPCluster.GetCache(), &routev1.Route{}, eventHandler())); err != nil { - return fmt.Errorf("failed to watch Route: %w", err) + // Skip when the management cluster does not expose the route.openshift.io API + // (non-OpenShift management cluster); otherwise the watch fails and HCCO does not start. + if opts.ManagementClusterCapabilities.Has(capabilities.CapabilityRoute) { + if err := c.Watch(source.Kind[client.Object](opts.CPCluster.GetCache(), &routev1.Route{}, eventHandler())); err != nil { + return fmt.Errorf("failed to watch Route: %w", err) + } } // Watch HostedControlPlane namespace pull-secret on the control plane cluster so guest pull secrets diff --git a/control-plane-operator/hostedclusterconfigoperator/operator/config.go b/control-plane-operator/hostedclusterconfigoperator/operator/config.go index e59c3b97463..076633d2d89 100644 --- a/control-plane-operator/hostedclusterconfigoperator/operator/config.go +++ b/control-plane-operator/hostedclusterconfigoperator/operator/config.go @@ -56,29 +56,30 @@ func cacheLabelSelector() labels.Selector { type ControllerSetupFunc func(context.Context, *HostedClusterConfigOperatorConfig) error type HostedClusterConfigOperatorConfig struct { - Manager ctrl.Manager - Config *rest.Config - TargetConfig *rest.Config - KubevirtInfraConfig *rest.Config - TargetCreateOrUpdateProvider upsert.CreateOrUpdateProvider - CPCluster cluster.Cluster - Logger logr.Logger - Versions map[string]string - HCPName string - Namespace string - InitialCA string - ClusterSignerCA string - Controllers []string - PlatformType hyperv1.PlatformType - ControllerFuncs map[string]ControllerSetupFunc - ReleaseProvider releaseinfo.Provider - KonnectivityAddress string - KonnectivityPort int32 - OAuthAddress string - OAuthPort int32 - OperateOnReleaseImage string - EnableCIDebugOutput bool - ImageMetaDataProvider util.ImageMetadataProvider + Manager ctrl.Manager + Config *rest.Config + TargetConfig *rest.Config + KubevirtInfraConfig *rest.Config + TargetCreateOrUpdateProvider upsert.CreateOrUpdateProvider + CPCluster cluster.Cluster + Logger logr.Logger + Versions map[string]string + HCPName string + Namespace string + InitialCA string + ClusterSignerCA string + Controllers []string + PlatformType hyperv1.PlatformType + ControllerFuncs map[string]ControllerSetupFunc + ReleaseProvider releaseinfo.Provider + KonnectivityAddress string + KonnectivityPort int32 + OAuthAddress string + OAuthPort int32 + OperateOnReleaseImage string + EnableCIDebugOutput bool + ImageMetaDataProvider util.ImageMetadataProvider + ManagementClusterCapabilities capabilities.CapabiltyChecker kubeClient kubeclient.Interface }