From 2b79709bffbe79982e0b8746100516c0667439d8 Mon Sep 17 00:00:00 2001 From: Michael Burman Date: Mon, 23 Sep 2024 16:54:01 +0300 Subject: [PATCH] Add support for namespace overrides in image pulling --- CHANGELOG.md | 1 + apis/config/v1beta1/imageconfig_types.go | 2 +- apis/config/v1beta1/zz_generated.deepcopy.go | 13 +++-- pkg/images/images.go | 53 ++++++++++++-------- pkg/images/images_test.go | 13 +++-- 5 files changed, 54 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f018c4b..986379b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Changelog for Cass Operator, new PRs should update the `main / unreleased` secti * [FEATURE] [#651](https://github.com/k8ssandra/cass-operator/issues/651) Add tsreload task for DSE deployments and ability to check if sync operation is available on the mgmt-api side * [ENHANCEMENT] [#532](https://github.com/k8ssandra/k8ssandra-operator/issues/532) Extend ImageConfig type to allow additional parameters for k8ssandra-operator requirements. These include per-image PullPolicy / PullSecrets as well as additional image +* [ENHANCEMENT] [#636](https://github.com/k8ssandra/cass-operator/issues/636) Add support for new field in ImageConfig, imageNamespace. This will allow to override namespace of all images when using private registries. Setting it to empty will remove the namespace entirely. ## v1.22.4 diff --git a/apis/config/v1beta1/imageconfig_types.go b/apis/config/v1beta1/imageconfig_types.go index fddf1ef0..f06afda9 100644 --- a/apis/config/v1beta1/imageconfig_types.go +++ b/apis/config/v1beta1/imageconfig_types.go @@ -44,7 +44,7 @@ type ImagePolicy struct { ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"` - ImageNamespace string `json:"imageNamespace,omitempty"` + ImageNamespace *string `json:"imageNamespace,omitempty"` } //+kubebuilder:object:root=true diff --git a/apis/config/v1beta1/zz_generated.deepcopy.go b/apis/config/v1beta1/zz_generated.deepcopy.go index cb23b64a..e7903da9 100644 --- a/apis/config/v1beta1/zz_generated.deepcopy.go +++ b/apis/config/v1beta1/zz_generated.deepcopy.go @@ -31,7 +31,7 @@ func (in *DefaultImages) DeepCopyInto(out *DefaultImages) { in, out := &in.ImageComponents, &out.ImageComponents *out = make(ImageComponents, len(*in)) for key, val := range *in { - (*out)[key] = val + (*out)[key] = *val.DeepCopy() } } } @@ -49,7 +49,7 @@ func (in *DefaultImages) DeepCopy() *DefaultImages { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ImageComponent) DeepCopyInto(out *ImageComponent) { *out = *in - out.ImagePolicy = in.ImagePolicy + in.ImagePolicy.DeepCopyInto(&out.ImagePolicy) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageComponent. @@ -68,7 +68,7 @@ func (in ImageComponents) DeepCopyInto(out *ImageComponents) { in := &in *out = make(ImageComponents, len(*in)) for key, val := range *in { - (*out)[key] = val + (*out)[key] = *val.DeepCopy() } } } @@ -97,7 +97,7 @@ func (in *ImageConfig) DeepCopyInto(out *ImageConfig) { *out = new(DefaultImages) (*in).DeepCopyInto(*out) } - out.ImagePolicy = in.ImagePolicy + in.ImagePolicy.DeepCopyInto(&out.ImagePolicy) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageConfig. @@ -122,6 +122,11 @@ func (in *ImageConfig) DeepCopyObject() runtime.Object { func (in *ImagePolicy) DeepCopyInto(out *ImagePolicy) { *out = *in out.ImagePullSecret = in.ImagePullSecret + if in.ImageNamespace != nil { + in, out := &in.ImageNamespace, &out.ImageNamespace + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImagePolicy. diff --git a/pkg/images/images.go b/pkg/images/images.go index 91a132c5..bd575589 100644 --- a/pkg/images/images.go +++ b/pkg/images/images.go @@ -78,43 +78,42 @@ func IsHCDVersionSupported(version string) bool { return validVersions.MatchString(version) } -func stripRegistry(image string) string { +func splitRegistry(image string) (registry string, imageNoRegistry string) { comps := strings.Split(image, "/") if len(comps) > 1 && (strings.Contains(comps[0], ".") || strings.Contains(comps[0], ":")) { - return strings.Join(comps[1:], "/") + return comps[0], strings.Join(comps[1:], "/") } else { - return image + return "", image } } -func applyNamespaceOverride(image string) string { - namespace := GetImageConfig().ImageNamespace - - if namespace == "" { - return image +// applyNamespaceOverride takes only input without registry +func applyNamespaceOverride(imageNoRegistry string) string { + if GetImageConfig().ImageNamespace == nil { + return imageNoRegistry } - // It can be first or second.. - imageNoRegistry := stripRegistry(image) + namespace := *GetImageConfig().ImageNamespace + comps := strings.Split(imageNoRegistry, "/") if len(comps) > 1 { noNamespace := strings.Join(comps[1:], "/") + if namespace == "" { + return noNamespace + } return fmt.Sprintf("%s/%s", namespace, noNamespace) } else { - return image // We can't process this correctly, we only have 1 component + // We can't process this correctly, we only have 1 component. We do not support a case where the original image has no registry and no namespace. + return imageNoRegistry } } -func applyDefaultRegistryOverride(customRegistry, image string) string { - customRegistry = strings.TrimSuffix(customRegistry, "/") - +func applyDefaultRegistryOverride(customRegistry, imageNoRegistry string) string { if customRegistry == "" { - return image - } else { - imageNoRegistry := stripRegistry(image) - return fmt.Sprintf("%s/%s", customRegistry, imageNoRegistry) + return imageNoRegistry } + return fmt.Sprintf("%s/%s", customRegistry, imageNoRegistry) } func getRegistryOverride(imageType string) string { @@ -136,9 +135,23 @@ func getRegistryOverride(imageType string) string { } func applyOverrides(imageType, image string) string { - registry := getRegistryOverride(imageType) + registryOverride := getRegistryOverride(imageType) + registryOverride = strings.TrimSuffix(registryOverride, "/") + registry, imageNoRegistry := splitRegistry(image) + + if registryOverride == "" && GetImageConfig().ImageNamespace == nil { + return image + } + + if GetImageConfig().ImageNamespace != nil { + imageNoRegistry = applyNamespaceOverride(imageNoRegistry) + } + + if registryOverride != "" { + return applyDefaultRegistryOverride(registryOverride, imageNoRegistry) + } - return applyDefaultRegistryOverride(registry, image) + return applyDefaultRegistryOverride(registry, imageNoRegistry) } func GetImageConfig() *configv1beta1.ImageConfig { diff --git a/pkg/images/images_test.go b/pkg/images/images_test.go index 582f4ee8..cbc473a8 100644 --- a/pkg/images/images_test.go +++ b/pkg/images/images_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" + "k8s.io/utils/ptr" configv1beta1 "github.com/k8ssandra/cass-operator/apis/config/v1beta1" ) @@ -203,7 +204,7 @@ func TestRepositoryAndNamespaceOverride(t *testing.T) { assert.NoError(err) assert.Equal("ghcr.io/datastax/dse-mgmtapi-6_8:6.8.44", path) - imageConfig.ImageNamespace = "enterprise" + imageConfig.ImageNamespace = ptr.To[string]("enterprise") path, err = GetCassandraImage("dse", "6.8.44") assert.NoError(err) assert.Equal("ghcr.io/enterprise/dse-mgmtapi-6_8:6.8.44", path) @@ -211,7 +212,7 @@ func TestRepositoryAndNamespaceOverride(t *testing.T) { imageConfig = configv1beta1.ImageConfig{} imageConfig.Images = &configv1beta1.Images{} imageConfig.DefaultImages = &configv1beta1.DefaultImages{} - imageConfig.ImageNamespace = "enterprise" + imageConfig.ImageNamespace = ptr.To[string]("enterprise") path, err = GetCassandraImage("dse", "6.8.44") assert.NoError(err) assert.Equal("enterprise/dse-mgmtapi-6_8:6.8.44", path) @@ -228,10 +229,16 @@ func TestRepositoryAndNamespaceOverride(t *testing.T) { path, err = GetCassandraImage("dse", "6.8.44") assert.NoError(err) assert.Equal("cr.dtsx.io/datastax/dse-mgmtapi-6_8:6.8.44", path) - imageConfig.ImageNamespace = "internal" + + imageConfig.ImageNamespace = ptr.To[string]("internal") path, err = GetCassandraImage("dse", "6.8.44") assert.NoError(err) assert.Equal("cr.dtsx.io/internal/dse-mgmtapi-6_8:6.8.44", path) + + imageConfig.ImageNamespace = ptr.To[string]("") + path, err = GetCassandraImage("dse", "6.8.44") + assert.NoError(err) + assert.Equal("cr.dtsx.io/dse-mgmtapi-6_8:6.8.44", path) } func TestImageConfigByteParsing(t *testing.T) {