@@ -22,8 +22,16 @@ package component
2222import (
2323 "fmt"
2424
25+ corev1 "k8s.io/api/core/v1"
26+ apierrors "k8s.io/apimachinery/pkg/api/errors"
27+ "k8s.io/apimachinery/pkg/types"
28+ "k8s.io/utils/ptr"
29+
2530 appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1"
31+ workloads "github.com/apecloud/kubeblocks/apis/workloads/v1"
2632 appsutil "github.com/apecloud/kubeblocks/controllers/apps/util"
33+ "github.com/apecloud/kubeblocks/pkg/constant"
34+ "github.com/apecloud/kubeblocks/pkg/controller/component"
2735 "github.com/apecloud/kubeblocks/pkg/controller/graph"
2836 intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil"
2937)
@@ -52,6 +60,9 @@ func (t *componentValidationTransformer) Transform(ctx graph.TransformContext, d
5260 if err = validateCompReplicas (comp , transCtx .CompDef ); err != nil {
5361 return intctrlutil .NewRequeueError (appsutil .RequeueDuration , err .Error ())
5462 }
63+ if err = validateExternalManagedConfigSources (transCtx ); err != nil {
64+ return intctrlutil .NewRequeueError (appsutil .RequeueDuration , err .Error ())
65+ }
5566 // if err = validateSidecarContainers(comp, transCtx.CompDef); err != nil {
5667 // return newRequeueError(requeueDuration, err.Error())
5768 // }
@@ -75,3 +86,103 @@ func validateCompReplicas(comp *appsv1.Component, compDef *appsv1.ComponentDefin
7586func replicasOutOfLimitError (replicas int32 , replicasLimit appsv1.ReplicasLimit ) error {
7687 return fmt .Errorf ("replicas %d out-of-limit [%d, %d]" , replicas , replicasLimit .MinReplicas , replicasLimit .MaxReplicas )
7788}
89+
90+ func validateExternalManagedConfigSources (transCtx * componentTransformContext ) error {
91+ comp := transCtx .Component
92+ if len (comp .Spec .Configs ) == 0 {
93+ return nil
94+ }
95+
96+ externalManagedTemplates := map [string ]component.SynthesizedFileTemplate {}
97+ for _ , tpl := range transCtx .SynthesizeComponent .FileTemplates {
98+ if tpl .Config && ptr .Deref (tpl .ExternalManaged , false ) {
99+ externalManagedTemplates [tpl .Name ] = tpl
100+ }
101+ }
102+
103+ var externalManagedConfigs []appsv1.ClusterComponentConfig
104+ externalManagedConfigTemplates := map [string ]component.SynthesizedFileTemplate {}
105+ for _ , config := range comp .Spec .Configs {
106+ if config .Name == nil || config .ConfigMap == nil || config .ConfigMap .Name == "" {
107+ continue
108+ }
109+ tpl , externalManagedTemplate := externalManagedTemplates [* config .Name ]
110+ if ! externalManagedTemplate && ! ptr .Deref (config .ExternalManaged , false ) {
111+ continue
112+ }
113+ externalManagedConfigs = append (externalManagedConfigs , config )
114+ if externalManagedTemplate {
115+ externalManagedConfigTemplates [* config .Name ] = tpl
116+ }
117+ }
118+ if len (externalManagedConfigs ) == 0 {
119+ return nil
120+ }
121+
122+ clusterName , err := component .GetClusterName (comp )
123+ if err != nil {
124+ return err
125+ }
126+ compName , err := component .ShortName (clusterName , comp .Name )
127+ if err != nil {
128+ return err
129+ }
130+
131+ for _ , config := range externalManagedConfigs {
132+ tpl := externalManagedConfigTemplates [* config .Name ]
133+ if isExistingExternalManagedConfigSource (transCtx , config , tpl ) {
134+ continue
135+ }
136+ if err := validateManagedConfigMapLabels (transCtx , config .ConfigMap .Name , clusterName , compName ); err != nil {
137+ return err
138+ }
139+ }
140+ return nil
141+ }
142+
143+ func isExistingExternalManagedConfigSource (transCtx * componentTransformContext , config appsv1.ClusterComponentConfig , tpl component.SynthesizedFileTemplate ) bool {
144+ if transCtx .RunningWorkload == nil || config .ConfigMap == nil || tpl .VolumeName == "" {
145+ return false
146+ }
147+ runningITS , ok := transCtx .RunningWorkload .(* workloads.InstanceSet )
148+ if ! ok || runningITS == nil {
149+ return false
150+ }
151+ for _ , volume := range runningITS .Spec .Template .Spec .Volumes {
152+ if volume .Name == tpl .VolumeName && volume .ConfigMap != nil && volume .ConfigMap .Name == config .ConfigMap .Name {
153+ return true
154+ }
155+ }
156+ return false
157+ }
158+
159+ func validateManagedConfigMapLabels (transCtx * componentTransformContext , cmName , clusterName , compName string ) error {
160+ cm := & corev1.ConfigMap {}
161+ cmKey := types.NamespacedName {
162+ Namespace : transCtx .Component .Namespace ,
163+ Name : cmName ,
164+ }
165+ if err := transCtx .Client .Get (transCtx .Context , cmKey , cm ); err != nil {
166+ if apierrors .IsNotFound (err ) {
167+ return fmt .Errorf ("configMap %q for external-managed config is not found" , cmName )
168+ }
169+ return err
170+ }
171+
172+ // Treat these common component labels as the apps-level handoff contract for
173+ // externally managed config sources. The component controller intentionally
174+ // does not inspect parameters-specific names, annotations, finalizers, or
175+ // owner references.
176+ expectedLabels := map [string ]string {
177+ constant .AppManagedByLabelKey : constant .AppName ,
178+ constant .AppInstanceLabelKey : clusterName ,
179+ constant .KBAppComponentLabelKey : compName ,
180+ }
181+ for key , expected := range expectedLabels {
182+ if actual := cm .Labels [key ]; actual != expected {
183+ return fmt .Errorf ("configMap %q for external-managed config must have label %q=%q, got %q" ,
184+ cmName , key , expected , actual )
185+ }
186+ }
187+ return nil
188+ }
0 commit comments