From e0e2992075d2ebbaf87622a5e3fd79bac5026751 Mon Sep 17 00:00:00 2001 From: yiannistri <8741709+yiannistri@users.noreply.github.com> Date: Mon, 24 Nov 2025 10:09:38 +0000 Subject: [PATCH] fix: Prefix image repository with Rancher image registry when pulling provider images --- .github/workflows/test_chart.yaml | 10 +++- .../rancher-turtles/templates/deployment.yaml | 2 +- charts/rancher-turtles/values.yaml | 4 ++ feature/feature.go | 10 ++-- .../clusterctl/config-community.yaml | 2 +- .../controllers/clusterctl/config-prime.yaml | 2 +- internal/controllers/clusterctl/config.go | 46 +++++++++++++++++++ .../controllers/clusterctl/config_test.go | 10 ++++ .../clusterctlconfig_controller.go | 1 + .../controllers/operator_reconciler_test.go | 25 ++++++---- internal/controllers/sync_controller_test.go | 12 +++++ .../data/capi-operator/clusterctlconfig.yaml | 4 ++ tilt/project/Tiltfile | 1 + 13 files changed, 113 insertions(+), 16 deletions(-) diff --git a/.github/workflows/test_chart.yaml b/.github/workflows/test_chart.yaml index 54ca12580..880cb2226 100644 --- a/.github/workflows/test_chart.yaml +++ b/.github/workflows/test_chart.yaml @@ -83,6 +83,12 @@ jobs: kubectl --namespace cattle-system wait --for=create deployments/rancher-webhook --timeout=300s kubectl --namespace cattle-system wait --for=condition=Available deployment/rancher-webhook --timeout=300s + - name: Set default registry + run: | + kubectl patch setting.management.cattle.io -n cattle-system system-default-registry \ + --type merge \ + --patch '{"value":"registry.suse.com"}' + - name: Run chart-testing (install) run: helm install rancher-turtles out/charts/rancher-turtles/ -n rancher-turtles-system --set namespace=rancher-turtles-system --create-namespace --wait --debug @@ -166,7 +172,7 @@ jobs: - name: Check capi-controller-manager image is from kubernetes-sigs (community) run: | IMAGE=$(kubectl get deployment capi-controller-manager -n cattle-capi-system -o jsonpath='{.spec.template.spec.containers[0].image}') - if [[ "$IMAGE" != registry.k8s.io/cluster-api/cluster-api-controller* ]]; then - echo "capi-controller-manager does not use registry.k8s.io/cluster-api/cluster-api-controller based image" + if [[ "$IMAGE" != rancher/cluster-api-controller* ]]; then + echo "capi-controller-manager does not use rancher/cluster-api-controller based image" exit 1 fi diff --git a/charts/rancher-turtles/templates/deployment.yaml b/charts/rancher-turtles/templates/deployment.yaml index b0178b37b..eb67109f7 100644 --- a/charts/rancher-turtles/templates/deployment.yaml +++ b/charts/rancher-turtles/templates/deployment.yaml @@ -26,7 +26,7 @@ spec: containers: - args: - --leader-elect - - --feature-gates=agent-tls-mode={{ index .Values "features" "agent-tls-mode" "enabled"}},ui-plugin={{ index .Values "turtlesUI" "enabled"}},no-cert-manager={{ index .Values "features" "no-cert-manager" "enabled"}} + - --feature-gates=agent-tls-mode={{ index .Values "features" "agent-tls-mode" "enabled"}},ui-plugin={{ index .Values "turtlesUI" "enabled"}},no-cert-manager={{ index .Values "features" "no-cert-manager" "enabled"}},use-rancher-default-registry={{ index .Values "features" "use-rancher-default-registry" "enabled"}} {{- range .Values.managerArguments }} - {{ . }} {{- end }} diff --git a/charts/rancher-turtles/values.yaml b/charts/rancher-turtles/values.yaml index d5654ff4a..618054b0d 100644 --- a/charts/rancher-turtles/values.yaml +++ b/charts/rancher-turtles/values.yaml @@ -39,6 +39,10 @@ features: no-cert-manager: # enabled: Turn on or off. enabled: false + # use-rancher-default-registry: Alpha feature to use Rancher's default registry for provider images. + use-rancher-default-registry: + # enabled: Turn on or off. + enabled: true # volumes: Volumes for controller pods. volumes: - name: clusterctl-config diff --git a/feature/feature.go b/feature/feature.go index 97984b2a9..b269aa74a 100644 --- a/feature/feature.go +++ b/feature/feature.go @@ -31,6 +31,9 @@ const ( // NoCertManager if enabled Turtles will remove cert-manager, including Certificate and Issuer. NoCertManager featuregate.Feature = "no-cert-manager" + + // UseRancherDefaultRegistry if enabled Turtles will use the Rancher default registry for pulling provider images. + UseRancherDefaultRegistry featuregate.Feature = "use-rancher-default-registry" ) func init() { @@ -38,7 +41,8 @@ func init() { } var defaultGates = map[featuregate.Feature]featuregate.FeatureSpec{ - AgentTLSMode: {Default: true, PreRelease: featuregate.Beta}, - UIPlugin: {Default: false, PreRelease: featuregate.Alpha}, - NoCertManager: {Default: false, PreRelease: featuregate.Alpha}, + AgentTLSMode: {Default: true, PreRelease: featuregate.Beta}, + UIPlugin: {Default: false, PreRelease: featuregate.Alpha}, + NoCertManager: {Default: false, PreRelease: featuregate.Alpha}, + UseRancherDefaultRegistry: {Default: true, PreRelease: featuregate.Alpha}, } diff --git a/internal/controllers/clusterctl/config-community.yaml b/internal/controllers/clusterctl/config-community.yaml index ed2965b04..bccc6e271 100644 --- a/internal/controllers/clusterctl/config-community.yaml +++ b/internal/controllers/clusterctl/config-community.yaml @@ -15,4 +15,4 @@ data: type: "CoreProvider" images: cluster-api: - repository: "registry.k8s.io/cluster-api" + repository: "rancher" diff --git a/internal/controllers/clusterctl/config-prime.yaml b/internal/controllers/clusterctl/config-prime.yaml index 669a6883a..62ad008ae 100644 --- a/internal/controllers/clusterctl/config-prime.yaml +++ b/internal/controllers/clusterctl/config-prime.yaml @@ -68,5 +68,5 @@ data: repository: "registry.suse.com/rancher" infrastructure-vsphere: repository: "registry.suse.com/rancher" - addon-fleet: + addon-rancher-fleet: repository: "registry.suse.com/rancher" diff --git a/internal/controllers/clusterctl/config.go b/internal/controllers/clusterctl/config.go index 99ea325ce..b13a71428 100644 --- a/internal/controllers/clusterctl/config.go +++ b/internal/controllers/clusterctl/config.go @@ -31,7 +31,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/yaml" + managementv3 "github.com/rancher/turtles/api/rancher/management/v3" turtlesv1 "github.com/rancher/turtles/api/v1alpha1" + "github.com/rancher/turtles/feature" ) var config *corev1.ConfigMap @@ -146,16 +148,60 @@ func ClusterConfig(ctx context.Context, c client.Client) (*ConfigRepository, err } } + if feature.Gates.Enabled(feature.UseRancherDefaultRegistry) { + log.Info("Turtles configured to use Rancher default registry for images") + + setting := &managementv3.Setting{} + if err := c.Get(ctx, client.ObjectKey{Name: "system-default-registry"}, setting); err != nil { + log.Error(err, "Unable to get system-default-registry setting") + return nil, err + } + + registry := setting.Value + if registry != "" { + log.Info("Rancher default registry has been set", "registry", registry) + + if !strings.HasSuffix(registry, "/") { + registry += "/" + } + + // Iterate through all images for the supported providers and override + // the repository to use Rancher's system default registry + for image, url := range clusterctlConfig.Images { + namespace := extractNamespace(url.Repository) + + clusterctlConfig.Images[image] = ConfigImage{ + Tag: url.Tag, + Repository: registry + namespace, + } + log.Info("Overridden provider image to use Rancher default registry", "image", image, + "repository", clusterctlConfig.Images[image].Repository, "tag", url.Tag) + } + } + } + + // Override images from ClusterctlConfig for _, image := range config.Spec.Images { clusterctlConfig.Images[image.Name] = ConfigImage{ Tag: image.Tag, Repository: image.Repository, } + + log.Info("Overridden provider image from ClusterctlConfig", "image", image.Name, "repository", image.Repository, "tag", image.Tag) } return clusterctlConfig, nil } +func extractNamespace(imageURI string) string { + parts := strings.Split(imageURI, "/") + if len(parts) > 1 { + return strings.Join(parts[1:], "/") + } + + return imageURI +} + // GetProviderVersion collects version of the collected provider overrides state. // Returns latest if the version is not found. func (r *ConfigRepository) GetProviderVersion(ctx context.Context, name, providerType string) (version string, providerKnown bool) { diff --git a/internal/controllers/clusterctl/config_test.go b/internal/controllers/clusterctl/config_test.go index a5de646c6..c415d88e0 100644 --- a/internal/controllers/clusterctl/config_test.go +++ b/internal/controllers/clusterctl/config_test.go @@ -23,6 +23,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + managementv3 "github.com/rancher/turtles/api/rancher/management/v3" "github.com/rancher/turtles/api/v1alpha1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -52,6 +53,9 @@ var _ = Describe("ClusterConfig Tests", func() { // Register the v1alpha1.ClusterctlConfig type Expect(v1alpha1.AddToScheme(scheme)).To(Succeed()) + // Register the management.Setting type + Expect(managementv3.AddToScheme(scheme)).To(Succeed()) + // Override the configDefault variable to provide custom data for the test configDefault := []byte(` data: @@ -111,6 +115,12 @@ data: }, }, }, + &managementv3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-default-registry", + }, + Value: "registry.example.com", + }, ). Build() }) diff --git a/internal/controllers/clusterctlconfig_controller.go b/internal/controllers/clusterctlconfig_controller.go index e6b03b260..a64f034b6 100644 --- a/internal/controllers/clusterctlconfig_controller.go +++ b/internal/controllers/clusterctlconfig_controller.go @@ -76,6 +76,7 @@ func (r *ClusterctlConfigReconciler) SetupWithManager(_ context.Context, mgr ctr //+kubebuilder:rbac:groups=turtles-capi.cattle.io,resources=clusterctlconfigs/status,verbs=get;list;watch;patch //+kubebuilder:rbac:groups=turtles-capi.cattle.io,resources=clusterctlconfigs/finalizers,verbs=get;list;watch;patch;update //+kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;patch +//+kubebuilder:rbac:groups="management.cattle.io",resources=settings,verbs=get;list;watch // Reconcile reconciles the ClusterctlConfig object. func (r *ClusterctlConfigReconciler) Reconcile(ctx context.Context, _ reconcile.Request) (ctrl.Result, error) { diff --git a/internal/controllers/operator_reconciler_test.go b/internal/controllers/operator_reconciler_test.go index 00346ca4e..fdda8f1eb 100644 --- a/internal/controllers/operator_reconciler_test.go +++ b/internal/controllers/operator_reconciler_test.go @@ -21,6 +21,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + managementv3 "github.com/rancher/turtles/api/rancher/management/v3" turtlesv1 "github.com/rancher/turtles/api/v1alpha1" "github.com/rancher/turtles/internal/provider" corev1 "k8s.io/api/core/v1" @@ -50,6 +51,7 @@ var _ = Describe("Provider sync", func() { capiProviderAzure *turtlesv1.CAPIProvider capiProviderGCP *turtlesv1.CAPIProvider clusterctlconfig *turtlesv1.ClusterctlConfig + setting *managementv3.Setting ) BeforeEach(func() { @@ -103,6 +105,13 @@ var _ = Describe("Provider sync", func() { }, } + setting = &managementv3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-default-registry", + }, + Value: "", + } + os.Setenv("POD_NAMESPACE", ns.Name) }) @@ -113,7 +122,7 @@ var _ = Describe("Provider sync", func() { It("Should sync spec down and leave version to latest", func() { origin := capiProvider.DeepCopy() origin.Spec.EnableAutomaticUpdate = true - fakeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(origin).Build() + fakeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(origin, setting).Build() r := &CAPIProviderReconciler{ Client: fakeClient, GenericProviderReconciler: controller.GenericProviderReconciler{ @@ -135,7 +144,7 @@ var _ = Describe("Provider sync", func() { It("Should use unknown provider to clusterctl override with unchanged 'latest' version", func() { origin := unknownCAPIProvider.DeepCopy() - fakeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(origin).Build() + fakeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(origin, setting).Build() r := &CAPIProviderReconciler{ Client: fakeClient, GenericProviderReconciler: controller.GenericProviderReconciler{ @@ -162,7 +171,7 @@ var _ = Describe("Provider sync", func() { It("Should reconcile unknown provider to clusterctl override with a specified version unchanged", func() { origin := unknownCAPIProvider.DeepCopy() origin.Spec.Version = "v1.0.0" - fakeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(origin).Build() + fakeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(origin, setting).Build() r := &CAPIProviderReconciler{ Client: fakeClient, GenericProviderReconciler: controller.GenericProviderReconciler{ @@ -190,7 +199,7 @@ var _ = Describe("Provider sync", func() { It("Should set custom provider version to latest according to clusterctlconfig override", func() { origin := customCAPIProvider.DeepCopy() origin.Spec.EnableAutomaticUpdate = true - fakeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(origin, clusterctlconfig).Build() + fakeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(origin, clusterctlconfig, setting).Build() r := &CAPIProviderReconciler{ Client: fakeClient, GenericProviderReconciler: controller.GenericProviderReconciler{ @@ -218,7 +227,7 @@ var _ = Describe("Provider sync", func() { It("Should not change custom provider version even if it is in the clusterctlconfig override", func() { origin := customCAPIProvider.DeepCopy() origin.Spec.Version = "v1.0.0" - fakeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(origin, clusterctlconfig).Build() + fakeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(origin, clusterctlconfig, setting).Build() r := &CAPIProviderReconciler{ Client: fakeClient, GenericProviderReconciler: controller.GenericProviderReconciler{ @@ -243,7 +252,7 @@ var _ = Describe("Provider sync", func() { It("Should sync azure spec", func() { origin := capiProviderAzure.DeepCopy() - fakeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(origin).Build() + fakeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(origin, setting).Build() r := &CAPIProviderReconciler{ Client: fakeClient, GenericProviderReconciler: controller.GenericProviderReconciler{ @@ -261,7 +270,7 @@ var _ = Describe("Provider sync", func() { It("Should sync gcp spec", func() { origin := capiProviderGCP.DeepCopy() - fakeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(origin).Build() + fakeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(origin, setting).Build() r := &CAPIProviderReconciler{ Client: fakeClient, GenericProviderReconciler: controller.GenericProviderReconciler{ @@ -280,7 +289,7 @@ var _ = Describe("Provider sync", func() { It("Should sync status up and set provisioning state", func() { origin := capiProvider.DeepCopy() origin.Spec.EnableAutomaticUpdate = true - fakeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(origin).Build() + fakeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(origin, setting).Build() r := &CAPIProviderReconciler{ Client: fakeClient, GenericProviderReconciler: controller.GenericProviderReconciler{ diff --git a/internal/controllers/sync_controller_test.go b/internal/controllers/sync_controller_test.go index bf465396c..c3e7d6574 100644 --- a/internal/controllers/sync_controller_test.go +++ b/internal/controllers/sync_controller_test.go @@ -21,6 +21,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + managementv3 "github.com/rancher/turtles/api/rancher/management/v3" turtlesv1 "github.com/rancher/turtles/api/v1alpha1" "github.com/rancher/turtles/internal/sync" corev1 "k8s.io/api/core/v1" @@ -71,10 +72,21 @@ var _ = Describe("Reconcile CAPIProvider", Ordered, func() { ns, err = testEnv.CreateNamespace(ctx, "capiprovider") Expect(err).ToNot(HaveOccurred()) + testEnv.Create(ctx, &managementv3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-default-registry", + }, + Value: "", + }) }) AfterEach(func() { Expect(testEnv.Delete(ctx, ns)).Should(Succeed()) + Expect(testEnv.Delete(ctx, &managementv3.Setting{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-default-registry", + }, + })).Should(Succeed()) }) It("Should create CAPIProvider secret", func() { diff --git a/test/e2e/data/capi-operator/clusterctlconfig.yaml b/test/e2e/data/capi-operator/clusterctlconfig.yaml index 376657c03..2614a26ee 100644 --- a/test/e2e/data/capi-operator/clusterctlconfig.yaml +++ b/test/e2e/data/capi-operator/clusterctlconfig.yaml @@ -7,3 +7,7 @@ spec: images: - name: infrastructure-docker repository: "gcr.io/k8s-staging-cluster-api" + - name: infrastructure-aws + repository: "registry.k8s.io/cluster-api-aws" + - name: infrastructure-gcp + repository: "registry.k8s.io/cluster-api-gcp" diff --git a/tilt/project/Tiltfile b/tilt/project/Tiltfile index b16752c82..3a1e5b13f 100644 --- a/tilt/project/Tiltfile +++ b/tilt/project/Tiltfile @@ -149,6 +149,7 @@ def update_manager(yaml, containerName, debug, env, name, project): debugArgs.append(arg) if debug_verbose: debugArgs.append("--v=5") + debugArgs.append("--feature-gates=agent-tls-mode=true,no-cert-manager=false,use-rancher-default-registry=true") c["command"] = cmd print(cmd) if len(debugArgs) == 0: