diff --git a/cmd/config-sync-controllers/main.go b/cmd/config-sync-controllers/main.go index de918b0aa..af0793ca2 100644 --- a/cmd/config-sync-controllers/main.go +++ b/cmd/config-sync-controllers/main.go @@ -26,6 +26,7 @@ import ( // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. + "k8s.io/client-go/kubernetes" _ "k8s.io/client-go/plugin/pkg/client/auth" "k8s.io/apimachinery/pkg/runtime" @@ -40,6 +41,11 @@ import ( metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" configv1 "github.com/openshift/api/config/v1" + "github.com/openshift/library-go/pkg/operator/configobserver/featuregates" + "github.com/openshift/library-go/pkg/operator/events" + + configv1client "github.com/openshift/client-go/config/clientset/versioned" + configinformers "github.com/openshift/client-go/config/informers/externalversions" "github.com/openshift/cluster-cloud-controller-manager-operator/pkg/controllers" "github.com/openshift/cluster-cloud-controller-manager-operator/pkg/restmapper" @@ -79,6 +85,12 @@ func main() { "The namespace for managed objects, target cloud-conf in particular.", ) + recorderName := "cloud-controller-manager-operator-cloud-config-sync-controller" + missingVersion := "0.0.1-snapshot" + desiredVersion := controllers.GetReleaseVersion() + sharedClock := clock.RealClock{} + ctx := ctrl.SetupSignalHandler() + // Once all the flags are regitered, switch to pflag // to allow leader lection flags to be bound pflag.CommandLine.AddGoFlagSet(flag.CommandLine) @@ -131,7 +143,36 @@ func main() { os.Exit(1) } - sharedClock := clock.RealClock{} + // Feature gate accessor + configClient, err := configv1client.NewForConfig(mgr.GetConfig()) + if err != nil { + setupLog.Error(err, "unable to create config client") + os.Exit(1) + } + kubeClient, err := kubernetes.NewForConfig(mgr.GetConfig()) + if err != nil { + setupLog.Error(err, "unable to create kube client") + os.Exit(1) + } + + configInformers := configinformers.NewSharedInformerFactory(configClient, 10*time.Minute) + controllerRef, err := events.GetControllerReferenceForCurrentPod(ctx, kubeClient, *managedNamespace, nil) + if err != nil { + klog.Warningf("unable to get owner reference (falling back to namespace): %v", err) + } + + featureGateAccessor := featuregates.NewFeatureGateAccess( + desiredVersion, missingVersion, + configInformers.Config().V1().ClusterVersions(), configInformers.Config().V1().FeatureGates(), + events.NewKubeRecorder(kubeClient.CoreV1().Events(*managedNamespace), recorderName, controllerRef, sharedClock), + ) + featureGateAccessor.SetChangeHandler(func(featureChange featuregates.FeatureChange) { + // Do nothing here. The controller watches feature gate changes and will react to them. + klog.InfoS("FeatureGates changed", "enabled", featureChange.New.Enabled, "disabled", featureChange.New.Disabled) + }) + go featureGateAccessor.Run(ctx) + go configInformers.Start(ctx.Done()) + if err = (&controllers.CloudConfigReconciler{ ClusterOperatorStatusClient: controllers.ClusterOperatorStatusClient{ Client: mgr.GetClient(), @@ -140,7 +181,8 @@ func main() { ReleaseVersion: controllers.GetReleaseVersion(), ManagedNamespace: *managedNamespace, }, - Scheme: mgr.GetScheme(), + Scheme: mgr.GetScheme(), + FeatureGateAccess: featureGateAccessor, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create cloud-config sync controller", "controller", "ClusterOperator") os.Exit(1) @@ -171,7 +213,7 @@ func main() { } setupLog.Info("starting manager") - if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + if err := mgr.Start(ctx); err != nil { setupLog.Error(err, "problem running manager") os.Exit(1) } diff --git a/pkg/cloud/aws/aws_config_transformer.go b/pkg/cloud/aws/aws_config_transformer.go index 594b5093a..92f96805b 100644 --- a/pkg/cloud/aws/aws_config_transformer.go +++ b/pkg/cloud/aws/aws_config_transformer.go @@ -11,6 +11,8 @@ import ( "gopkg.in/ini.v1" awsconfig "k8s.io/cloud-provider-aws/pkg/providers/v1/config" + + "github.com/openshift/library-go/pkg/operator/configobserver/featuregates" ) // defaultConfig is a string holding the absolute bare minimum INI string that the AWS CCM needs to start. @@ -20,7 +22,7 @@ const defaultConfig = `[Global] // CloudConfigTransformer is used to inject OpenShift configuration defaults into the Cloud Provider config // for the AWS Cloud Provider. If an empty source string is provided, a minimal default configuration will be created. -func CloudConfigTransformer(source string, infra *configv1.Infrastructure, network *configv1.Network) (string, error) { +func CloudConfigTransformer(source string, infra *configv1.Infrastructure, network *configv1.Network, features featuregates.FeatureGate) (string, error) { cfg, err := readAWSConfig(source) if err != nil { return "", fmt.Errorf("failed to read the cloud.conf: %w", err) diff --git a/pkg/cloud/aws/aws_config_transformer_test.go b/pkg/cloud/aws/aws_config_transformer_test.go index adb857395..34c5116d8 100644 --- a/pkg/cloud/aws/aws_config_transformer_test.go +++ b/pkg/cloud/aws/aws_config_transformer_test.go @@ -4,6 +4,8 @@ import ( "testing" . "github.com/onsi/gomega" + + "github.com/openshift/library-go/pkg/operator/configobserver/featuregates" ) func TestCloudConfigTransformer(t *testing.T) { @@ -87,7 +89,7 @@ SigningRegion = signing_region t.Run(tc.name, func(t *testing.T) { g := NewWithT(t) - gotConfig, err := CloudConfigTransformer(tc.source, nil, nil) // No Infra or Network are required for the current functionality. + gotConfig, err := CloudConfigTransformer(tc.source, nil, nil, featuregates.NewFeatureGate(nil, nil)) // No Infra or Network are required for the current functionality. g.Expect(err).ToNot(HaveOccurred()) g.Expect(gotConfig).To(Equal(tc.expected)) diff --git a/pkg/cloud/azure/azure.go b/pkg/cloud/azure/azure.go index 560e831e4..77e02aa6d 100644 --- a/pkg/cloud/azure/azure.go +++ b/pkg/cloud/azure/azure.go @@ -20,6 +20,7 @@ import ( "github.com/openshift/cluster-cloud-controller-manager-operator/pkg/cloud/common" "github.com/openshift/cluster-cloud-controller-manager-operator/pkg/config" + "github.com/openshift/library-go/pkg/operator/configobserver/featuregates" ) const providerName = "azure" @@ -136,7 +137,7 @@ func IsAzure(infra *configv1.Infrastructure) bool { return false } -func CloudConfigTransformer(source string, infra *configv1.Infrastructure, network *configv1.Network) (string, error) { +func CloudConfigTransformer(source string, infra *configv1.Infrastructure, network *configv1.Network, features featuregates.FeatureGate) (string, error) { if !IsAzure(infra) { return "", fmt.Errorf("invalid platform, expected CloudName to be %s", configv1.AzurePublicCloud) } diff --git a/pkg/cloud/azure/azure_test.go b/pkg/cloud/azure/azure_test.go index 578c41e8d..89b8336d1 100644 --- a/pkg/cloud/azure/azure_test.go +++ b/pkg/cloud/azure/azure_test.go @@ -9,6 +9,7 @@ import ( "github.com/onsi/gomega/format" configv1 "github.com/openshift/api/config/v1" "github.com/openshift/cluster-cloud-controller-manager-operator/pkg/config" + "github.com/openshift/library-go/pkg/operator/configobserver/featuregates" "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -259,7 +260,7 @@ func TestCloudConfigTransformer(t *testing.T) { src, err := json.Marshal(tc.source) g.Expect(err).NotTo(HaveOccurred(), "Marshal of source data should succeed") - actual, err := CloudConfigTransformer(string(src), tc.infra, nil) + actual, err := CloudConfigTransformer(string(src), tc.infra, nil, featuregates.NewFeatureGate(nil, nil)) if tc.errMsg != "" { g.Expect(err).Should(MatchError(tc.errMsg)) g.Expect(actual).Should(Equal("")) diff --git a/pkg/cloud/azurestack/azurestack.go b/pkg/cloud/azurestack/azurestack.go index 5f86d59c7..7c8ccf206 100644 --- a/pkg/cloud/azurestack/azurestack.go +++ b/pkg/cloud/azurestack/azurestack.go @@ -15,6 +15,7 @@ import ( "github.com/openshift/cluster-cloud-controller-manager-operator/pkg/cloud/common" "github.com/openshift/cluster-cloud-controller-manager-operator/pkg/config" + "github.com/openshift/library-go/pkg/operator/configobserver/featuregates" ) const providerName = "azurestack" @@ -104,7 +105,7 @@ func IsAzureStackHub(platformStatus *configv1.PlatformStatus) bool { // modifies it to be compatible with the external cloud provider. It returns // an error if the platform is not OpenStackPlatformType or if any errors are // encountered while attempting to rework the configuration. -func CloudConfigTransformer(source string, infra *configv1.Infrastructure, network *configv1.Network) (string, error) { +func CloudConfigTransformer(source string, infra *configv1.Infrastructure, network *configv1.Network, features featuregates.FeatureGate) (string, error) { if !IsAzureStackHub(infra.Status.PlatformStatus) { return "", fmt.Errorf("invalid platform, expected CloudName to be %s", configv1.AzureStackCloud) } diff --git a/pkg/cloud/azurestack/azurestack_test.go b/pkg/cloud/azurestack/azurestack_test.go index e82cd3aaa..965089a20 100644 --- a/pkg/cloud/azurestack/azurestack_test.go +++ b/pkg/cloud/azurestack/azurestack_test.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/openshift/cluster-cloud-controller-manager-operator/pkg/config" + "github.com/openshift/library-go/pkg/operator/configobserver/featuregates" ) const ( @@ -144,7 +145,7 @@ func TestCloudConfigTransformer(t *testing.T) { src, err := json.Marshal(tc.source) g.Expect(err).NotTo(HaveOccurred(), "Marshal of source data should succeed") - actual, err := CloudConfigTransformer(string(src), tc.infra, nil) + actual, err := CloudConfigTransformer(string(src), tc.infra, nil, featuregates.NewFeatureGate(nil, nil)) if tc.errMsg != "" { g.Expect(err).Should(MatchError(tc.errMsg)) g.Expect(actual).Should(Equal("")) diff --git a/pkg/cloud/cloud.go b/pkg/cloud/cloud.go index 2ae88af49..74845bf0d 100644 --- a/pkg/cloud/cloud.go +++ b/pkg/cloud/cloud.go @@ -8,6 +8,7 @@ import ( "github.com/openshift/cluster-cloud-controller-manager-operator/pkg/cloud/common" "github.com/openshift/cluster-cloud-controller-manager-operator/pkg/config" + "github.com/openshift/library-go/pkg/operator/configobserver/featuregates" "github.com/openshift/cluster-cloud-controller-manager-operator/pkg/cloud/aws" "github.com/openshift/cluster-cloud-controller-manager-operator/pkg/cloud/azure" @@ -23,7 +24,7 @@ import ( // cloudConfigTransformer function transforms the source config map using the input infrastructure.config.openshift.io // and network.config.openshift.io objects. Only the data and binaryData field of the output ConfigMap will be respected by // consumer of the transformer. -type cloudConfigTransformer func(source string, infra *configv1.Infrastructure, network *configv1.Network) (string, error) +type cloudConfigTransformer func(source string, infra *configv1.Infrastructure, network *configv1.Network, features featuregates.FeatureGate) (string, error) // GetCloudConfigTransformer returns the function that should be used to transform // the cloud configuration config map, and a boolean to indicate if the config should diff --git a/pkg/cloud/common/config.go b/pkg/cloud/common/config.go index a7e81e8ed..051d5f03a 100644 --- a/pkg/cloud/common/config.go +++ b/pkg/cloud/common/config.go @@ -2,10 +2,12 @@ package common import ( configv1 "github.com/openshift/api/config/v1" + + "github.com/openshift/library-go/pkg/operator/configobserver/featuregates" ) // NoOpTransformer implements the cloudConfigTransformer. It makes no changes // to the source configuration and simply returns it as-is. -func NoOpTransformer(source string, infra *configv1.Infrastructure, network *configv1.Network) (string, error) { +func NoOpTransformer(source string, infra *configv1.Infrastructure, network *configv1.Network, features featuregates.FeatureGate) (string, error) { return source, nil } diff --git a/pkg/cloud/openstack/openstack.go b/pkg/cloud/openstack/openstack.go index 4807f640b..99bd432bd 100644 --- a/pkg/cloud/openstack/openstack.go +++ b/pkg/cloud/openstack/openstack.go @@ -14,6 +14,7 @@ import ( "github.com/openshift/cluster-cloud-controller-manager-operator/pkg/cloud/common" "github.com/openshift/cluster-cloud-controller-manager-operator/pkg/config" + "github.com/openshift/library-go/pkg/operator/configobserver/featuregates" ) const providerName = "openstack" @@ -142,7 +143,7 @@ func NewProviderAssets(config config.OperatorConfig) (common.CloudProviderAssets // modifies it to be compatible with the external cloud provider. It returns // an error if the platform is not OpenStackPlatformType or if any errors are // encountered while attempting to rework the configuration. -func CloudConfigTransformer(source string, infra *configv1.Infrastructure, network *configv1.Network) (string, error) { +func CloudConfigTransformer(source string, infra *configv1.Infrastructure, network *configv1.Network, features featuregates.FeatureGate) (string, error) { if infra.Status.PlatformStatus == nil || infra.Status.PlatformStatus.Type != configv1.OpenStackPlatformType { return "", fmt.Errorf("invalid platform, expected to be %s", configv1.OpenStackPlatformType) diff --git a/pkg/cloud/openstack/openstack_test.go b/pkg/cloud/openstack/openstack_test.go index d9942ac7e..440ba268e 100644 --- a/pkg/cloud/openstack/openstack_test.go +++ b/pkg/cloud/openstack/openstack_test.go @@ -10,6 +10,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/openshift/cluster-cloud-controller-manager-operator/pkg/config" + "github.com/openshift/library-go/pkg/operator/configobserver/featuregates" ) const ( @@ -172,7 +173,7 @@ use-octavia = false for _, tc := range tc { t.Run(tc.name, func(t *testing.T) { g := NewWithT(t) - actual, err := CloudConfigTransformer(tc.source, tc.infra, tc.network) + actual, err := CloudConfigTransformer(tc.source, tc.infra, tc.network, featuregates.NewFeatureGate(nil, nil)) if tc.errMsg != "" { g.Expect(err).Should(MatchError(tc.errMsg)) return diff --git a/pkg/cloud/vsphere/vsphere_config_transformer.go b/pkg/cloud/vsphere/vsphere_config_transformer.go index c5f953213..8135b642f 100644 --- a/pkg/cloud/vsphere/vsphere_config_transformer.go +++ b/pkg/cloud/vsphere/vsphere_config_transformer.go @@ -8,6 +8,7 @@ import ( "k8s.io/utils/net" ccmConfig "github.com/openshift/cluster-cloud-controller-manager-operator/pkg/cloud/vsphere/vsphere_cloud_config" + "github.com/openshift/library-go/pkg/operator/configobserver/featuregates" ) // Well-known OCP-specific vSphere tags. These values are going to the "labels" sections in CCM cloud-config. @@ -27,7 +28,7 @@ const ( // Currently, CloudConfigTransformer is responsible to populate vcenters, labels, and node networking parameters from // the Infrastructure resource. // Also, this function converts legacy deprecated INI configuration format to a YAML-based one. -func CloudConfigTransformer(source string, infra *configv1.Infrastructure, network *configv1.Network) (string, error) { +func CloudConfigTransformer(source string, infra *configv1.Infrastructure, network *configv1.Network, features featuregates.FeatureGate) (string, error) { if infra.Status.PlatformStatus == nil || infra.Status.PlatformStatus.Type != configv1.VSpherePlatformType { return "", fmt.Errorf("invalid platform, expected to be %s", configv1.VSpherePlatformType) diff --git a/pkg/cloud/vsphere/vsphere_config_transformer_test.go b/pkg/cloud/vsphere/vsphere_config_transformer_test.go index 741507756..2bec7f1d2 100644 --- a/pkg/cloud/vsphere/vsphere_config_transformer_test.go +++ b/pkg/cloud/vsphere/vsphere_config_transformer_test.go @@ -8,6 +8,8 @@ import ( configv1 "github.com/openshift/api/config/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/openshift/library-go/pkg/operator/configobserver/featuregates" + ccm "k8s.io/cloud-provider-vsphere/pkg/cloudprovider/vsphere/config" ) @@ -393,6 +395,7 @@ func TestCloudConfigTransformer(t *testing.T) { inputConfig string equivalentConfig string errMsg string + features featuregates.FeatureGate }{ { name: "in-tree to external with empty infra", @@ -400,6 +403,7 @@ func TestCloudConfigTransformer(t *testing.T) { networkBuilder: makeDummyNetworkConfig(), inputConfig: iniConfigWithWorkspace, equivalentConfig: iniConfigWithoutWorkspace, + features: featuregates.NewFeatureGate(nil, nil), }, { name: "in-tree to external with node networking", @@ -407,6 +411,7 @@ func TestCloudConfigTransformer(t *testing.T) { networkBuilder: makeDummyNetworkConfig(), inputConfig: iniConfigWithWorkspace, equivalentConfig: iniConfigNodeNetworking, + features: featuregates.NewFeatureGate(nil, nil), }, { name: "populating labels datacenters from zones config", @@ -414,6 +419,7 @@ func TestCloudConfigTransformer(t *testing.T) { networkBuilder: makeDummyNetworkConfig(), inputConfig: iniConfigWithWorkspace, equivalentConfig: iniConfigZonal, + features: featuregates.NewFeatureGate(nil, nil), }, { name: "replacing existing labels with openshift specific", @@ -421,6 +427,7 @@ func TestCloudConfigTransformer(t *testing.T) { networkBuilder: makeDummyNetworkConfig(), inputConfig: iniConfigWithExistingLabels, equivalentConfig: iniConfigZonal, + features: featuregates.NewFeatureGate(nil, nil), }, { name: "yaml and ini config parsing results should be the same", @@ -428,6 +435,7 @@ func TestCloudConfigTransformer(t *testing.T) { networkBuilder: makeDummyNetworkConfig(), inputConfig: yamlConfig, equivalentConfig: iniConfigWithoutWorkspace, + features: featuregates.NewFeatureGate(nil, nil), }, { name: "yaml and ini config parsing results should be the same, with zones", @@ -435,6 +443,7 @@ func TestCloudConfigTransformer(t *testing.T) { networkBuilder: makeDummyNetworkConfig(), inputConfig: yamlConfigZonal, equivalentConfig: iniConfigZonal, + features: featuregates.NewFeatureGate(nil, nil), }, { name: "yaml and ini config parsing results should be the same, node networking", @@ -442,6 +451,7 @@ func TestCloudConfigTransformer(t *testing.T) { networkBuilder: makeDummyNetworkConfig(), inputConfig: yamlConfigNodeNetworking, equivalentConfig: iniConfigNodeNetworking, + features: featuregates.NewFeatureGate(nil, nil), }, { name: "yaml config should contain node networking if it's specified in infra", @@ -449,6 +459,7 @@ func TestCloudConfigTransformer(t *testing.T) { networkBuilder: makeDummyNetworkConfig(), inputConfig: yamlConfig, equivalentConfig: yamlConfigNodeNetworking, + features: featuregates.NewFeatureGate(nil, nil), }, { name: "yaml config should be populated with datacenters and labels if failure domains specified", @@ -456,6 +467,7 @@ func TestCloudConfigTransformer(t *testing.T) { networkBuilder: makeDummyNetworkConfig(), inputConfig: yamlConfig, equivalentConfig: yamlConfigZonal, + features: featuregates.NewFeatureGate(nil, nil), }, { name: "yaml config should contain ipv4-primary dual-stack config and correct excluded subnets", @@ -463,6 +475,7 @@ func TestCloudConfigTransformer(t *testing.T) { networkBuilder: withDualStackPrimaryIPv4NetworkConfig(), inputConfig: yamlConfig, equivalentConfig: yamlConfigNodeNetworkingDualStackPrimaryIPv4, + features: featuregates.NewFeatureGate(nil, nil), }, { name: "yaml config should contain ipv6-primary dual-stack config and correct excluded subnets", @@ -470,6 +483,7 @@ func TestCloudConfigTransformer(t *testing.T) { networkBuilder: withDualStackPrimaryIPv6NetworkConfig(), inputConfig: yamlConfig, equivalentConfig: yamlConfigNodeNetworkingDualStackPrimaryIPv6, + features: featuregates.NewFeatureGate(nil, nil), }, { name: "yaml config should contain ipv6-only config and correct excluded subnets", @@ -477,18 +491,21 @@ func TestCloudConfigTransformer(t *testing.T) { networkBuilder: withIPv6onlyNetworkConfig(), inputConfig: yamlConfig, equivalentConfig: yamlConfigNodeNetworkingIPv6only, + features: featuregates.NewFeatureGate(nil, nil), }, { name: "empty input", infraBuilder: newVsphereInfraBuilder(), networkBuilder: makeDummyNetworkConfig(), errMsg: "failed to read the cloud.conf: vSphere config is empty", + features: featuregates.NewFeatureGate(nil, nil), }, { name: "incorrect platform", infraBuilder: newVsphereInfraBuilder().withPlatform(configv1.NonePlatformType), networkBuilder: makeDummyNetworkConfig(), errMsg: "invalid platform, expected to be VSphere", + features: featuregates.NewFeatureGate(nil, nil), }, { name: "invalid ini input", @@ -496,6 +513,7 @@ func TestCloudConfigTransformer(t *testing.T) { networkBuilder: makeDummyNetworkConfig(), inputConfig: ":", errMsg: "failed to read the cloud.conf", + features: featuregates.NewFeatureGate(nil, nil), }, } @@ -503,7 +521,7 @@ func TestCloudConfigTransformer(t *testing.T) { t.Run(tc.name, func(t *testing.T) { g := gmg.NewWithT(t) infraResouce := tc.infraBuilder.Build() - transformedConfig, err := CloudConfigTransformer(tc.inputConfig, infraResouce, tc.networkBuilder) + transformedConfig, err := CloudConfigTransformer(tc.inputConfig, infraResouce, tc.networkBuilder, tc.features) if tc.errMsg != "" { g.Expect(err).To(gmg.MatchError(gmg.ContainSubstring(tc.errMsg))) return diff --git a/pkg/controllers/cloud_config_sync_controller.go b/pkg/controllers/cloud_config_sync_controller.go index 26b5a5f73..dc50e4acf 100644 --- a/pkg/controllers/cloud_config_sync_controller.go +++ b/pkg/controllers/cloud_config_sync_controller.go @@ -16,6 +16,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" configv1 "github.com/openshift/api/config/v1" + "github.com/openshift/library-go/pkg/operator/configobserver/featuregates" "github.com/openshift/cluster-cloud-controller-manager-operator/pkg/cloud" ) @@ -32,7 +33,8 @@ const ( type CloudConfigReconciler struct { ClusterOperatorStatusClient - Scheme *runtime.Scheme + Scheme *runtime.Scheme + FeatureGateAccess featuregates.FeatureGateAccess } func (r *CloudConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { @@ -133,11 +135,29 @@ func (r *CloudConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, err } + // Check if FeatureGateAccess is configured + if r.FeatureGateAccess == nil { + klog.Errorf("FeatureGateAccess is not configured") + if err := r.setDegradedCondition(ctx); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to set conditions for cloud config controller: %v", err) + } + return ctrl.Result{}, fmt.Errorf("FeatureGateAccess is not configured") + } + + features, err := r.FeatureGateAccess.CurrentFeatureGates() + if err != nil { + klog.Errorf("unable to get feature gates: %v", err) + if errD := r.setDegradedCondition(ctx); errD != nil { + return ctrl.Result{}, fmt.Errorf("failed to set conditions for cloud config controller: %v", errD) + } + return ctrl.Result{}, err + } + if cloudConfigTransformerFn != nil { // We ignore stuff in sourceCM.BinaryData. This isn't allowed to // contain any key that overlaps with those found in sourceCM.Data and // we're not expecting users to put their data in the former. - output, err := cloudConfigTransformerFn(sourceCM.Data[defaultConfigKey], infra, network) + output, err := cloudConfigTransformerFn(sourceCM.Data[defaultConfigKey], infra, network, features) if err != nil { if err := r.setDegradedCondition(ctx); err != nil { return ctrl.Result{}, fmt.Errorf("failed to set conditions for cloud config controller: %v", err) diff --git a/pkg/controllers/cloud_config_sync_controller_test.go b/pkg/controllers/cloud_config_sync_controller_test.go index 6588ace28..575318dd2 100644 --- a/pkg/controllers/cloud_config_sync_controller_test.go +++ b/pkg/controllers/cloud_config_sync_controller_test.go @@ -21,6 +21,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/manager" metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/openshift/library-go/pkg/operator/configobserver/featuregates" ) const ( @@ -112,7 +114,9 @@ func makeManagedCloudConfig(platform configv1.PlatformType) *corev1.ConfigMap { } var _ = Describe("isCloudConfigEqual reconciler method", func() { - reconciler := &CloudConfigReconciler{} + reconciler := &CloudConfigReconciler{ + FeatureGateAccess: featuregates.NewHardcodedFeatureGateAccessForTesting(nil, nil, nil, nil), + } It("should return 'true' if ConfigMaps content are equal", func() { Expect(reconciler.isCloudConfigEqual(makeManagedCloudConfig(configv1.AzurePlatformType), makeManagedCloudConfig(configv1.AzurePlatformType))).Should(BeTrue()) @@ -130,7 +134,9 @@ var _ = Describe("isCloudConfigEqual reconciler method", func() { }) var _ = Describe("prepareSourceConfigMap reconciler method", func() { - reconciler := &CloudConfigReconciler{} + reconciler := &CloudConfigReconciler{ + FeatureGateAccess: featuregates.NewHardcodedFeatureGateAccessForTesting(nil, nil, nil, nil), + } infra := makeInfrastructureResource(configv1.AzurePlatformType) infraCloudConfig := makeInfraCloudConfig(configv1.AzurePlatformType) managedCloudConfig := makeManagedCloudConfig(configv1.AzurePlatformType) @@ -199,7 +205,8 @@ var _ = Describe("Cloud config sync controller", func() { Clock: clocktesting.NewFakePassiveClock(time.Now()), ManagedNamespace: targetNamespaceName, }, - Scheme: scheme.Scheme, + Scheme: scheme.Scheme, + FeatureGateAccess: featuregates.NewHardcodedFeatureGateAccessForTesting(nil, nil, nil, nil), } Expect(reconciler.SetupWithManager(mgr)).To(Succeed()) @@ -400,7 +407,8 @@ var _ = Describe("Cloud config sync reconciler", func() { Clock: clocktesting.NewFakePassiveClock(time.Now()), ManagedNamespace: targetNamespaceName, }, - Scheme: scheme.Scheme, + Scheme: scheme.Scheme, + FeatureGateAccess: featuregates.NewHardcodedFeatureGateAccessForTesting(nil, nil, nil, nil), } networkResource := makeNetworkResource() @@ -513,6 +521,30 @@ var _ = Describe("Cloud config sync reconciler", func() { Expect(err.Error()).To(ContainSubstring("specified in infra resource does not exist in source configmap")) }) + + It("should continue with reconcile when feature gates are available", func() { + reconciler.FeatureGateAccess = featuregates.NewHardcodedFeatureGateAccessForTesting( + []configv1.FeatureGateName{"CloudControllerManagerWebhook", "ChocobombVanilla", "ChocobombStrawberry"}, + nil, // no disabled gates + nil, // no initial observed channel + nil, // no error + ) + + infraResource := makeInfrastructureResource(configv1.AWSPlatformType) + Expect(cl.Create(ctx, infraResource)).To(Succeed()) + + infraResource.Status = makeInfraStatus(infraResource.Spec.PlatformSpec.Type) + Expect(cl.Status().Update(ctx, infraResource.DeepCopy())).To(Succeed()) + + _, err := reconciler.Reconcile(context.TODO(), ctrl.Request{}) + Expect(err).To(BeNil()) + + allCMs := &corev1.ConfigMapList{} + Expect(cl.List(ctx, allCMs, &client.ListOptions{Namespace: targetNamespaceName})).To(Succeed()) + + Expect(len(allCMs.Items)).NotTo(BeZero()) + Expect(len(allCMs.Items)).To(BeEquivalentTo(1)) + }) }) Context("On Azure platform", func() {