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  }