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 }