github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controller/configuration/envfrom_utils.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package configuration
    21  
    22  import (
    23  	"context"
    24  
    25  	"github.com/spf13/cast"
    26  	corev1 "k8s.io/api/core/v1"
    27  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    28  	"sigs.k8s.io/controller-runtime/pkg/client"
    29  
    30  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    31  	"github.com/1aal/kubeblocks/pkg/configuration/core"
    32  	cfgutil "github.com/1aal/kubeblocks/pkg/configuration/util"
    33  	"github.com/1aal/kubeblocks/pkg/configuration/validate"
    34  	"github.com/1aal/kubeblocks/pkg/constant"
    35  	"github.com/1aal/kubeblocks/pkg/controller/component"
    36  	intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil"
    37  	"github.com/1aal/kubeblocks/pkg/generics"
    38  )
    39  
    40  func injectTemplateEnvFrom(cluster *appsv1alpha1.Cluster, component *component.SynthesizedComponent, podSpec *corev1.PodSpec, cli client.Client, ctx context.Context, localObjs []client.Object) error {
    41  	var err error
    42  	var cm *corev1.ConfigMap
    43  
    44  	injectConfigmap := func(envMap map[string]string, configSpec appsv1alpha1.ComponentConfigSpec, cmName string) error {
    45  		envConfigMap, err := createEnvFromConfigmap(cluster, component.Name, configSpec, client.ObjectKeyFromObject(cm), envMap, ctx, cli)
    46  		if err != nil {
    47  			return core.WrapError(err, "failed to generate env configmap[%s]", cmName)
    48  		}
    49  		injectEnvFrom(podSpec.Containers, configSpec.AsEnvFrom, envConfigMap.Name)
    50  		injectEnvFrom(podSpec.InitContainers, configSpec.AsEnvFrom, envConfigMap.Name)
    51  		return nil
    52  	}
    53  
    54  	for _, template := range component.ConfigTemplates {
    55  		if len(template.AsEnvFrom) == 0 || template.ConfigConstraintRef == "" {
    56  			continue
    57  		}
    58  		cmName := core.GetComponentCfgName(cluster.Name, component.Name, template.Name)
    59  		if cm, err = fetchConfigmap(localObjs, cmName, cluster.Namespace, cli, ctx); err != nil {
    60  			return err
    61  		}
    62  		cc, err := getConfigConstraint(template, cli, ctx)
    63  		if err != nil {
    64  			return err
    65  		}
    66  		envMap, err := fromConfigmapFiles(fromConfigSpec(template, cm), cm, cc.FormatterConfig)
    67  		if err != nil {
    68  			return err
    69  		}
    70  		if len(envMap) == 0 {
    71  			continue
    72  		}
    73  		if err := injectConfigmap(envMap, template, cmName); err != nil {
    74  			return err
    75  		}
    76  	}
    77  	return nil
    78  }
    79  
    80  func getConfigConstraint(template appsv1alpha1.ComponentConfigSpec, cli client.Client, ctx context.Context) (*appsv1alpha1.ConfigConstraintSpec, error) {
    81  	ccKey := client.ObjectKey{
    82  		Namespace: "",
    83  		Name:      template.ConfigConstraintRef,
    84  	}
    85  	cc := &appsv1alpha1.ConfigConstraint{}
    86  	if err := cli.Get(ctx, ccKey, cc); err != nil {
    87  		return nil, core.WrapError(err, "failed to get ConfigConstraint, key[%v]", ccKey)
    88  	}
    89  	if cc.Spec.FormatterConfig == nil {
    90  		return nil, core.MakeError("ConfigConstraint[%v] is not a formatter", cc.Name)
    91  	}
    92  	return &cc.Spec, nil
    93  }
    94  
    95  func fromConfigmapFiles(keys []string, cm *corev1.ConfigMap, formatter *appsv1alpha1.FormatterConfig) (map[string]string, error) {
    96  	mergeMap := func(dst, src map[string]string) {
    97  		for key, val := range src {
    98  			dst[key] = val
    99  		}
   100  	}
   101  
   102  	gEnvMap := make(map[string]string)
   103  	for _, file := range keys {
   104  		envMap, err := fromFileContent(formatter, cm.Data[file])
   105  		if err != nil {
   106  			return nil, err
   107  		}
   108  		mergeMap(gEnvMap, envMap)
   109  	}
   110  	return gEnvMap, nil
   111  }
   112  
   113  func fetchConfigmap(localObjs []client.Object, cmName, namespace string, cli client.Client, ctx context.Context) (*corev1.ConfigMap, error) {
   114  	var (
   115  		cmObj = &corev1.ConfigMap{}
   116  		cmKey = client.ObjectKey{Name: cmName, Namespace: namespace}
   117  	)
   118  
   119  	localObject := findMatchedLocalObject(localObjs, cmKey, generics.ToGVK(cmObj))
   120  	if localObject != nil {
   121  		return localObject.(*corev1.ConfigMap), nil
   122  	}
   123  	if err := cli.Get(ctx, cmKey, cmObj); err != nil {
   124  		return nil, err
   125  	}
   126  	return cmObj, nil
   127  }
   128  
   129  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) {
   130  	cmKey := client.ObjectKey{
   131  		Name:      core.GenerateEnvFromName(originKey.Name),
   132  		Namespace: originKey.Namespace,
   133  	}
   134  	cm := &corev1.ConfigMap{}
   135  	err := cli.Get(ctx, cmKey, cm)
   136  	if err == nil {
   137  		return cm, nil
   138  	}
   139  	if !apierrors.IsNotFound(err) {
   140  		return nil, err
   141  	}
   142  	cm.Name = cmKey.Name
   143  	cm.Namespace = cmKey.Namespace
   144  	cm.Data = envMap
   145  	cm.Labels = map[string]string{
   146  		constant.CMTemplateNameLabelKey: template.Name,
   147  		constant.AppNameLabelKey:        cluster.Spec.ClusterDefRef,
   148  		constant.AppInstanceLabelKey:    cluster.Name,
   149  		constant.KBAppComponentLabelKey: componentName,
   150  	}
   151  	if err := intctrlutil.SetOwnerReference(cluster, cm); err != nil {
   152  		return nil, err
   153  	}
   154  	return cm, cli.Create(ctx, cm)
   155  }
   156  
   157  func CheckEnvFrom(container *corev1.Container, cmName string) bool {
   158  	for i := range container.EnvFrom {
   159  		source := &container.EnvFrom[i]
   160  		if source.ConfigMapRef != nil && source.ConfigMapRef.Name == cmName {
   161  			return true
   162  		}
   163  	}
   164  	return false
   165  }
   166  
   167  func injectEnvFrom(containers []corev1.Container, asEnvFrom []string, cmName string) {
   168  	sets := cfgutil.NewSet(asEnvFrom...)
   169  	for i := range containers {
   170  		container := &containers[i]
   171  		if sets.InArray(container.Name) && !CheckEnvFrom(container, cmName) {
   172  			container.EnvFrom = append(container.EnvFrom,
   173  				corev1.EnvFromSource{
   174  					ConfigMapRef: &corev1.ConfigMapEnvSource{
   175  						LocalObjectReference: corev1.LocalObjectReference{
   176  							Name: cmName,
   177  						}},
   178  				})
   179  		}
   180  	}
   181  }
   182  
   183  func fromFileContent(format *appsv1alpha1.FormatterConfig, configContext string) (map[string]string, error) {
   184  	keyValue, err := validate.LoadConfigObjectFromContent(format.Format, configContext)
   185  	if err != nil {
   186  		return nil, err
   187  	}
   188  	envMap := make(map[string]string, len(keyValue))
   189  	for key, v := range keyValue {
   190  		envMap[key] = cast.ToString(v)
   191  	}
   192  	return envMap, nil
   193  }
   194  
   195  func fromConfigSpec(configSpec appsv1alpha1.ComponentConfigSpec, cm *corev1.ConfigMap) []string {
   196  	keys := configSpec.Keys
   197  	if len(keys) == 0 {
   198  		keys = cfgutil.ToSet(cm.Data).AsSlice()
   199  	}
   200  	return keys
   201  }
   202  
   203  func SyncEnvConfigmap(configSpec appsv1alpha1.ComponentConfigSpec, cmObj *corev1.ConfigMap, cc *appsv1alpha1.ConfigConstraintSpec, cli client.Client, ctx context.Context) error {
   204  	if len(configSpec.AsEnvFrom) == 0 || cc == nil || cc.FormatterConfig == nil {
   205  		return nil
   206  	}
   207  	envMap, err := fromConfigmapFiles(fromConfigSpec(configSpec, cmObj), cmObj, cc.FormatterConfig)
   208  	if err != nil {
   209  		return err
   210  	}
   211  	if len(envMap) == 0 {
   212  		return nil
   213  	}
   214  
   215  	return updateEnvFromConfigmap(client.ObjectKeyFromObject(cmObj), envMap, cli, ctx)
   216  }
   217  
   218  func updateEnvFromConfigmap(origObj client.ObjectKey, envMap map[string]string, cli client.Client, ctx context.Context) error {
   219  	cmKey := client.ObjectKey{
   220  		Name:      core.GenerateEnvFromName(origObj.Name),
   221  		Namespace: origObj.Namespace,
   222  	}
   223  	cm := &corev1.ConfigMap{}
   224  	if err := cli.Get(ctx, cmKey, cm); err != nil {
   225  		return err
   226  	}
   227  	patch := client.MergeFrom(cm.DeepCopy())
   228  	cm.Data = envMap
   229  	if err := cli.Patch(ctx, cm, patch); err != nil {
   230  		return err
   231  	}
   232  	return nil
   233  }