github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controller/component/component_fieldref_util.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 component 21 22 import ( 23 "bytes" 24 "fmt" 25 "strconv" 26 "strings" 27 28 corev1 "k8s.io/api/core/v1" 29 jsonpath "k8s.io/client-go/util/jsonpath" 30 klog "k8s.io/klog/v2" 31 32 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 33 ) 34 35 func buildComponentRef(clusterDef *appsv1alpha1.ClusterDefinition, 36 cluster *appsv1alpha1.Cluster, 37 clusterCompDef *appsv1alpha1.ClusterComponentDefinition, 38 clusterComp *appsv1alpha1.ClusterComponentSpec, 39 component *SynthesizedComponent) error { 40 41 compRefs := clusterCompDef.ComponentDefRef 42 if len(compRefs) == 0 { 43 return nil 44 } 45 46 component.ComponentRefEnvs = make([]*corev1.EnvVar, 0) 47 48 for _, compRef := range compRefs { 49 referredComponentDef := clusterDef.GetComponentDefByName(compRef.ComponentDefName) 50 referredComponents := cluster.Spec.GetDefNameMappingComponents()[compRef.ComponentDefName] 51 52 if referredComponentDef == nil || len(referredComponents) == 0 { 53 err := fmt.Errorf("failes to match %s in cluster %s", compRef.ComponentDefName, cluster.Name) 54 if compRef.FailurePolicy == appsv1alpha1.FailurePolicyFail { 55 return err 56 } else { 57 klog.V(1).Info(err.Error()) 58 continue 59 } 60 } 61 62 envMap := make(map[string]string) 63 for _, refEnv := range compRef.ComponentRefEnvs { 64 env := &corev1.EnvVar{Name: refEnv.Name} 65 var err error 66 if len(refEnv.Value) != 0 { 67 env.Value = refEnv.Value 68 } else if refEnv.ValueFrom != nil { 69 switch refEnv.ValueFrom.Type { 70 case appsv1alpha1.FromFieldRef: 71 if env.Value, err = resolveFieldRef(refEnv.ValueFrom, referredComponents, referredComponentDef); err != nil { 72 return err 73 } 74 case appsv1alpha1.FromServiceRef: 75 if env.Value, err = resolveServiceRef(cluster.Name, referredComponents, referredComponentDef); err != nil { 76 return err 77 } 78 case appsv1alpha1.FromHeadlessServiceRef: 79 if referredComponentDef.WorkloadType == appsv1alpha1.Stateless { 80 errMsg := fmt.Sprintf("headless service ref is not supported for stateless component, cluster: %s, referred component: %s", cluster.Name, referredComponentDef.Name) 81 klog.V(1).Infof(errMsg) 82 if compRef.FailurePolicy == appsv1alpha1.FailurePolicyFail { 83 return fmt.Errorf(errMsg) 84 } 85 } 86 env.Value = resolveHeadlessServiceFieldRef(refEnv.ValueFrom, cluster, referredComponents) 87 } 88 } 89 90 component.ComponentRefEnvs = append(component.ComponentRefEnvs, env) 91 envMap[env.Name] = env.Value 92 } 93 94 // for each env in componentRefEnvs, resolve reference 95 for _, env := range component.ComponentRefEnvs { 96 val := env.Value 97 for k, v := range envMap { 98 val = strings.ReplaceAll(val, fmt.Sprintf("$(%s)", k), v) 99 } 100 env.Value = val 101 } 102 } 103 return nil 104 } 105 106 type referredObject struct { 107 ComponentDef *appsv1alpha1.ClusterComponentDefinition `json:"componentDef"` 108 Components []appsv1alpha1.ClusterComponentSpec `json:"components"` 109 } 110 111 func resolveFieldRef(valueFrom *appsv1alpha1.ComponentValueFrom, components []appsv1alpha1.ClusterComponentSpec, componentDef *appsv1alpha1.ClusterComponentDefinition) (string, error) { 112 referred := referredObject{ 113 ComponentDef: componentDef, 114 Components: components, 115 } 116 117 if value, err := retrieveValueByJSONPath(referred, valueFrom.FieldPath); err != nil { 118 return "", err 119 } else { 120 return string(value), nil 121 } 122 } 123 124 func resolveServiceRef(clusterName string, components []appsv1alpha1.ClusterComponentSpec, componentDef *appsv1alpha1.ClusterComponentDefinition) (string, error) { 125 if componentDef.Service == nil { 126 return "", fmt.Errorf("componentDef %s does not have service", componentDef.Name) 127 } 128 if len(components) != 1 { 129 return "", fmt.Errorf("expect one component but got %d for componentDef %s", len(components), componentDef.Name) 130 } 131 return fmt.Sprintf("%s-%s", clusterName, components[0].Name), nil 132 } 133 134 func resolveHeadlessServiceFieldRef(valueFrom *appsv1alpha1.ComponentValueFrom, 135 cluster *appsv1alpha1.Cluster, components []appsv1alpha1.ClusterComponentSpec) string { 136 137 preDefineVars := []string{"POD_NAME", "POD_FQDN", "POD_ORDINAL"} 138 139 format := valueFrom.Format 140 if len(format) == 0 { 141 format = "$(POD_FQDN)" 142 } 143 joinWith := valueFrom.JoinWith 144 if len(joinWith) == 0 { 145 joinWith = "," 146 } 147 148 hosts := make([]string, 0) 149 for _, comp := range components { 150 for i := int32(0); i < comp.Replicas; i++ { 151 qualifiedName := fmt.Sprintf("%s-%s", cluster.Name, comp.Name) 152 podOrdinal := strconv.Itoa(int(i)) 153 podName := fmt.Sprintf("%s-%s", qualifiedName, podOrdinal) 154 podFQDN := fmt.Sprintf("%s.%s-headless.%s.svc", podName, qualifiedName, cluster.Namespace) 155 156 valuesToReplace := []string{podName, podFQDN, podOrdinal} 157 158 host := format 159 for idx, preDefineVar := range preDefineVars { 160 host = strings.ReplaceAll(host, "$("+preDefineVar+")", valuesToReplace[idx]) 161 } 162 hosts = append(hosts, host) 163 } 164 } 165 return strings.Join(hosts, joinWith) 166 } 167 168 func retrieveValueByJSONPath(jsonObj interface{}, jpath string) ([]byte, error) { 169 path := jsonpath.New("jsonpath") 170 if err := path.Parse(fmt.Sprintf("{%s}", jpath)); err != nil { 171 return nil, fmt.Errorf("failed to parse jsonpath %s", jpath) 172 } 173 buff := bytes.NewBuffer([]byte{}) 174 if err := path.Execute(buff, jsonObj); err != nil { 175 return nil, fmt.Errorf("failed to execute jsonpath %s", jpath) 176 } 177 return buff.Bytes(), nil 178 }