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 }