diff --git a/apis/apps/v1alpha1/config_meta.go b/apis/apps/v1alpha1/config_meta.go index 40a6ceaef56..6371c17c056 100644 --- a/apis/apps/v1alpha1/config_meta.go +++ b/apis/apps/v1alpha1/config_meta.go @@ -84,6 +84,10 @@ func (configSpec *ComponentConfigSpec) InjectEnvEnabled() bool { return len(configSpec.AsEnvFrom) > 0 || len(configSpec.InjectEnvTo) > 0 } +func (configSpec *ComponentConfigSpec) ToSecret() bool { + return configSpec.AsSecret != nil && *configSpec.AsSecret +} + func (configSpec *ComponentConfigSpec) ContainersInjectedTo() []string { if len(configSpec.InjectEnvTo) != 0 { return configSpec.InjectEnvTo diff --git a/apis/apps/v1alpha1/type.go b/apis/apps/v1alpha1/type.go index 886a47f20e1..37a50e3a927 100644 --- a/apis/apps/v1alpha1/type.go +++ b/apis/apps/v1alpha1/type.go @@ -62,9 +62,9 @@ type ComponentTemplateSpec struct { // template will be mounted to the corresponding volume. Must be a DNS_LABEL name. // The volume name must be defined in podSpec.containers[*].volumeMounts. // - // +kubebuilder:validation:Required // +kubebuilder:validation:MaxLength=63 // +kubebuilder:validation:Pattern:=`^[a-z]([a-z0-9\-]*[a-z0-9])?$` + // +optional VolumeName string `json:"volumeName"` // The operator attempts to set default file permissions for scripts (0555) and configurations (0444). @@ -198,6 +198,11 @@ type ComponentConfigSpec struct { // +listType=set // +optional ReRenderResourceTypes []RerenderResourceType `json:"reRenderResourceTypes,omitempty"` + + // Whether to store the final rendered parameters as a secret. + // + // +optional + AsSecret *bool `json:"asSecret,omitempty"` } // RerenderResourceType defines the resource requirements for a component. diff --git a/apis/apps/v1alpha1/zz_generated.deepcopy.go b/apis/apps/v1alpha1/zz_generated.deepcopy.go index f86aaa75efd..72a991a5dd0 100644 --- a/apis/apps/v1alpha1/zz_generated.deepcopy.go +++ b/apis/apps/v1alpha1/zz_generated.deepcopy.go @@ -1163,6 +1163,11 @@ func (in *ComponentConfigSpec) DeepCopyInto(out *ComponentConfigSpec) { *out = make([]RerenderResourceType, len(*in)) copy(*out, *in) } + if in.AsSecret != nil { + in, out := &in.AsSecret, &out.AsSecret + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentConfigSpec. diff --git a/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml b/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml index bd9064e8e9f..6c4450f3881 100644 --- a/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml +++ b/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml @@ -138,6 +138,10 @@ spec: type: string type: array x-kubernetes-list-type: set + asSecret: + description: Whether to store the final rendered parameters + as a secret. + type: boolean constraintRef: description: Specifies the name of the referenced configuration constraints object. @@ -289,7 +293,6 @@ spec: type: string required: - name - - volumeName type: object type: array x-kubernetes-list-map-keys: @@ -11638,7 +11641,6 @@ spec: type: string required: - name - - volumeName type: object type: array x-kubernetes-list-map-keys: diff --git a/config/crd/bases/apps.kubeblocks.io_configurations.yaml b/config/crd/bases/apps.kubeblocks.io_configurations.yaml index 861788bc31a..90310656942 100644 --- a/config/crd/bases/apps.kubeblocks.io_configurations.yaml +++ b/config/crd/bases/apps.kubeblocks.io_configurations.yaml @@ -135,6 +135,10 @@ spec: type: string type: array x-kubernetes-list-type: set + asSecret: + description: Whether to store the final rendered parameters + as a secret. + type: boolean constraintRef: description: Specifies the name of the referenced configuration constraints object. @@ -286,7 +290,6 @@ spec: type: string required: - name - - volumeName type: object importTemplateRef: description: |- diff --git a/controllers/apps/configuration/configuration_controller_test.go b/controllers/apps/configuration/configuration_controller_test.go index 49bfe4c4c6e..9e6d8b28af8 100644 --- a/controllers/apps/configuration/configuration_controller_test.go +++ b/controllers/apps/configuration/configuration_controller_test.go @@ -24,7 +24,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - + corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" @@ -114,4 +114,75 @@ var _ = Describe("Configuration Controller", func() { }, time.Second*60, time.Second*1).Should(Succeed()) }) }) + + Context("When updating configuration with injectEnvTo", func() { + It("Should reconcile success", func() { + _, _, clusterObj, componentObj, synthesizedComp := mockReconcileResource() + synthesizedComp.ConfigTemplates[0].AsSecret = cfgutil.ToPointer(true) + synthesizedComp.ConfigTemplates[0].InjectEnvTo = []string{"mock-container"} + + cfgKey := client.ObjectKey{ + Name: core.GenerateComponentConfigurationName(clusterName, defaultCompName), + Namespace: testCtx.DefaultNamespace, + } + renderedKey := client.ObjectKey{ + Name: core.GetComponentCfgName(synthesizedComp.ClusterName, synthesizedComp.Name, synthesizedComp.ConfigTemplates[0].Name), + Namespace: testCtx.DefaultNamespace, + } + checkCfgStatus := func(phase appsv1alpha1.ConfigurationPhase) func() bool { + return func() bool { + cfg := &appsv1alpha1.Configuration{} + Expect(k8sClient.Get(ctx, cfgKey, cfg)).Should(Succeed()) + itemStatus := cfg.Status.GetItemStatus(configSpecName) + return itemStatus != nil && itemStatus.Phase == phase + } + } + + By("wait for configuration status to be init phase.") + Eventually(checkCfgStatus(appsv1alpha1.CInitPhase)).Should(BeFalse()) + Expect(initConfiguration(&configctrl.ResourceCtx{ + Client: k8sClient, + Context: ctx, + Namespace: testCtx.DefaultNamespace, + ClusterName: clusterName, + ComponentName: defaultCompName, + }, synthesizedComp, clusterObj, componentObj)).Should(Succeed()) + + Eventually(checkCfgStatus(appsv1alpha1.CFinishedPhase)).Should(BeTrue()) + + Eventually(testapps.CheckObjExists(&testCtx, renderedKey, &corev1.ConfigMap{}, false)).Should(Succeed()) + Eventually(testapps.CheckObjExists(&testCtx, client.ObjectKey{ + Name: core.GenerateEnvFromName(renderedKey.Name), + Namespace: renderedKey.Namespace, + }, &corev1.Secret{}, true)).Should(Succeed()) + + By("reconfiguring parameters.") + Eventually(testapps.GetAndChangeObj(&testCtx, cfgKey, func(cfg *appsv1alpha1.Configuration) { + cfg.Spec.GetConfigurationItem(configSpecName).ConfigFileParams = map[string]appsv1alpha1.ConfigParams{ + "my.cnf": { + Parameters: map[string]*string{ + "max_connections": cfgutil.ToPointer("1000"), + "gtid_mode": cfgutil.ToPointer("ON"), + }, + }, + } + })).Should(Succeed()) + + Eventually(func(g Gomega) { + cfg := &appsv1alpha1.Configuration{} + g.Expect(k8sClient.Get(ctx, cfgKey, cfg)).Should(Succeed()) + itemStatus := cfg.Status.GetItemStatus(configSpecName) + g.Expect(itemStatus).ShouldNot(BeNil()) + g.Expect(itemStatus.UpdateRevision).Should(BeEquivalentTo("2")) + g.Expect(itemStatus.Phase).Should(BeEquivalentTo(appsv1alpha1.CFinishedPhase)) + }, time.Second*60, time.Second*1).Should(Succeed()) + + Eventually(testapps.CheckObjExists(&testCtx, renderedKey, &corev1.ConfigMap{}, false)).Should(Succeed()) + Eventually(testapps.CheckObjExists(&testCtx, client.ObjectKey{ + Name: core.GenerateEnvFromName(renderedKey.Name), + Namespace: renderedKey.Namespace, + }, &corev1.Secret{}, true)).Should(Succeed()) + }) + + }) }) diff --git a/controllers/apps/configuration/configuration_test.go b/controllers/apps/configuration/configuration_test.go index 64342353d9a..9c5a21ea1c0 100644 --- a/controllers/apps/configuration/configuration_test.go +++ b/controllers/apps/configuration/configuration_test.go @@ -193,7 +193,8 @@ func cleanEnv() { testapps.ClearResources(&testCtx, generics.ConfigConstraintSignature, ml) // namespaced testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.ComponentSignature, true, inNS, ml) - testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.ConfigMapSignature, true, inNS, ml) + testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.ConfigMapSignature, true, inNS) + testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.SecretSignature, true, inNS) testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.InstanceSetSignature, true, inNS, ml) testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.ConfigurationSignature, false, inNS, ml) } diff --git a/controllers/apps/configuration/reconcile_task.go b/controllers/apps/configuration/reconcile_task.go index 77557471f5f..828ccf4db38 100644 --- a/controllers/apps/configuration/reconcile_task.go +++ b/controllers/apps/configuration/reconcile_task.go @@ -23,6 +23,7 @@ import ( "strconv" corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/core" @@ -56,6 +57,12 @@ func NewTask(item appsv1alpha1.ConfigurationItemDetail, status *appsv1alpha1.Con return core.MakeError("not found config spec: %s", item.Name) } if err := fetcher.ConfigMap(item.Name).Complete(); err != nil { + if !apierrors.IsNotFound(err) { + return err + } + if item.ConfigSpec.InjectEnvEnabled() && item.ConfigSpec.ToSecret() { + return syncSecretStatus(status) + } return err } // Do reconcile for config template @@ -74,6 +81,14 @@ func NewTask(item appsv1alpha1.ConfigurationItemDetail, status *appsv1alpha1.Con } } +func syncSecretStatus(status *appsv1alpha1.ConfigurationItemDetailStatus) error { + status.Phase = appsv1alpha1.CFinishedPhase + if status.LastDoneRevision == "" { + status.LastDoneRevision = status.UpdateRevision + } + return nil +} + func syncImpl(fetcher *Task, item appsv1alpha1.ConfigurationItemDetail, status *appsv1alpha1.ConfigurationItemDetailStatus, diff --git a/controllers/apps/configuration/suite_test.go b/controllers/apps/configuration/suite_test.go index a64d20c0512..358c8785733 100644 --- a/controllers/apps/configuration/suite_test.go +++ b/controllers/apps/configuration/suite_test.go @@ -27,6 +27,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/go-logr/logr" "go.uber.org/zap/zapcore" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" @@ -63,6 +64,8 @@ var _ = BeforeSuite(func() { logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true), func(o *zap.Options) { o.TimeEncoder = zapcore.ISO8601TimeEncoder })) + } else { + logf.SetLogger(logr.New(logf.NullLogSink{})) } ctx, cancel = context.WithCancel(context.TODO()) diff --git a/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml b/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml index bd9064e8e9f..6c4450f3881 100644 --- a/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml +++ b/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml @@ -138,6 +138,10 @@ spec: type: string type: array x-kubernetes-list-type: set + asSecret: + description: Whether to store the final rendered parameters + as a secret. + type: boolean constraintRef: description: Specifies the name of the referenced configuration constraints object. @@ -289,7 +293,6 @@ spec: type: string required: - name - - volumeName type: object type: array x-kubernetes-list-map-keys: @@ -11638,7 +11641,6 @@ spec: type: string required: - name - - volumeName type: object type: array x-kubernetes-list-map-keys: diff --git a/deploy/helm/crds/apps.kubeblocks.io_configurations.yaml b/deploy/helm/crds/apps.kubeblocks.io_configurations.yaml index 861788bc31a..90310656942 100644 --- a/deploy/helm/crds/apps.kubeblocks.io_configurations.yaml +++ b/deploy/helm/crds/apps.kubeblocks.io_configurations.yaml @@ -135,6 +135,10 @@ spec: type: string type: array x-kubernetes-list-type: set + asSecret: + description: Whether to store the final rendered parameters + as a secret. + type: boolean constraintRef: description: Specifies the name of the referenced configuration constraints object. @@ -286,7 +290,6 @@ spec: type: string required: - name - - volumeName type: object importTemplateRef: description: |- diff --git a/docs/developer_docs/api-reference/cluster.md b/docs/developer_docs/api-reference/cluster.md index a023352644a..431f5cfbe65 100644 --- a/docs/developer_docs/api-reference/cluster.md +++ b/docs/developer_docs/api-reference/cluster.md @@ -6120,6 +6120,18 @@ or cluster topology. Examples:

+ + +asSecret
+ +bool + + + +(Optional) +

Whether to store the final rendered parameters as a secret.

+ +

ComponentDefinitionSpec @@ -7944,6 +7956,7 @@ string +(Optional)

Refers to the volume name of PodTemplate. The configuration file produced through the configuration template will be mounted to the corresponding volume. Must be a DNS_LABEL name. The volume name must be defined in podSpec.containers[*].volumeMounts.

diff --git a/pkg/constant/labels.go b/pkg/constant/labels.go index 8fee18e22c4..7ce3eb8e56a 100644 --- a/pkg/constant/labels.go +++ b/pkg/constant/labels.go @@ -62,10 +62,10 @@ const ( ) // GetKBConfigMapWellKnownLabels returns the well-known labels for KB ConfigMap -func GetKBConfigMapWellKnownLabels(cmTplName, clusterDefName, clusterName, componentName string) map[string]string { +func GetKBConfigMapWellKnownLabels(cmTplName, componentDefName, clusterName, componentName string) map[string]string { return map[string]string{ CMTemplateNameLabelKey: cmTplName, - AppNameLabelKey: clusterDefName, + AppNameLabelKey: componentDefName, AppInstanceLabelKey: clusterName, KBAppComponentLabelKey: componentName, } diff --git a/pkg/controller/configuration/config_utils.go b/pkg/controller/configuration/config_utils.go index d58f8a4426f..4e638ba0bdf 100644 --- a/pkg/controller/configuration/config_utils.go +++ b/pkg/controller/configuration/config_utils.go @@ -41,9 +41,13 @@ import ( viper "github.com/apecloud/kubeblocks/pkg/viperx" ) -func createConfigObjects(cli client.Client, ctx context.Context, objs []client.Object) error { +func createConfigObjects(cli client.Client, ctx context.Context, objs []client.Object, excludeObjs []client.Object) error { for _, obj := range objs { + if slices.Contains(excludeObjs, obj) { + continue + } if err := cli.Create(ctx, obj, inDataContext()); err != nil { + if !apierrors.IsAlreadyExists(err) { return err } diff --git a/pkg/controller/configuration/envfrom_utils.go b/pkg/controller/configuration/envfrom_utils.go index 102b43dd890..03a2bbf1e45 100644 --- a/pkg/controller/configuration/envfrom_utils.go +++ b/pkg/controller/configuration/envfrom_utils.go @@ -42,13 +42,34 @@ func injectTemplateEnvFrom(cluster *appsv1alpha1.Cluster, component *component.S var err error var cm *corev1.ConfigMap + withEnvSource := func(asSecret bool) func(name string) corev1.EnvFromSource { + return func(name string) corev1.EnvFromSource { + if asSecret { + return corev1.EnvFromSource{ + SecretRef: &corev1.SecretEnvSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: name, + }}} + } + return corev1.EnvFromSource{ + ConfigMapRef: &corev1.ConfigMapEnvSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: name, + }}} + } + } + injectConfigmap := func(envMap map[string]string, configSpec appsv1alpha1.ComponentConfigSpec, cmName string) error { - envConfigMap, err := createEnvFromConfigmap(cluster, component.Name, configSpec, client.ObjectKeyFromObject(cm), envMap, ctx, cli) + envSourceObject, err := createOrUpdateResourceFromConfigTemplate(cluster, component, configSpec, client.ObjectKeyFromObject(cm), envMap, ctx, cli, true) if err != nil { return core.WrapError(err, "failed to generate env configmap[%s]", cmName) } - injectEnvFrom(podSpec.Containers, configSpec.ContainersInjectedTo(), envConfigMap.Name) - injectEnvFrom(podSpec.InitContainers, configSpec.ContainersInjectedTo(), envConfigMap.Name) + if configSpec.ToSecret() && configSpec.VolumeName != "" { + podSpec.Volumes = updateSecretVolumes(podSpec.Volumes, configSpec, envSourceObject, component) + } else { + injectEnvFrom(podSpec.Containers, configSpec.ContainersInjectedTo(), envSourceObject.GetName(), withEnvSource(configSpec.ToSecret())) + injectEnvFrom(podSpec.InitContainers, configSpec.ContainersInjectedTo(), envSourceObject.GetName(), withEnvSource(configSpec.ToSecret())) + } return nil } @@ -78,6 +99,23 @@ func injectTemplateEnvFrom(cluster *appsv1alpha1.Cluster, component *component.S return nil } +func updateSecretVolumes(volumes []corev1.Volume, configSpec appsv1alpha1.ComponentConfigSpec, secret client.Object, component *component.SynthesizedComponent) []corev1.Volume { + sets := configSetFromComponent(component.ConfigTemplates) + createFn := func(_ string) corev1.Volume { + return corev1.Volume{ + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: secret.GetName(), + DefaultMode: intctrlutil.BuildVolumeMode(sets, configSpec.ComponentTemplateSpec), + }, + }, + Name: configSpec.VolumeName, + } + } + volumes, _ = intctrlutil.CreateOrUpdateVolume(volumes, configSpec.VolumeName, createFn, nil) + return volumes +} + func getConfigConstraint(template appsv1alpha1.ComponentConfigSpec, cli client.Client, ctx context.Context) (*appsv1beta1.ConfigConstraintSpec, error) { ccKey := client.ObjectKey{ Namespace: "", @@ -127,27 +165,46 @@ func fetchConfigmap(localObjs []client.Object, cmName, namespace string, cli cli return cmObj, nil } -func createEnvFromConfigmap(cluster *appsv1alpha1.Cluster, componentName string, template appsv1alpha1.ComponentConfigSpec, originKey client.ObjectKey, envMap map[string]string, ctx context.Context, cli client.Client) (*corev1.ConfigMap, error) { +func createOrUpdateResourceFromConfigTemplate(cluster *appsv1alpha1.Cluster, component *component.SynthesizedComponent, template appsv1alpha1.ComponentConfigSpec, originKey client.ObjectKey, envMap map[string]string, ctx context.Context, cli client.Client, createOnly bool) (client.Object, error) { cmKey := client.ObjectKey{ Name: core.GenerateEnvFromName(originKey.Name), Namespace: originKey.Namespace, } - cm := &corev1.ConfigMap{} - err := cli.Get(ctx, cmKey, cm, inDataContext()) - if err == nil { - return cm, nil + + updateObjectMeta := func(obj client.Object) { + obj.SetLabels(constant.GetKBConfigMapWellKnownLabels(template.Name, component.CompDefName, component.ClusterName, component.Name)) + _ = intctrlutil.SetOwnerReference(cluster, obj) } - if !apierrors.IsNotFound(err) { - return nil, err + + if template.ToSecret() { + return updateOrCreateEnvObject(ctx, cli, &corev1.Secret{}, cmKey, func(c *corev1.Secret) { + c.StringData = envMap + updateObjectMeta(c) + }, createOnly) } - cm.Name = cmKey.Name - cm.Namespace = cmKey.Namespace - cm.Data = envMap - cm.Labels = constant.GetKBConfigMapWellKnownLabels(template.Name, cluster.Spec.ClusterDefRef, cluster.Name, componentName) - if err := intctrlutil.SetOwnerReference(cluster, cm); err != nil { - return nil, err + return updateOrCreateEnvObject(ctx, cli, &corev1.ConfigMap{}, cmKey, func(c *corev1.ConfigMap) { + c.Data = envMap + updateObjectMeta(c) + }, createOnly) +} + +func updateOrCreateEnvObject[T generics.Object, PT generics.PObject[T]](ctx context.Context, cli client.Client, obj PT, objKey client.ObjectKey, updater func(PT), createOnly bool) (client.Object, error) { + err := cli.Get(ctx, objKey, obj, inDataContext()) + switch { + case err != nil: + if !apierrors.IsNotFound(err) { + return nil, err + } + obj.SetName(objKey.Name) + obj.SetNamespace(objKey.Namespace) + updater(obj) + return obj, cli.Create(ctx, obj, inDataContext()) + case err == nil && createOnly: + return obj, nil + default: + updater(obj) + return obj, cli.Update(ctx, obj, inDataContext()) } - return cm, cli.Create(ctx, cm, inDataContext()) } func CheckEnvFrom(container *corev1.Container, cmName string) bool { @@ -156,22 +213,19 @@ func CheckEnvFrom(container *corev1.Container, cmName string) bool { if source.ConfigMapRef != nil && source.ConfigMapRef.Name == cmName { return true } + if source.SecretRef != nil && source.SecretRef.Name == cmName { + return true + } } return false } -func injectEnvFrom(containers []corev1.Container, injectEnvTo []string, cmName string) { +func injectEnvFrom(containers []corev1.Container, injectEnvTo []string, cmName string, fn func(string) corev1.EnvFromSource) { sets := cfgutil.NewSet(injectEnvTo...) for i := range containers { container := &containers[i] if sets.InArray(container.Name) && !CheckEnvFrom(container, cmName) { - container.EnvFrom = append(container.EnvFrom, - corev1.EnvFromSource{ - ConfigMapRef: &corev1.ConfigMapEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: cmName, - }}, - }) + container.EnvFrom = append(container.EnvFrom, fn(cmName)) } } } @@ -196,7 +250,7 @@ func fromConfigSpec(configSpec appsv1alpha1.ComponentConfigSpec, cm *corev1.Conf return keys } -func SyncEnvConfigmap(configSpec appsv1alpha1.ComponentConfigSpec, cmObj *corev1.ConfigMap, cc *appsv1beta1.ConfigConstraintSpec, cli client.Client, ctx context.Context) error { +func SyncEnvSourceObject(configSpec appsv1alpha1.ComponentConfigSpec, cmObj *corev1.ConfigMap, cc *appsv1beta1.ConfigConstraintSpec, cli client.Client, ctx context.Context, cluster *appsv1alpha1.Cluster, component *component.SynthesizedComponent) error { if !configSpec.InjectEnvEnabled() || cc == nil || cc.FileFormatConfig == nil { return nil } @@ -204,27 +258,8 @@ func SyncEnvConfigmap(configSpec appsv1alpha1.ComponentConfigSpec, cmObj *corev1 if err != nil { return err } - if len(envMap) == 0 { - return nil + if len(envMap) != 0 { + _, err = createOrUpdateResourceFromConfigTemplate(cluster, component, configSpec, client.ObjectKeyFromObject(cmObj), envMap, ctx, cli, false) } - - return updateEnvFromConfigmap(client.ObjectKeyFromObject(cmObj), envMap, cli, ctx) -} - -// TODO(leon) -func updateEnvFromConfigmap(origObj client.ObjectKey, envMap map[string]string, cli client.Client, ctx context.Context) error { - cmKey := client.ObjectKey{ - Name: core.GenerateEnvFromName(origObj.Name), - Namespace: origObj.Namespace, - } - cm := &corev1.ConfigMap{} - if err := cli.Get(ctx, cmKey, cm); err != nil { - return err - } - patch := client.MergeFrom(cm.DeepCopy()) - cm.Data = envMap - if err := cli.Patch(ctx, cm, patch); err != nil { - return err - } - return nil + return err } diff --git a/pkg/controller/configuration/envfrom_utils_test.go b/pkg/controller/configuration/envfrom_utils_test.go index 9e2d94fc18b..db4888192bd 100644 --- a/pkg/controller/configuration/envfrom_utils_test.go +++ b/pkg/controller/configuration/envfrom_utils_test.go @@ -29,6 +29,7 @@ import ( appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" "github.com/apecloud/kubeblocks/pkg/configuration/core" + cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" "github.com/apecloud/kubeblocks/pkg/controller/component" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" @@ -108,31 +109,52 @@ var _ = Describe("ConfigEnvFrom test", func() { testutil.WithCreateReturned(testutil.WithCreatedSucceedResult(), testutil.WithAnyTimes()), ) + synthesizeComp.ConfigTemplates[0].AsSecret = cfgutil.ToPointer(true) Expect(injectTemplateEnvFrom(cluster, synthesizeComp, podSpec, k8sMockClient.Client(), reqCtx.Ctx, nil)).ShouldNot(Succeed()) Expect(injectTemplateEnvFrom(cluster, synthesizeComp, podSpec, k8sMockClient.Client(), reqCtx.Ctx, nil)).Should(Succeed()) }) - It("should SyncEnvConfigmap success", func() { + It("should SyncEnvSourceObject success", func() { configSpec := compDef.Spec.Configs[0] configSpec.Keys = []string{"env-config"} + reqCtx := intctrlutil.RequestCtx{ + Ctx: ctx, + Log: logger, + } + comp, err := component.BuildComponent(cluster, &cluster.Spec.ComponentSpecs[0], nil, nil) + Expect(err).Should(Succeed()) + + synthesizeComp, err := component.BuildSynthesizedComponent(reqCtx, testCtx.Cli, cluster, compDef, comp) + Expect(err).Should(Succeed()) + cmObj := origCMObject.DeepCopy() cmObj.SetName(core.GenerateEnvFromName(origCMObject.Name)) k8sMockClient.MockGetMethod(testutil.WithGetReturned(testutil.WithConstructSimpleGetResult([]client.Object{ cmObj, configConstraint, }), testutil.WithAnyTimes())) - k8sMockClient.MockPatchMethod(testutil.WithFailed(core.MakeError("failed to patch"), testutil.WithTimes(1)), + k8sMockClient.MockUpdateMethod(testutil.WithFailed(core.MakeError("failed to patch"), testutil.WithTimes(1)), testutil.WithSucceed(), testutil.WithAnyTimes()) - Expect(SyncEnvConfigmap(configSpec, origCMObject, &configConstraint.Spec, k8sMockClient.Client(), ctx)).ShouldNot(Succeed()) - Expect(SyncEnvConfigmap(configSpec, origCMObject, &configConstraint.Spec, k8sMockClient.Client(), ctx)).Should(Succeed()) + Expect(SyncEnvSourceObject(configSpec, origCMObject, &configConstraint.Spec, k8sMockClient.Client(), ctx, cluster, synthesizeComp)).ShouldNot(Succeed()) + Expect(SyncEnvSourceObject(configSpec, origCMObject, &configConstraint.Spec, k8sMockClient.Client(), ctx, cluster, synthesizeComp)).Should(Succeed()) }) - It("SyncEnvConfigmap abnormal test", func() { + It("SyncEnvSourceObject abnormal test", func() { + reqCtx := intctrlutil.RequestCtx{ + Ctx: ctx, + Log: logger, + } + comp, err := component.BuildComponent(cluster, &cluster.Spec.ComponentSpecs[0], nil, nil) + Expect(err).Should(Succeed()) + + synthesizeComp, err := component.BuildSynthesizedComponent(reqCtx, testCtx.Cli, cluster, compDef, comp) + Expect(err).Should(Succeed()) + configSpec := compDef.Spec.Configs[0] configSpec.InjectEnvTo = nil - Expect(SyncEnvConfigmap(configSpec, origCMObject, &configConstraint.Spec, k8sMockClient.Client(), ctx)).Should(Succeed()) + Expect(SyncEnvSourceObject(configSpec, origCMObject, &configConstraint.Spec, k8sMockClient.Client(), ctx, cluster, synthesizeComp)).Should(Succeed()) configSpec.InjectEnvTo = nil cmObj := origCMObject.DeepCopy() @@ -141,11 +163,11 @@ var _ = Describe("ConfigEnvFrom test", func() { cmObj, configConstraint, }), testutil.WithAnyTimes())) - k8sMockClient.MockPatchMethod(testutil.WithSucceed(testutil.WithAnyTimes())) + k8sMockClient.MockUpdateMethod(testutil.WithSucceed(testutil.WithAnyTimes())) configSpec = compDef.Spec.Configs[0] configSpec.Keys = []string{"env-config", "not-exist"} - Expect(SyncEnvConfigmap(configSpec, origCMObject, &configConstraint.Spec, k8sMockClient.Client(), ctx)).Should(Succeed()) + Expect(SyncEnvSourceObject(configSpec, origCMObject, &configConstraint.Spec, k8sMockClient.Client(), ctx, cluster, synthesizeComp)).Should(Succeed()) }) }) }) diff --git a/pkg/controller/configuration/pipeline.go b/pkg/controller/configuration/pipeline.go index ace142b2d84..33f8d4eeb00 100644 --- a/pkg/controller/configuration/pipeline.go +++ b/pkg/controller/configuration/pipeline.go @@ -196,7 +196,7 @@ func (p *pipeline) UpdateConfigRelatedObject() *pipeline { if err := injectTemplateEnvFrom(p.ctx.Cluster, p.ctx.SynthesizedComponent, p.ctx.PodSpec, p.Client, p.Context, p.renderWrapper.renderedObjs); err != nil { return err } - return createConfigObjects(p.Client, p.Context, p.renderWrapper.renderedObjs) + return createConfigObjects(p.Client, p.Context, p.renderWrapper.renderedObjs, p.renderWrapper.renderedSecretObjs) } return p.Wrap(updateMeta) @@ -366,10 +366,13 @@ func (p *updatePipeline) UpdateConfigVersion(revision string) *updatePipeline { func (p *updatePipeline) Sync() *updatePipeline { return p.Wrap(func() error { if p.ConfigConstraintObj != nil && !p.isDone() { - if err := SyncEnvConfigmap(*p.configSpec, p.newCM, &p.ConfigConstraintObj.Spec, p.Client, p.Context); err != nil { + if err := SyncEnvSourceObject(*p.configSpec, p.newCM, &p.ConfigConstraintObj.Spec, p.Client, p.Context, p.ctx.Cluster, p.ctx.SynthesizedComponent); err != nil { return err } } + if p.configSpec.InjectEnvEnabled() && p.configSpec.ToSecret() { + return nil + } switch { case p.isDone(): return nil diff --git a/pkg/controller/configuration/template_wrapper.go b/pkg/controller/configuration/template_wrapper.go index c217a5aecc5..d88010ec6fc 100644 --- a/pkg/controller/configuration/template_wrapper.go +++ b/pkg/controller/configuration/template_wrapper.go @@ -52,6 +52,8 @@ type renderWrapper struct { templateAnnotations map[string]string renderedObjs []client.Object + renderedSecretObjs []client.Object + ctx context.Context cli client.Client cluster *appsv1alpha1.Cluster @@ -114,7 +116,7 @@ func (wrapper *renderWrapper) renderConfigTemplate(cluster *appsv1alpha1.Cluster // and does not update the ConfigMap objects in the subsequent reconfiguration process. // The subsequent reconfiguration process is handled by the Configuration controller. if origCMObj != nil { - wrapper.addVolumeMountMeta(configSpec.ComponentTemplateSpec, origCMObj, false) + wrapper.addVolumeMountMeta(configSpec.ComponentTemplateSpec, origCMObj, false, !configSpec.ToSecret()) continue } if configuration != nil { @@ -127,7 +129,7 @@ func (wrapper *renderWrapper) renderConfigTemplate(cluster *appsv1alpha1.Cluster if err := applyUpdatedParameters(item, newCMObj, configSpec, wrapper.cli, wrapper.ctx); err != nil { return err } - if err := wrapper.addRenderedObject(configSpec.ComponentTemplateSpec, newCMObj, configuration); err != nil { + if err := wrapper.addRenderedObject(configSpec.ComponentTemplateSpec, newCMObj, configuration, !configSpec.ToSecret()); err != nil { return err } if err := updateConfigMetaForCM(newCMObj, item, revision); err != nil { @@ -224,6 +226,9 @@ func (wrapper *renderWrapper) rerenderConfigTemplate(cluster *appsv1alpha1.Clust newCMObj.Data = newData } UpdateCMConfigSpecLabels(newCMObj, configSpec) + if configSpec.InjectEnvEnabled() && configSpec.ToSecret() { + wrapper.renderedSecretObjs = append(wrapper.renderedSecretObjs, newCMObj) + } return newCMObj, nil } @@ -235,7 +240,7 @@ func (wrapper *renderWrapper) renderScriptTemplate(cluster *appsv1alpha1.Cluster Name: cmName, Namespace: wrapper.cluster.Namespace}, generics.ToGVK(&corev1.ConfigMap{})) if object != nil { - wrapper.addVolumeMountMeta(templateSpec, object, false) + wrapper.addVolumeMountMeta(templateSpec, object, false, true) continue } @@ -244,14 +249,14 @@ func (wrapper *renderWrapper) renderScriptTemplate(cluster *appsv1alpha1.Cluster if err != nil { return err } - if err := wrapper.addRenderedObject(templateSpec, cm, nil); err != nil { + if err := wrapper.addRenderedObject(templateSpec, cm, nil, true); err != nil { return err } } return nil } -func (wrapper *renderWrapper) addRenderedObject(templateSpec appsv1alpha1.ComponentTemplateSpec, cm *corev1.ConfigMap, configuration *appsv1alpha1.Configuration) (err error) { +func (wrapper *renderWrapper) addRenderedObject(templateSpec appsv1alpha1.ComponentTemplateSpec, cm *corev1.ConfigMap, configuration *appsv1alpha1.Configuration, asVolume bool) (err error) { // The owner of the configmap object is a cluster, // in order to manage the life cycle of configmap if configuration != nil { @@ -264,12 +269,14 @@ func (wrapper *renderWrapper) addRenderedObject(templateSpec appsv1alpha1.Compon } core.SetParametersUpdateSource(cm, constant.ReconfigureManagerSource) - wrapper.addVolumeMountMeta(templateSpec, cm, true) + wrapper.addVolumeMountMeta(templateSpec, cm, true, asVolume) return nil } -func (wrapper *renderWrapper) addVolumeMountMeta(templateSpec appsv1alpha1.ComponentTemplateSpec, object client.Object, rendered bool) { - wrapper.volumes[object.GetName()] = templateSpec +func (wrapper *renderWrapper) addVolumeMountMeta(templateSpec appsv1alpha1.ComponentTemplateSpec, object client.Object, rendered bool, asVolume bool) { + if asVolume { + wrapper.volumes[object.GetName()] = templateSpec + } if rendered { wrapper.renderedObjs = append(wrapper.renderedObjs, object) } diff --git a/pkg/controllerutil/volume_util.go b/pkg/controllerutil/volume_util.go index ee3135ebd21..15100785236 100644 --- a/pkg/controllerutil/volume_util.go +++ b/pkg/controllerutil/volume_util.go @@ -87,7 +87,7 @@ func CreateOrUpdatePodVolumes(podSpec *corev1.PodSpec, volumes map[string]appsv1 ConfigMap: &corev1.ConfigMapVolumeSource{ LocalObjectReference: corev1.LocalObjectReference{Name: cmName}, // TODO: remove ComponentTemplateSpec.DefaultMode - DefaultMode: buildVolumeMode(configSet, templateSpec), + DefaultMode: BuildVolumeMode(configSet, templateSpec), }, }, } @@ -106,7 +106,7 @@ func CreateOrUpdatePodVolumes(podSpec *corev1.PodSpec, volumes map[string]appsv1 return nil } -func buildVolumeMode(configs []string, configSpec appsv1alpha1.ComponentTemplateSpec) *int32 { +func BuildVolumeMode(configs []string, configSpec appsv1alpha1.ComponentTemplateSpec) *int32 { // If the defaultMode is not set, permissions are automatically set based on the template type. if !viper.GetBool(constant.FeatureGateIgnoreConfigTemplateDefaultMode) && configSpec.DefaultMode != nil { return configSpec.DefaultMode diff --git a/pkg/controllerutil/volume_util_test.go b/pkg/controllerutil/volume_util_test.go index 2f265ed83fc..2d9c72cb5e4 100644 --- a/pkg/controllerutil/volume_util_test.go +++ b/pkg/controllerutil/volume_util_test.go @@ -218,8 +218,8 @@ func Test_buildVolumeMode(t *testing.T) { viper.Set(constant.FeatureGateIgnoreConfigTemplateDefaultMode, false) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := buildVolumeMode(tt.args.configs, tt.args.configSpec); !reflect.DeepEqual(got, tt.want) { - t.Errorf("buildVolumeMode() = %v, want %v", got, tt.want) + if got := BuildVolumeMode(tt.args.configs, tt.args.configSpec); !reflect.DeepEqual(got, tt.want) { + t.Errorf("BuildVolumeMode() = %v, want %v", got, tt.want) } }) } diff --git a/pkg/gotemplate/tpl_engine_test.go b/pkg/gotemplate/tpl_engine_test.go index 5e9b70f93e5..3f00945bd27 100644 --- a/pkg/gotemplate/tpl_engine_test.go +++ b/pkg/gotemplate/tpl_engine_test.go @@ -31,7 +31,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" + appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" + "github.com/apecloud/kubeblocks/pkg/configuration/validate" testutil "github.com/apecloud/kubeblocks/pkg/testutil/k8s" ) @@ -89,8 +91,8 @@ my friend name is test2 It("Should success with no error", func() { tplString := ` {{- $key := sshKeyGen }} -PRIVATE_KEY="{{ $key.PrivateKey }}" -PUBLIC_KEY="{{ $key.PublicKey }}" +private_key={{ $key.PrivateKey | quote }} +public_key={{ $key.PublicKey | quote }} ` context, err := emptyTplEngine(&TplValues{ @@ -99,8 +101,10 @@ PUBLIC_KEY="{{ $key.PublicKey }}" }, nil, tplString) Expect(err).NotTo(HaveOccurred()) - Expect(context).To(ContainSubstring("PRIVATE_KEY")) - Expect(context).To(ContainSubstring("PUBLIC_KEY")) + keyValue, err := validate.LoadConfigObjectFromContent(appsv1beta1.Dotenv, context) + Expect(err).NotTo(HaveOccurred()) + Expect(keyValue).To(HaveKey("private_key")) + Expect(keyValue).To(HaveKey("public_key")) }) }) diff --git a/pkg/testutil/k8s/k8sclient_util.go b/pkg/testutil/k8s/k8sclient_util.go index 7d5e433d974..4457d1a9e30 100644 --- a/pkg/testutil/k8s/k8sclient_util.go +++ b/pkg/testutil/k8s/k8sclient_util.go @@ -145,7 +145,7 @@ func (helper *K8sClientMockHelper) MockCreateMethod(options ...any) { func (helper *K8sClientMockHelper) MockUpdateMethod(options ...any) { helper.updateCaller.Caller(func() (CallerFunction, DoReturnedFunction) { caller := func() *gomock.Call { - return helper.k8sClient.EXPECT().Update(gomock.Any(), gomock.Any()) + return helper.k8sClient.EXPECT().Update(gomock.Any(), gomock.Any(), gomock.Any()) } doAndReturn := func(caller *gomock.Call, fnWrap func(obj client.Object) error) { caller.DoAndReturn(func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {