github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controller/plan/service_descriptor_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 plan
    21  
    22  import (
    23  	"fmt"
    24  	"regexp"
    25  	"strings"
    26  
    27  	"golang.org/x/exp/slices"
    28  	corev1 "k8s.io/api/core/v1"
    29  	"k8s.io/apimachinery/pkg/types"
    30  	"sigs.k8s.io/controller-runtime/pkg/client"
    31  
    32  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    33  	"github.com/1aal/kubeblocks/pkg/constant"
    34  	"github.com/1aal/kubeblocks/pkg/controller/builder"
    35  	"github.com/1aal/kubeblocks/pkg/controller/component"
    36  	intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil"
    37  )
    38  
    39  func GenServiceReferences(reqCtx intctrlutil.RequestCtx,
    40  	cli client.Client,
    41  	cluster *appsv1alpha1.Cluster,
    42  	clusterCompDef *appsv1alpha1.ClusterComponentDefinition,
    43  	clusterCompSpec *appsv1alpha1.ClusterComponentSpec,
    44  ) (map[string]*appsv1alpha1.ServiceDescriptor, error) {
    45  	if cluster == nil || clusterCompDef == nil || clusterCompSpec == nil {
    46  		return nil, nil
    47  	}
    48  
    49  	if len(clusterCompDef.ServiceRefDeclarations) == 0 {
    50  		return nil, nil
    51  	}
    52  
    53  	serviceReferences := make(map[string]*appsv1alpha1.ServiceDescriptor, len(clusterCompDef.ServiceRefDeclarations))
    54  	for _, serviceRefDecl := range clusterCompDef.ServiceRefDeclarations {
    55  		for _, serviceRef := range clusterCompSpec.ServiceRefs {
    56  			if serviceRef.Name != serviceRefDecl.Name {
    57  				continue
    58  			}
    59  			targetNamespace := cluster.Namespace
    60  			if serviceRef.Namespace != "" {
    61  				targetNamespace = serviceRef.Namespace
    62  			}
    63  			// if service reference is another KubeBlocks Cluster, then it is necessary to generate a service connection credential from the cluster connection credential secret
    64  			if serviceRef.Cluster != "" {
    65  				if err := handleClusterTypeServiceRef(reqCtx, cli, targetNamespace, cluster, serviceRef, serviceRefDecl, serviceReferences); err != nil {
    66  					return nil, err
    67  				}
    68  				// serviceRef.Cluster takes precedence, and if serviceRef.Cluster is set, serviceRef.ServiceDescriptor will be ignored
    69  				break
    70  			}
    71  
    72  			if serviceRef.ServiceDescriptor != "" {
    73  				if err := handleServiceDescriptorTypeServiceRef(reqCtx, cli, targetNamespace, serviceRef, serviceRefDecl, serviceReferences); err != nil {
    74  					return nil, err
    75  				}
    76  			}
    77  		}
    78  		// _, exist := serviceReferences[serviceRefDecl.Name]
    79  		// if !exist {
    80  		//	 return nil, fmt.Errorf("componentDef %s's serviceRefDeclaration %s has not been defined, please check if there is corresponding service definition and binding in Cluster.spec.componentSpecs[*].serviceRefs", clusterCompDef.Name, serviceRefDecl.Name)
    81  		// }
    82  	}
    83  	if len(serviceReferences) == 0 {
    84  		return nil, nil
    85  	}
    86  	return serviceReferences, nil
    87  }
    88  
    89  // handleClusterTypeServiceRef handles the service reference is another KubeBlocks Cluster.
    90  func handleClusterTypeServiceRef(reqCtx intctrlutil.RequestCtx,
    91  	cli client.Client,
    92  	namespace string,
    93  	cluster *appsv1alpha1.Cluster,
    94  	serviceRef appsv1alpha1.ServiceRef,
    95  	serviceRefDecl appsv1alpha1.ServiceRefDeclaration,
    96  	serviceReferences map[string]*appsv1alpha1.ServiceDescriptor) error {
    97  	if serviceRef.Cluster == cluster.Name {
    98  		return fmt.Errorf("cluster %s cannot reference itself", cluster.Name)
    99  	}
   100  	referencedCluster := &appsv1alpha1.Cluster{}
   101  	if err := cli.Get(reqCtx.Ctx, types.NamespacedName{Namespace: namespace, Name: serviceRef.Cluster}, referencedCluster); err != nil {
   102  		return err
   103  	}
   104  
   105  	// get the connection credential secret of the referenced cluster
   106  	secretRef := &corev1.Secret{}
   107  	secretRefName := component.GenerateConnCredential(referencedCluster.Name)
   108  	if err := cli.Get(reqCtx.Ctx, types.NamespacedName{Namespace: namespace, Name: secretRefName}, secretRef); err != nil {
   109  		return err
   110  	}
   111  
   112  	handleSecretKey := func(secretRef *corev1.Secret, sdBuilder *builder.ServiceDescriptorBuilder, key string, setter func(appsv1alpha1.CredentialVar) *builder.ServiceDescriptorBuilder) {
   113  		if _, ok := secretRef.Data[key]; ok {
   114  			setter(appsv1alpha1.CredentialVar{
   115  				ValueFrom: &corev1.EnvVarSource{
   116  					SecretKeyRef: &corev1.SecretKeySelector{
   117  						LocalObjectReference: corev1.LocalObjectReference{Name: secretRef.Name},
   118  						Key:                  key,
   119  					},
   120  				},
   121  			})
   122  		}
   123  	}
   124  
   125  	// TODO: Second-stage optimization: Cluster-type references no longer perform conversion on the connection credential field. Instead, the configMap or secret is directly passed through to the serviceDescriptor.
   126  	sdBuilder := builder.NewServiceDescriptorBuilder(namespace, component.GenerateDefaultServiceDescriptorName(cluster.Name))
   127  	sdBuilder.SetServiceKind("")
   128  	sdBuilder.SetServiceVersion("")
   129  	handleSecretKey(secretRef, sdBuilder, constant.ServiceDescriptorEndpointKey, sdBuilder.SetEndpoint)
   130  	handleSecretKey(secretRef, sdBuilder, constant.ServiceDescriptorPortKey, sdBuilder.SetPort)
   131  	_, uOk := secretRef.Data[constant.ServiceDescriptorUsernameKey]
   132  	_, pOk := secretRef.Data[constant.ServiceDescriptorPasswordKey]
   133  	if uOk && pOk {
   134  		sdBuilder.SetAuth(appsv1alpha1.ConnectionCredentialAuth{
   135  			Username: &appsv1alpha1.CredentialVar{
   136  				ValueFrom: &corev1.EnvVarSource{
   137  					SecretKeyRef: &corev1.SecretKeySelector{
   138  						LocalObjectReference: corev1.LocalObjectReference{Name: secretRef.Name},
   139  						Key:                  constant.ServiceDescriptorUsernameKey,
   140  					},
   141  				},
   142  			},
   143  			Password: &appsv1alpha1.CredentialVar{
   144  				ValueFrom: &corev1.EnvVarSource{
   145  					SecretKeyRef: &corev1.SecretKeySelector{
   146  						LocalObjectReference: corev1.LocalObjectReference{Name: secretRef.Name},
   147  						Key:                  constant.ServiceDescriptorPasswordKey,
   148  					},
   149  				},
   150  			},
   151  		})
   152  	}
   153  	serviceReferences[serviceRefDecl.Name] = sdBuilder.GetObject()
   154  	return nil
   155  }
   156  
   157  // handleServiceDescriptorTypeServiceRef handles the service reference is provided by external ServiceDescriptor object.
   158  func handleServiceDescriptorTypeServiceRef(reqCtx intctrlutil.RequestCtx,
   159  	cli client.Client,
   160  	namespace string,
   161  	serviceRef appsv1alpha1.ServiceRef,
   162  	serviceRefDecl appsv1alpha1.ServiceRefDeclaration,
   163  	serviceReferences map[string]*appsv1alpha1.ServiceDescriptor) error {
   164  	// verify service kind and version
   165  	verifyServiceKindAndVersion := func(serviceDescriptor appsv1alpha1.ServiceDescriptor, serviceRefDeclSpecs ...appsv1alpha1.ServiceRefDeclarationSpec) bool {
   166  		for _, serviceRefDeclSpec := range serviceRefDecl.ServiceRefDeclarationSpecs {
   167  			if getWellKnownServiceKindAliasMapping(serviceRefDeclSpec.ServiceKind) != getWellKnownServiceKindAliasMapping(serviceDescriptor.Spec.ServiceKind) {
   168  				continue
   169  			}
   170  			versionMatch := verifyServiceVersion(serviceDescriptor.Spec.ServiceVersion, serviceRefDeclSpec.ServiceVersion)
   171  			if versionMatch {
   172  				return true
   173  			}
   174  		}
   175  		return false
   176  	}
   177  	serviceDescriptor := &appsv1alpha1.ServiceDescriptor{}
   178  	if err := cli.Get(reqCtx.Ctx, client.ObjectKey{Namespace: namespace, Name: serviceRef.ServiceDescriptor}, serviceDescriptor); err != nil {
   179  		return err
   180  	}
   181  	if serviceDescriptor.Status.Phase != appsv1alpha1.AvailablePhase {
   182  		return fmt.Errorf("service descriptor %s status is not available", serviceDescriptor.Name)
   183  	}
   184  	match := verifyServiceKindAndVersion(*serviceDescriptor, serviceRefDecl.ServiceRefDeclarationSpecs...)
   185  	if !match {
   186  		return fmt.Errorf("service descriptor %s kind or version is not match with service reference declaration %s", serviceDescriptor.Name, serviceRefDecl.Name)
   187  	}
   188  	serviceReferences[serviceRefDecl.Name] = serviceDescriptor
   189  	return nil
   190  }
   191  
   192  func verifyServiceVersion(serviceDescriptorVersion, serviceRefDeclarationServiceVersion string) bool {
   193  	isRegex := false
   194  	regex, err := regexp.Compile(serviceRefDeclarationServiceVersion)
   195  	if err == nil {
   196  		isRegex = true
   197  	}
   198  	if !isRegex {
   199  		return serviceDescriptorVersion == serviceRefDeclarationServiceVersion
   200  	}
   201  	return regex.MatchString(serviceDescriptorVersion)
   202  }
   203  
   204  func getWellKnownServiceKindAliasMapping(serviceKind string) string {
   205  	lowerServiceKind := strings.ToLower(serviceKind)
   206  	switch {
   207  	case slices.Contains(constant.GetZookeeperAlias(), lowerServiceKind):
   208  		return constant.ServiceKindZookeeper
   209  	case slices.Contains(constant.GetElasticSearchAlias(), lowerServiceKind):
   210  		return constant.ServiceKindElasticSearch
   211  	case slices.Contains(constant.GetMongoDBAlias(), lowerServiceKind):
   212  		return constant.ServiceKindMongoDB
   213  	case slices.Contains(constant.GetPostgreSQLAlias(), lowerServiceKind):
   214  		return constant.ServiceKindPostgreSQL
   215  	case slices.Contains(constant.GetClickHouseAlias(), lowerServiceKind):
   216  		return constant.ServiceKindClickHouse
   217  	default:
   218  		return lowerServiceKind
   219  	}
   220  }