diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 417d7057b04..f9ffea1f7f2 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -286,9 +286,11 @@ func main() { if err := viper.ReadInConfig(); err != nil { // Handle errors reading the config file setupLog.Info("unable to read in config, errors ignored") } + intctrlutil.ReloadRegistryConfig() setupLog.Info(fmt.Sprintf("config file: %s", viper.GetViper().ConfigFileUsed())) viper.OnConfigChange(func(e fsnotify.Event) { setupLog.Info(fmt.Sprintf("config file changed: %s", e.Name)) + intctrlutil.ReloadRegistryConfig() }) viper.WatchConfig() diff --git a/controllers/apps/component_controller_test.go b/controllers/apps/component_controller_test.go index 31a82de54f5..b45a0133dfd 100644 --- a/controllers/apps/component_controller_test.go +++ b/controllers/apps/component_controller_test.go @@ -2099,4 +2099,92 @@ var _ = Describe("Component Controller", func() { testImageUnchangedAfterNewReleasePublished(release) }) }) + + Context("with registry replace enabled", func() { + registry := "foo.bar" + setRegistryConfig := func() { + viper.Set(constant.CfgRegistries, intctrlutil.RegistriesConfig{ + DefaultRegistry: registry, + }) + intctrlutil.ReloadRegistryConfig() + } + + BeforeEach(func() { + createAllDefinitionObjects() + }) + + AfterEach(func() { + viper.Set(constant.CfgRegistries, nil) + intctrlutil.ReloadRegistryConfig() + }) + + It("replaces image registry", func() { + setRegistryConfig() + + createClusterObj(defaultCompName, compDefName, nil) + + itsKey := compKey + Eventually(testapps.CheckObj(&testCtx, itsKey, func(g Gomega, its *workloads.InstanceSet) { + // check the image + c := its.Spec.Template.Spec.Containers[0] + g.Expect(c.Image).To(HavePrefix(registry)) + })).Should(Succeed()) + }) + + It("handles running its and upgrade", func() { + createClusterObj(defaultCompName, compDefName, nil) + itsKey := compKey + Eventually(testapps.CheckObj(&testCtx, itsKey, func(g Gomega, its *workloads.InstanceSet) { + // check the image + c := its.Spec.Template.Spec.Containers[0] + g.Expect(c.Image).To(Equal(compVerObj.Spec.Releases[0].Images[c.Name])) + })).Should(Succeed()) + + setRegistryConfig() + By("trigger component reconcile") + now := time.Now().Format(time.RFC3339) + Expect(testapps.GetAndChangeObj(&testCtx, compKey, func(comp *kbappsv1.Component) { + comp.Annotations["now"] = now + })()).Should(Succeed()) + + Consistently(testapps.CheckObj(&testCtx, itsKey, func(g Gomega, its *workloads.InstanceSet) { + // check the image + c := its.Spec.Template.Spec.Containers[0] + g.Expect(c.Image).NotTo(HavePrefix(registry)) + })).Should(Succeed()) + + By("replaces registry when upgrading") + release := kbappsv1.ComponentVersionRelease{ + Name: "8.0.31", + ServiceVersion: "8.0.31", + Images: map[string]string{ + testapps.DefaultMySQLContainerName: "docker.io/apecloud/mysql:8.0.31", + }, + } + + By("publish a new release") + compVerKey := client.ObjectKeyFromObject(compVerObj) + Expect(testapps.GetAndChangeObj(&testCtx, compVerKey, func(compVer *kbappsv1.ComponentVersion) { + compVer.Spec.Releases = append(compVer.Spec.Releases, release) + compVer.Spec.CompatibilityRules[0].Releases = append(compVer.Spec.CompatibilityRules[0].Releases, release.Name) + })()).Should(Succeed()) + + By("update serviceversion in cluster") + Expect(testapps.GetAndChangeObj(&testCtx, clusterKey, func(cluster *kbappsv1.Cluster) { + cluster.Spec.ComponentSpecs[0].ServiceVersion = "8.0.31" + })()).Should(Succeed()) + + By("trigger component reconcile") + now = time.Now().Format(time.RFC3339) + Expect(testapps.GetAndChangeObj(&testCtx, compKey, func(comp *kbappsv1.Component) { + comp.Annotations["now"] = now + })()).Should(Succeed()) + + Eventually(testapps.CheckObj(&testCtx, itsKey, func(g Gomega, its *workloads.InstanceSet) { + // check the image + c := its.Spec.Template.Spec.Containers[0] + g.Expect(c.Image).To(HavePrefix(registry)) + })).Should(Succeed()) + }) + }) }) diff --git a/controllers/apps/suite_test.go b/controllers/apps/suite_test.go index 17992859076..40e69fc601a 100644 --- a/controllers/apps/suite_test.go +++ b/controllers/apps/suite_test.go @@ -179,7 +179,7 @@ var _ = BeforeSuite(func() { Expect(err).ToNot(HaveOccurred()) viper.SetDefault("CERT_DIR", "/tmp/k8s-webhook-server/serving-certs") - viper.SetDefault(constant.KBToolsImage, "apecloud/kubeblocks-tools:latest") + viper.SetDefault(constant.KBToolsImage, "docker.io/apecloud/kubeblocks-tools:latest") viper.SetDefault("PROBE_SERVICE_PORT", 3501) viper.SetDefault("PROBE_SERVICE_LOG_LEVEL", "info") viper.SetDefault("CM_NAMESPACE", "default") diff --git a/controllers/apps/transformer_component_workload.go b/controllers/apps/transformer_component_workload.go index 50d17998e07..8d6c284a5a4 100644 --- a/controllers/apps/transformer_component_workload.go +++ b/controllers/apps/transformer_component_workload.go @@ -145,6 +145,16 @@ func (t *componentWorkloadTransformer) reconcileWorkload(synthesizedComp *compon protoITS.Spec.Template.Labels = intctrlutil.MergeMetadataMaps(runningITS.Spec.Template.Labels, synthesizedComp.DynamicLabels) } + // if runningITS already exists, the image changes in protoITS will be + // rollback to the original image in `checkNRollbackProtoImages`. + // So changing registry configs won't affect existing clusters. + for i, container := range protoITS.Spec.Template.Spec.Containers { + protoITS.Spec.Template.Spec.Containers[i].Image = intctrlutil.ReplaceImageRegistry(container.Image) + } + for i, container := range protoITS.Spec.Template.Spec.InitContainers { + protoITS.Spec.Template.Spec.InitContainers[i].Image = intctrlutil.ReplaceImageRegistry(container.Image) + } + buildInstanceSetPlacementAnnotation(comp, protoITS) // build configuration template annotations to workload diff --git a/controllers/extensions/addon_controller_stages.go b/controllers/extensions/addon_controller_stages.go index 9e37c95514a..8f455305685 100644 --- a/controllers/extensions/addon_controller_stages.go +++ b/controllers/extensions/addon_controller_stages.go @@ -514,7 +514,7 @@ func setInitContainer(addon *extensionsv1alpha1.Addon, helmJobPodSpec *corev1.Po } copyChartsContainer := corev1.Container{ Name: "copy-charts", - Image: addon.Spec.Helm.ChartsImage, + Image: intctrlutil.ReplaceImageRegistry(addon.Spec.Helm.ChartsImage), Command: []string{"sh", "-c", fmt.Sprintf("cp %s/* /mnt/charts", fromPath)}, VolumeMounts: []corev1.VolumeMount{ { diff --git a/go.mod b/go.mod index 92b562bb37c..d91a822c579 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/charmbracelet/keygen v0.5.1 github.com/clbanning/mxj/v2 v2.5.7 github.com/containers/common v0.55.4 + github.com/distribution/reference v0.6.0 github.com/docker/docker v25.0.6+incompatible github.com/evanphx/json-patch v5.6.0+incompatible github.com/fasthttp/router v1.4.20 @@ -104,7 +105,6 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/distribution/reference v0.5.0 // indirect github.com/docker/cli v25.0.1+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker-credential-helpers v0.8.0 // indirect diff --git a/go.sum b/go.sum index 7de2e547ae1..850f8b206fe 100644 --- a/go.sum +++ b/go.sum @@ -192,8 +192,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI= -github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/cli v25.0.1+incompatible h1:mFpqnrS6Hsm3v1k7Wa/BO23oz0k121MTbTO1lpcGSkU= github.com/docker/cli v25.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= diff --git a/pkg/constant/viper_config.go b/pkg/constant/viper_config.go index d3b7fe6e1ab..a99fd6ccb3f 100644 --- a/pkg/constant/viper_config.go +++ b/pkg/constant/viper_config.go @@ -52,4 +52,6 @@ const ( CfgKBReconcileWorkers = "KUBEBLOCKS_RECONCILE_WORKERS" CfgClientQPS = "CLIENT_QPS" CfgClientBurst = "CLIENT_BURST" + + CfgRegistries = "registries" ) diff --git a/pkg/controllerutil/image_util.go b/pkg/controllerutil/image_util.go new file mode 100644 index 00000000000..d1e90fab2ed --- /dev/null +++ b/pkg/controllerutil/image_util.go @@ -0,0 +1,202 @@ +/* +Copyright (C) 2022-2024 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package controllerutil + +import ( + // Import these crypto algorithm so that the image parser can work with digest + _ "crypto/sha256" + _ "crypto/sha512" + "fmt" + "strings" + "sync" + + "github.com/distribution/reference" + "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/apecloud/kubeblocks/pkg/constant" + viper "github.com/apecloud/kubeblocks/pkg/viperx" +) + +var imageLogger = log.Log.WithName("ImageUtil") + +type RegistryConfig struct { + From string `mapstructure:"from"` + To string `mapstructure:"to"` + RegistryDefaultNamespace string `mapstructure:"registryDefaultNamespace"` + + // Quote from https://docs.docker.com/reference/cli/docker/image/tag/ + // > While the OCI Distribution Specification supports more than two slash-separated components, + // > most registries only support two slash-separated components. + // > For Docker's public registry, the path format is as follows: + // > [NAMESPACE/]REPOSITORY: The first, optional component is typically a user's or an organization's + // > namespace. The second, mandatory component is the repository name. When the namespace is + // > not present, Docker uses `library` as the default namespace. + // + // So if there are more than two components (like `a/b` as a namespace), specify them both, + // or they won't be matched. Note empty namespace is legal too. + // + // key is the orignal namespace, value is the new namespace + NamespaceMapping map[string]string `mapstructure:"namespaceMapping"` +} + +type RegistriesConfig struct { + DefaultRegistry string `mapstructure:"defaultRegistry"` + DefaultNamespace string `mapstructure:"defaultNamespace"` + RegistryConfig []RegistryConfig `mapstructure:"registryConfig"` +} + +// this lock protects r/w to this variable itself, +// not the data it points to +var registriesConfigMutex sync.RWMutex +var registriesConfig = &RegistriesConfig{} + +func GetRegistriesConfig() *RegistriesConfig { + registriesConfigMutex.RLock() + defer registriesConfigMutex.RUnlock() + + // this will return a copy of the pointer + return registriesConfig +} + +func ReloadRegistryConfig() { + registriesConfigMutex.Lock() + defer registriesConfigMutex.Unlock() + + registriesConfig = &RegistriesConfig{} + if err := viper.UnmarshalKey(constant.CfgRegistries, ®istriesConfig); err != nil { + panic(err) + } + + for _, registry := range registriesConfig.RegistryConfig { + if len(registry.From) == 0 { + panic("from can't be empty") + } + + if len(registry.To) == 0 { + panic("to can't be empty") + } + } + + // since the use of kb tools image is widespread, set viper value here so that we don't need + // to replace it every time + viper.Set(constant.KBToolsImage, ReplaceImageRegistry(viper.GetString(constant.KBToolsImage))) + + imageLogger.Info("registriesConfig reloaded", "registriesConfig", registriesConfig) +} + +// For a detailed explanation of an image's format, see: +// https://pkg.go.dev/github.com/distribution/reference + +// if registry is omitted, the default (docker hub) will be added. +// if namespace is omiited when using docker hub, the default namespace (library) will be added. +func parseImageName(image string) ( + host string, namespace string, repository string, remainder string, err error, +) { + named, err := reference.ParseNormalizedNamed(image) + if err != nil { + imageLogger.Error(err, "parse image failed, the image remains unchanged", "image", image) + return + } + + tagged, ok := named.(reference.Tagged) + if ok { + remainder += ":" + tagged.Tag() + } + + digested, ok := named.(reference.Digested) + if ok { + remainder += "@" + digested.Digest().String() + } + + host = reference.Domain(named) + + pathSplit := strings.Split(reference.Path(named), "/") + if len(pathSplit) > 1 { + namespace = strings.Join(pathSplit[:len(pathSplit)-1], "/") + } + repository = pathSplit[len(pathSplit)-1] + + return +} + +func ReplaceImageRegistry(image string) string { + registry, namespace, repository, remainder, err := parseImageName(image) + // if parse has failed, return the original image. Since k8s will always error an invalid image, we + // don't need to deal with the error here + if err != nil { + return image + } + registriesConfigCopy := GetRegistriesConfig() + + chooseRegistry := func() string { + if registriesConfigCopy.DefaultRegistry != "" { + return registriesConfigCopy.DefaultRegistry + } else { + return registry + } + } + + chooseNamespace := func() *string { + if registriesConfigCopy.DefaultNamespace != "" { + return ®istriesConfigCopy.DefaultNamespace + } else { + return &namespace + } + } + + var dstRegistry string + var dstNamespace *string + for _, registryMapping := range registriesConfigCopy.RegistryConfig { + if registryMapping.From == registry { + dstRegistry = registryMapping.To + + for orig, new := range registryMapping.NamespaceMapping { + if namespace == orig { + dstNamespace = &new + break + } + } + + if dstNamespace == nil { + if registryMapping.RegistryDefaultNamespace != "" { + dstNamespace = ®istryMapping.RegistryDefaultNamespace + } else { + dstNamespace = &namespace + } + } + + break + } + } + + // no match in registriesConf.Registries + if dstRegistry == "" { + dstRegistry = chooseRegistry() + } + + if dstNamespace == nil { + dstNamespace = chooseNamespace() + } + + if *dstNamespace == "" { + return fmt.Sprintf("%v/%v%v", dstRegistry, repository, remainder) + } + return fmt.Sprintf("%v/%v/%v%v", dstRegistry, *dstNamespace, repository, remainder) +} diff --git a/pkg/controllerutil/image_util_test.go b/pkg/controllerutil/image_util_test.go new file mode 100644 index 00000000000..2f3ea8c1801 --- /dev/null +++ b/pkg/controllerutil/image_util_test.go @@ -0,0 +1,162 @@ +/* +Copyright (C) 2022-2024 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package controllerutil + +import ( + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("image util test", func() { + imageList := [][]string{ + // original image name, registry, namespace, image name, tag and digest + {"busybox", "docker.io", "library", "busybox", ""}, + {"apecloud/busybox:1.28", "docker.io", "apecloud", "busybox", ":1.28"}, + {"foo.io/a/b/busybox", "foo.io", "a/b", "busybox", ""}, + { + "registry.k8s.io/pause:latest@sha256:1ff6c18fbef2045af6b9c16bf034cc421a29027b800e4f9b68ae9b1cb3e9ae07", + "registry.k8s.io", "", "pause", ":latest@sha256:1ff6c18fbef2045af6b9c16bf034cc421a29027b800e4f9b68ae9b1cb3e9ae07"}, + } + + AfterEach(func() { + // reset config + registriesConfig = &RegistriesConfig{} + }) + + It("parses image", func() { + for _, image := range imageList { + host, ns, repository, remainder, err := parseImageName(image[0]) + // fmt.Println(host, ns, repository, remainder) + Expect(err).NotTo(HaveOccurred()) + Expect(host).To(Equal(image[1])) + Expect(ns).To(Equal(image[2])) + Expect(repository).To(Equal(image[3])) + Expect(remainder).To(Equal(image[4])) + } + + _, _, _, _, err := parseImageName("/invalid") + Expect(err).To(HaveOccurred()) + }) + + It("only expands image when config does not exist", func() { + for _, image := range imageList { + newImage := ReplaceImageRegistry(image[0]) + if image[2] == "" { + Expect(newImage).To(Equal(fmt.Sprintf("%v/%v%v", image[1], image[3], image[4]))) + } else { + Expect(newImage).To(Equal(fmt.Sprintf("%v/%v/%v%v", image[1], image[2], image[3], image[4]))) + } + } + }) + + It("replaces image when default config exists", func() { + registriesConfig = &RegistriesConfig{ + DefaultRegistry: "foo.bar", + } + for _, image := range imageList { + newImage := ReplaceImageRegistry(image[0]) + if image[2] == "" { + Expect(newImage).To(Equal(fmt.Sprintf("%v/%v%v", "foo.bar", image[3], image[4]))) + } else { + Expect(newImage).To(Equal(fmt.Sprintf("%v/%v/%v%v", "foo.bar", image[2], image[3], image[4]))) + } + } + + registriesConfig = &RegistriesConfig{ + DefaultRegistry: "foo.bar", + DefaultNamespace: "test", + } + for _, image := range imageList { + newImage := ReplaceImageRegistry(image[0]) + Expect(newImage).To(Equal(fmt.Sprintf("%v/%v/%v%v", "foo.bar", "test", image[3], image[4]))) + } + }) + + It("replaces image when registry/namespace config exists", func() { + registriesConfig = &RegistriesConfig{ + DefaultRegistry: "foo.bar", + DefaultNamespace: "default", + RegistryConfig: []RegistryConfig{ + { + From: "docker.io", + To: "bar.io", + NamespaceMapping: map[string]string{ + "library": "foo", + "apecloud": "", + }, + }, + { + From: "foo.io", + To: "foo.bar", + NamespaceMapping: map[string]string{ + "a/b": "foo", + }, + }, + { + From: "registry.k8s.io", + To: "k8s.bar", + NamespaceMapping: map[string]string{ + "": "k8s", + }, + }, + }, + } + expectedImage := []string{ + "bar.io/foo/busybox", + "bar.io/busybox:1.28", + "foo.bar/foo/busybox", + "k8s.bar/k8s/pause:latest@sha256:1ff6c18fbef2045af6b9c16bf034cc421a29027b800e4f9b68ae9b1cb3e9ae07", + } + for i, image := range imageList { + newImage := ReplaceImageRegistry(image[0]) + Expect(newImage).To(Equal(expectedImage[i])) + } + }) + + It("replaces image w/ or w/o RegistryDefaultNamespace", func() { + registriesConfig = &RegistriesConfig{ + DefaultRegistry: "foo.bar", + DefaultNamespace: "default", + RegistryConfig: []RegistryConfig{ + { + From: "docker.io", + To: "bar.io", + RegistryDefaultNamespace: "docker", + }, + { + From: "foo.io", + To: "foo.bar", + }, + }, + } + expectedImage := []string{ + "bar.io/docker/busybox", + "bar.io/docker/busybox:1.28", + "foo.bar/a/b/busybox", + "foo.bar/default/pause:latest@sha256:1ff6c18fbef2045af6b9c16bf034cc421a29027b800e4f9b68ae9b1cb3e9ae07", + } + for i, image := range imageList { + newImage := ReplaceImageRegistry(image[0]) + Expect(newImage).To(Equal(expectedImage[i])) + } + }) +}) diff --git a/pkg/dataprotection/backup/deleter.go b/pkg/dataprotection/backup/deleter.go index f22eb310c07..545cbd9e42e 100644 --- a/pkg/dataprotection/backup/deleter.go +++ b/pkg/dataprotection/backup/deleter.go @@ -325,10 +325,11 @@ func (d *Deleter) doPreDeleteAction( if d.actionSet != nil { envVars = append(envVars, d.actionSet.Spec.Env...) } + image := common.Expand(preDeleteAction.Image, common.MappingFuncFor(utils.CovertEnvToMap(envVars))) container := corev1.Container{ Name: backup.Name, Command: preDeleteAction.Command, - Image: common.Expand(preDeleteAction.Image, common.MappingFuncFor(utils.CovertEnvToMap(envVars))), + Image: ctrlutil.ReplaceImageRegistry(image), Env: envVars, ImagePullPolicy: corev1.PullPolicy(viper.GetString(constant.KBImagePullPolicy)), SecurityContext: &corev1.SecurityContext{ diff --git a/pkg/dataprotection/backup/request.go b/pkg/dataprotection/backup/request.go index 47ed59fd25d..ca86bc9f4f1 100644 --- a/pkg/dataprotection/backup/request.go +++ b/pkg/dataprotection/backup/request.go @@ -405,10 +405,11 @@ func (r *Request) BuildJobActionPodSpec(targetPod *corev1.Pod, if err != nil { return nil, err } + // expand the image value with the env variables. + image := common.Expand(job.Image, common.MappingFuncFor(utils.CovertEnvToMap(env))) container := corev1.Container{ - Name: name, - // expand the image value with the env variables. - Image: common.Expand(job.Image, common.MappingFuncFor(utils.CovertEnvToMap(env))), + Name: name, + Image: intctrlutil.ReplaceImageRegistry(image), Command: job.Command, Env: env, EnvFrom: targetPod.Spec.Containers[0].EnvFrom, diff --git a/pkg/dataprotection/restore/builder.go b/pkg/dataprotection/restore/builder.go index 54f09d39343..4596f327d93 100644 --- a/pkg/dataprotection/restore/builder.go +++ b/pkg/dataprotection/restore/builder.go @@ -338,6 +338,8 @@ func (r *restoreJobBuilder) build() *batchv1.Job { // 2. set restore container r.specificVolumeMounts = append(r.specificVolumeMounts, r.commonVolumeMounts...) + // expand the image value with the env variables. + image := common.Expand(r.image, common.MappingFuncFor(utils.CovertEnvToMap(r.env))) container := corev1.Container{ Name: Restore, Resources: r.restore.Spec.ContainerResources, @@ -347,7 +349,7 @@ func (r *restoreJobBuilder) build() *batchv1.Job { Command: r.command, Args: r.args, // expand the image value with the env variables. - Image: common.Expand(r.image, common.MappingFuncFor(utils.CovertEnvToMap(r.env))), + Image: intctrlutil.ReplaceImageRegistry(image), ImagePullPolicy: corev1.PullIfNotPresent, } diff --git a/pkg/dataprotection/utils/backuprepo.go b/pkg/dataprotection/utils/backuprepo.go index da9efdd7bf2..a79b77336f1 100644 --- a/pkg/dataprotection/utils/backuprepo.go +++ b/pkg/dataprotection/utils/backuprepo.go @@ -148,7 +148,7 @@ func injectDatasafedInstaller(podSpec *corev1.PodSpec) { } initContainer := corev1.Container{ Name: "dp-copy-datasafed", - Image: datasafedImage, + Image: intctrlutil.ReplaceImageRegistry(datasafedImage), ImagePullPolicy: corev1.PullPolicy(viper.GetString(constant.KBImagePullPolicy)), Command: []string{"/bin/sh", "-c", fmt.Sprintf("/scripts/install-datasafed.sh %s", datasafedBinMountPath)}, VolumeMounts: []corev1.VolumeMount{sharedVolumeMount}, diff --git a/pkg/operations/switchover_util.go b/pkg/operations/switchover_util.go index 32297fb7aa4..b1ffd758d3d 100644 --- a/pkg/operations/switchover_util.go +++ b/pkg/operations/switchover_util.go @@ -212,7 +212,7 @@ func renderSwitchoverCmdJob(ctx context.Context, Containers: []corev1.Container{ { Name: KBSwitchoverJobContainerName, - Image: switchoverSpec.Exec.Image, + Image: intctrlutil.ReplaceImageRegistry(switchoverSpec.Exec.Image), ImagePullPolicy: corev1.PullIfNotPresent, Command: switchoverSpec.Exec.Command, Args: switchoverSpec.Exec.Args, diff --git a/pkg/viperx/viperx.go b/pkg/viperx/viperx.go index 620fc2ae4ba..113bc60582e 100644 --- a/pkg/viperx/viperx.go +++ b/pkg/viperx/viperx.go @@ -67,6 +67,18 @@ func GetDuration(key string) time.Duration { return rCall(key, viper.GetDuration) } +func Unmarshal(rawVal interface{}, opts ...viper.DecoderConfigOption) error { + lock.RLock() + defer lock.RUnlock() + return viper.Unmarshal(rawVal, opts...) +} + +func UnmarshalKey(key string, rawVal interface{}, opts ...viper.DecoderConfigOption) error { + lock.RLock() + defer lock.RUnlock() + return viper.UnmarshalKey(key, rawVal, opts...) +} + func AllSettings() map[string]interface{} { lock.RLock() defer lock.RUnlock()