github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controller/configuration/builtin_env.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  	"regexp"
    24  	"strings"
    25  
    26  	"github.com/StudioSol/set"
    27  	corev1 "k8s.io/api/core/v1"
    28  	"k8s.io/apimachinery/pkg/runtime/schema"
    29  	"k8s.io/kubectl/pkg/util/resource"
    30  	coreclient "sigs.k8s.io/controller-runtime/pkg/client"
    31  
    32  	cfgcore "github.com/1aal/kubeblocks/pkg/configuration/core"
    33  	"github.com/1aal/kubeblocks/pkg/constant"
    34  	"github.com/1aal/kubeblocks/pkg/controller/component"
    35  	"github.com/1aal/kubeblocks/pkg/generics"
    36  )
    37  
    38  type envBuildInFunc func(container interface{}, envName string) (string, error)
    39  
    40  type envWrapper struct {
    41  	// prevent circular references.
    42  	referenceCount int
    43  	*configTemplateBuilder
    44  
    45  	// configmap or secret not yet submitted.
    46  	localObjects  []coreclient.Object
    47  	clusterName   string
    48  	clusterUID    string
    49  	componentName string
    50  	// cache remoted configmap and secret.
    51  	cache map[schema.GroupVersionKind]map[coreclient.ObjectKey]coreclient.Object
    52  }
    53  
    54  const maxReferenceCount = 10
    55  
    56  func wrapGetEnvByName(templateBuilder *configTemplateBuilder, component *component.SynthesizedComponent, localObjs []coreclient.Object) envBuildInFunc {
    57  	wrapper := &envWrapper{
    58  		configTemplateBuilder: templateBuilder,
    59  		localObjects:          localObjs,
    60  		cache:                 make(map[schema.GroupVersionKind]map[coreclient.ObjectKey]coreclient.Object),
    61  	}
    62  	// hack for test cases of cli update cmd...
    63  	if component != nil {
    64  		wrapper.clusterName = component.ClusterName
    65  		wrapper.clusterUID = component.ClusterUID
    66  		wrapper.componentName = component.Name
    67  	}
    68  	return func(args interface{}, envName string) (string, error) {
    69  		container, err := fromJSONObject[corev1.Container](args)
    70  		if err != nil {
    71  			return "", err
    72  		}
    73  		return wrapper.getEnvByName(container, envName)
    74  	}
    75  }
    76  
    77  func (w *envWrapper) getEnvByName(container *corev1.Container, envName string) (string, error) {
    78  	for _, v := range container.Env {
    79  		if v.Name != envName {
    80  			continue
    81  		}
    82  		switch {
    83  		case v.ValueFrom == nil:
    84  			return w.checkAndReplaceEnv(v.Value, container)
    85  		case v.ValueFrom.ConfigMapKeyRef != nil:
    86  			return w.configMapValue(v.ValueFrom.ConfigMapKeyRef, container)
    87  		case v.ValueFrom.SecretKeyRef != nil:
    88  			return w.secretValue(v.ValueFrom.SecretKeyRef, container)
    89  		case v.ValueFrom.FieldRef != nil:
    90  			return fieldRefValue(v.ValueFrom.FieldRef, w.podSpec)
    91  		case v.ValueFrom.ResourceFieldRef != nil:
    92  			return resourceRefValue(v.ValueFrom.ResourceFieldRef, w.podSpec.Containers, container)
    93  		}
    94  	}
    95  	return w.getEnvFromResources(container.EnvFrom, envName, container)
    96  }
    97  
    98  func (w *envWrapper) getEnvFromResources(envSources []corev1.EnvFromSource, envName string, container *corev1.Container) (string, error) {
    99  	for _, source := range envSources {
   100  		if value, err := w.getEnvFromResource(source, envName, container); err != nil {
   101  			return "", err
   102  		} else if value != "" {
   103  			return w.checkAndReplaceEnv(value, container)
   104  		}
   105  	}
   106  	return "", nil
   107  }
   108  
   109  func (w *envWrapper) getEnvFromResource(envSource corev1.EnvFromSource, envName string, container *corev1.Container) (string, error) {
   110  	fromConfigMap := func(configmapRef *corev1.ConfigMapEnvSource) (string, error) {
   111  		return w.configMapValue(&corev1.ConfigMapKeySelector{
   112  			Key:                  envName,
   113  			LocalObjectReference: corev1.LocalObjectReference{Name: configmapRef.Name},
   114  		}, container)
   115  	}
   116  	fromSecret := func(secretRef *corev1.SecretEnvSource) (string, error) {
   117  		return w.secretValue(&corev1.SecretKeySelector{
   118  			Key:                  envName,
   119  			LocalObjectReference: corev1.LocalObjectReference{Name: secretRef.Name},
   120  		}, container)
   121  	}
   122  
   123  	switch {
   124  	default:
   125  		return "", nil
   126  	case envSource.ConfigMapRef != nil:
   127  		return fromConfigMap(envSource.ConfigMapRef)
   128  	case envSource.SecretRef != nil:
   129  		return fromSecret(envSource.SecretRef)
   130  	}
   131  }
   132  
   133  func (w *envWrapper) secretValue(secretRef *corev1.SecretKeySelector, container *corev1.Container) (string, error) {
   134  	secretPlaintext := func(m map[string]string) (string, error) {
   135  		if v, ok := m[secretRef.Key]; ok {
   136  			return w.checkAndReplaceEnv(v, container)
   137  		}
   138  		return "", nil
   139  	}
   140  	secretCiphertext := func(m map[string][]byte) (string, error) {
   141  		if v, ok := m[secretRef.Key]; ok {
   142  			return string(v), nil
   143  		}
   144  		return "", nil
   145  	}
   146  
   147  	if w.cli == nil {
   148  		return "", cfgcore.MakeError("not support secret[%s] value in local mode, cli is nil", secretRef.Name)
   149  	}
   150  
   151  	secretName, err := w.checkAndReplaceEnv(secretRef.Name, container)
   152  	if err != nil {
   153  		return "", err
   154  	}
   155  	secretKey := coreclient.ObjectKey{
   156  		Name:      secretName,
   157  		Namespace: w.namespace,
   158  	}
   159  	secret, err := getResourceObject(w, &corev1.Secret{}, secretKey)
   160  	if err != nil {
   161  		return "", err
   162  	}
   163  	if secret.StringData != nil {
   164  		return secretPlaintext(secret.StringData)
   165  	}
   166  	if secret.Data != nil {
   167  		return secretCiphertext(secret.Data)
   168  	}
   169  	return "", nil
   170  }
   171  
   172  func (w *envWrapper) configMapValue(configmapRef *corev1.ConfigMapKeySelector, container *corev1.Container) (string, error) {
   173  	if w.cli == nil {
   174  		return "", cfgcore.MakeError("not supported configmap[%s] value in local mode, cli is nil", configmapRef.Name)
   175  	}
   176  
   177  	cmName, err := w.checkAndReplaceEnv(configmapRef.Name, container)
   178  	if err != nil {
   179  		return "", err
   180  	}
   181  	cmKey := coreclient.ObjectKey{
   182  		Name:      cmName,
   183  		Namespace: w.namespace,
   184  	}
   185  	cm, err := getResourceObject(w, &corev1.ConfigMap{}, cmKey)
   186  	if err != nil {
   187  		return "", err
   188  	}
   189  	return cm.Data[configmapRef.Key], nil
   190  }
   191  
   192  func (w *envWrapper) getResourceFromLocal(key coreclient.ObjectKey, gvk schema.GroupVersionKind) coreclient.Object {
   193  	if _, ok := w.cache[gvk]; !ok {
   194  		w.cache[gvk] = make(map[coreclient.ObjectKey]coreclient.Object)
   195  	}
   196  	if v, ok := w.cache[gvk][key]; ok {
   197  		return v
   198  	}
   199  	return findMatchedLocalObject(w.localObjects, key, gvk)
   200  }
   201  
   202  var envPlaceHolderRegexp = regexp.MustCompile(`\$\(\w+\)`)
   203  
   204  func (w *envWrapper) checkAndReplaceEnv(value string, container *corev1.Container) (string, error) {
   205  	// env value replace,e.g: $(CONN_CREDENTIAL_SECRET_NAME), $(KB_CLUSTER_COMP_NAME)
   206  	// - name: KB_POD_FQDN
   207  	//      value: $(KB_POD_NAME).$(KB_CLUSTER_COMP_NAME)-headless.$(KB_NAMESPACE).svc
   208  	//
   209  	// - name: MYSQL_ROOT_USER
   210  	//      valueFrom:
   211  	//        secretKeyRef:
   212  	//          key: username
   213  	//          name: $(CONN_CREDENTIAL_SECRET_NAME)
   214  	// var := "$(KB_POD_NAME).$(KB_CLUSTER_COMP_NAME)-headless.$(KB_NAMESPACE).svc"
   215  	//
   216  	// loop reference
   217  	// - name: LOOP_REF_A
   218  	//   value: $(LOOP_REF_B)
   219  	// - name: LOOP_REF_B
   220  	//   value: $(LOOP_REF_A)
   221  
   222  	if len(value) == 0 || strings.IndexByte(value, '$') < 0 {
   223  		return value, nil
   224  	}
   225  	envHolderVec := envPlaceHolderRegexp.FindAllString(value, -1)
   226  	if len(envHolderVec) == 0 {
   227  		return value, nil
   228  	}
   229  	return w.doEnvReplace(set.NewLinkedHashSetString(envHolderVec...), value, container)
   230  }
   231  
   232  func (w *envWrapper) doEnvReplace(replacedVars *set.LinkedHashSetString, oldValue string, container *corev1.Container) (string, error) {
   233  	var (
   234  		clusterName   = w.clusterName
   235  		clusterUID    = w.clusterUID
   236  		componentName = w.componentName
   237  		builtInEnvMap = component.GetReplacementMapForBuiltInEnv(clusterName, clusterUID, componentName)
   238  	)
   239  
   240  	builtInEnvMap[constant.KBConnCredentialPlaceHolder] = component.GenerateConnCredential(clusterName)
   241  	kbInnerEnvReplaceFn := func(envName string, strToReplace string) string {
   242  		return strings.ReplaceAll(strToReplace, envName, builtInEnvMap[envName])
   243  	}
   244  
   245  	if !w.incAndCheckReferenceCount() {
   246  		return "", cfgcore.MakeError("too many reference count, maybe there is a cycled reference: [%s] more than %d times ", oldValue, w.referenceCount)
   247  	}
   248  
   249  	replacedValue := oldValue
   250  	for envHolder := range replacedVars.Iter() {
   251  		if len(envHolder) <= 3 {
   252  			continue
   253  		}
   254  		if _, ok := builtInEnvMap[envHolder]; ok {
   255  			replacedValue = kbInnerEnvReplaceFn(envHolder, replacedValue)
   256  			continue
   257  		}
   258  		envName := envHolder[2 : len(envHolder)-1]
   259  		envValue, err := w.getEnvByName(container, envName)
   260  		if err != nil {
   261  			w.decReferenceCount()
   262  			return envValue, err
   263  		}
   264  		replacedValue = strings.ReplaceAll(replacedValue, envHolder, envValue)
   265  	}
   266  	w.decReferenceCount()
   267  	return replacedValue, nil
   268  }
   269  
   270  func (w *envWrapper) incReferenceCount() {
   271  	w.referenceCount++
   272  }
   273  
   274  func (w *envWrapper) decReferenceCount() {
   275  	w.referenceCount--
   276  }
   277  
   278  func (w *envWrapper) incAndCheckReferenceCount() bool {
   279  	w.incReferenceCount()
   280  	return w.referenceCount <= maxReferenceCount
   281  }
   282  
   283  func getResourceObject[T generics.Object, PT generics.PObject[T]](w *envWrapper, obj PT, key coreclient.ObjectKey) (PT, error) {
   284  	gvk := generics.ToGVK(obj)
   285  	object := w.getResourceFromLocal(key, gvk)
   286  	if object != nil {
   287  		if v, ok := object.(PT); ok {
   288  			return v, nil
   289  		}
   290  	}
   291  	if err := w.cli.Get(w.ctx, key, obj); err != nil {
   292  		return nil, err
   293  	}
   294  	w.cache[gvk][key] = obj
   295  	return obj, nil
   296  }
   297  
   298  func resourceRefValue(resourceRef *corev1.ResourceFieldSelector, containers []corev1.Container, curContainer *corev1.Container) (string, error) {
   299  	if resourceRef.ContainerName == "" {
   300  		return containerResourceRefValue(resourceRef, curContainer)
   301  	}
   302  	for _, v := range containers {
   303  		if v.Name == resourceRef.ContainerName {
   304  			return containerResourceRefValue(resourceRef, &v)
   305  		}
   306  	}
   307  	return "", cfgcore.MakeError("not found named[%s] container", resourceRef.ContainerName)
   308  }
   309  
   310  func containerResourceRefValue(fieldSelector *corev1.ResourceFieldSelector, c *corev1.Container) (string, error) {
   311  	return resource.ExtractContainerResourceValue(fieldSelector, c)
   312  }
   313  
   314  func fieldRefValue(podReference *corev1.ObjectFieldSelector, podSpec *corev1.PodSpec) (string, error) {
   315  	return "", cfgcore.MakeError("not support pod field ref")
   316  }