diff --git a/apis/apps/v1alpha1/componentdefinition_types.go b/apis/apps/v1alpha1/componentdefinition_types.go index 8a6e8fa95c1..376b87d7cfb 100644 --- a/apis/apps/v1alpha1/componentdefinition_types.go +++ b/apis/apps/v1alpha1/componentdefinition_types.go @@ -321,10 +321,16 @@ type ConnectionCredential struct { AccountName string `json:"accountName,omitempty"` // TODO: how to use the secret? - // CredentialSecret specifies the secret used to access the component service. + // SecretName specifies the name of existed secret used to access the component service. // Cannot be updated. // +optional - CredentialSecret string `json:"credentialSecret,omitempty"` + SecretName string `json:"secretName,omitempty"` + + // SecretNamespace specifies the namespace existed secret used to access the component service. + // If not specified, the namespace component existed will be used. + // Cannot be updated. + // +optional + SecretNamespace string `json:"secretNamespace,omitempty"` } // ComponentRoleArbitrator defines how to arbitrate the role of replicas. diff --git a/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml b/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml index 47dfec03757..84f363bbfad 100644 --- a/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml +++ b/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml @@ -172,11 +172,6 @@ spec: the component service. If specified, the account must be defined in @SystemAccounts. Cannot be updated. type: string - credentialSecret: - description: 'TODO: how to use the secret? CredentialSecret - specifies the secret used to access the component service. - Cannot be updated.' - type: string name: description: The name of the ConnectionCredential. Cannot be updated. @@ -187,6 +182,16 @@ spec: a specific port must be specified to use here. Otherwise, the unique port of the service will be used. Cannot be updated. type: string + secretName: + description: 'TODO: how to use the secret? SecretName specifies + the name of existed secret used to access the component service. + Cannot be updated.' + type: string + secretNamespace: + description: SecretNamespace specifies the namespace existed + secret used to access the component service. If not specified, + the namespace component existed will be used. Cannot be updated. + type: string serviceName: description: ServiceName specifies the name of component service to use for accessing the component service. Cannot be updated. diff --git a/controllers/apps/transformer_component_credential.go b/controllers/apps/transformer_component_credential.go index ad0b1423cbc..3d9033d4dae 100644 --- a/controllers/apps/transformer_component_credential.go +++ b/controllers/apps/transformer_component_credential.go @@ -20,7 +20,20 @@ along with this program. If not, see . package apps import ( + "fmt" + "reflect" + + "golang.org/x/exp/maps" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + + appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + "github.com/apecloud/kubeblocks/pkg/constant" + "github.com/apecloud/kubeblocks/pkg/controller/component" + "github.com/apecloud/kubeblocks/pkg/controller/factory" "github.com/apecloud/kubeblocks/pkg/controller/graph" + "github.com/apecloud/kubeblocks/pkg/controller/model" ) // ComponentCredentialTransformer handles referenced resources validation and load them into context @@ -29,6 +42,167 @@ type ComponentCredentialTransformer struct{} var _ graph.Transformer = &ComponentCredentialTransformer{} func (t *ComponentCredentialTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error { - // TODO: build component credential + cctx, _ := ctx.(*ComponentTransformContext) + synthesizeComp := cctx.SynthesizeComponent + + if model.IsObjectDeleting(cctx.ComponentOrig) { + return nil + } + + graphCli, _ := cctx.Client.(model.GraphClient) + for _, credential := range synthesizeComp.ConnectionCredentials { + secret, err := t.buildConnCredential(ctx, synthesizeComp, credential) + if err != nil { + return err + } + if err = t.createOrUpdate(ctx, dag, graphCli, secret); err != nil { + return err + } + } + return nil +} + +func (t *ComponentCredentialTransformer) buildConnCredential(ctx graph.TransformContext, + synthesizeComp *component.SynthesizedComponent, credential appsv1alpha1.ConnectionCredential) (*corev1.Secret, error) { + secret := factory.BuildConnCredential4Component(synthesizeComp, credential.Name) + if len(credential.SecretName) != 0 { + if err := t.buildFromExistedSecret(ctx, credential, secret); err != nil { + return nil, err + } + } else { + if err := t.buildFromServiceAndAccount(ctx, synthesizeComp, credential, secret); err != nil { + return nil, err + } + } + return secret, nil +} + +func (t *ComponentCredentialTransformer) buildFromExistedSecret(ctx graph.TransformContext, + credential appsv1alpha1.ConnectionCredential, secret *corev1.Secret) error { + namespace := func() string { + namespace := credential.SecretNamespace + if len(namespace) == 0 { + namespace = secret.Namespace + } + return namespace + } + secretKey := types.NamespacedName{ + Namespace: namespace(), + Name: credential.SecretName, + } + obj := &corev1.Secret{} + if err := ctx.GetClient().Get(ctx.GetContext(), secretKey, obj); err != nil { + return err + } + secret.Immutable = obj.Immutable + secret.Data = obj.Data + secret.StringData = obj.StringData + secret.Type = obj.Type + return nil +} + +func (t *ComponentCredentialTransformer) buildFromServiceAndAccount(ctx graph.TransformContext, + synthesizeComp *component.SynthesizedComponent, credential appsv1alpha1.ConnectionCredential, secret *corev1.Secret) error { + data := make(map[string]string) + if len(credential.ServiceName) > 0 { + if err := t.buildEndpoint(synthesizeComp, credential, &data); err != nil { + return err + } + } + if len(credential.AccountName) > 0 { + if err := t.buildCredential(ctx, synthesizeComp.Namespace, credential.AccountName, &data); err != nil { + return err + } + } + // TODO: define the format of conn-credential secret + secret.StringData = data + return nil +} + +func (t *ComponentCredentialTransformer) buildEndpoint(synthesizeComp *component.SynthesizedComponent, + credential appsv1alpha1.ConnectionCredential, data *map[string]string) error { + var service *appsv1alpha1.ComponentService + for i, svc := range synthesizeComp.ComponentServices { + if svc.Name == credential.ServiceName { + service = &synthesizeComp.ComponentServices[i] + break + } + } + if service == nil { + return fmt.Errorf("connection credential references a service not definied, credential: %s, service: %s", + credential.Name, credential.ServiceName) + } + if len(service.Ports) <= 0 { + return fmt.Errorf("connection credential references a service which doesn't define any ports, credential: %s, service: %s", + credential.Name, credential.ServiceName) + } + if len(credential.PortName) == 0 && len(service.Ports) > 1 { + return fmt.Errorf("connection credential should specify which port to use for the referenced service, credential: %s, service: %s", + credential.Name, credential.ServiceName) + } + + t.buildEndpointFromService(synthesizeComp, credential, service, data) + return nil +} + +func (t *ComponentCredentialTransformer) buildEndpointFromService(synthesizeComp *component.SynthesizedComponent, + credential appsv1alpha1.ConnectionCredential, service *appsv1alpha1.ComponentService, data *map[string]string) { + serviceName := constant.GenerateComponentServiceEndpoint(synthesizeComp.ClusterName, + synthesizeComp.Name, string(service.ServiceName), synthesizeComp.Namespace) + + port := int32(0) + if len(credential.PortName) == 0 { + port = service.Ports[0].Port + } else { + for _, servicePort := range service.Ports { + if servicePort.Name == credential.PortName { + port = servicePort.Port + break + } + } + } + + // TODO: define the service and port pattern + (*data)["service"] = serviceName + (*data)["port"] = fmt.Sprintf("%d", port) +} + +func (t *ComponentCredentialTransformer) buildCredential(ctx graph.TransformContext, + namespace, accountName string, data *map[string]string) error { + key := types.NamespacedName{ + Namespace: namespace, + Name: accountName, + } + secret := &corev1.Secret{} + if err := ctx.GetClient().Get(ctx.GetContext(), key, secret); err != nil { + return err + } + // TODO: which field should to use from accounts? + maps.Copy(*data, secret.StringData) + return nil +} + +func (t *ComponentCredentialTransformer) createOrUpdate(ctx graph.TransformContext, + dag *graph.DAG, graphCli model.GraphClient, secret *corev1.Secret) error { + key := types.NamespacedName{ + Namespace: secret.Namespace, + Name: secret.Name, + } + obj := &corev1.Secret{} + if err := ctx.GetClient().Get(ctx.GetContext(), key, obj); err != nil { + if apierrors.IsNotFound(err) { + graphCli.Create(dag, secret) + return nil + } + return err + } + objCopy := obj.DeepCopy() + objCopy.Immutable = secret.Immutable + objCopy.Data = secret.Data + objCopy.StringData = secret.StringData + objCopy.Type = secret.Type + if !reflect.DeepEqual(obj, objCopy) { + graphCli.Update(dag, obj, objCopy) + } return nil } diff --git a/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml b/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml index 47dfec03757..84f363bbfad 100644 --- a/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml +++ b/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml @@ -172,11 +172,6 @@ spec: the component service. If specified, the account must be defined in @SystemAccounts. Cannot be updated. type: string - credentialSecret: - description: 'TODO: how to use the secret? CredentialSecret - specifies the secret used to access the component service. - Cannot be updated.' - type: string name: description: The name of the ConnectionCredential. Cannot be updated. @@ -187,6 +182,16 @@ spec: a specific port must be specified to use here. Otherwise, the unique port of the service will be used. Cannot be updated. type: string + secretName: + description: 'TODO: how to use the secret? SecretName specifies + the name of existed secret used to access the component service. + Cannot be updated.' + type: string + secretNamespace: + description: SecretNamespace specifies the namespace existed + secret used to access the component service. If not specified, + the namespace component existed will be used. Cannot be updated. + type: string serviceName: description: ServiceName specifies the name of component service to use for accessing the component service. Cannot be updated. diff --git a/pkg/constant/pattern.go b/pkg/constant/pattern.go index d9051560df6..98afb1d7f71 100644 --- a/pkg/constant/pattern.go +++ b/pkg/constant/pattern.go @@ -28,17 +28,35 @@ func GenerateDefaultConnCredential(clusterName string) string { return fmt.Sprintf("%s-conn-credential", clusterName) } -// GenerateComponentServiceEndpoint generates default service endpoint name of component -func GenerateComponentServiceEndpoint(clusterName, componentName, namespace string) string { - return fmt.Sprintf("%s-%s.%s.svc", clusterName, componentName, namespace) +// GenerateComponentConnCredential generates connection credential name of component +func GenerateComponentConnCredential(clusterName, compName, name string) string { + if len(name) == 0 { + name = "conn-credential" + } + return fmt.Sprintf("%s-%s-%s", clusterName, compName, name) } -// GenerateComponentHeadlessServiceEndpoint generates default service endpoint name of component -func GenerateComponentHeadlessServiceEndpoint(clusterName, componentName, namespace string) string { - return fmt.Sprintf("%s-%s-headless.%s.svc", clusterName, componentName, namespace) +// GenerateComponentServiceEndpoint generates service endpoint of component +func GenerateComponentServiceEndpoint(clusterName, compName, svcName, namespace string) string { + return fmt.Sprintf("%s-%s-%s.%s.svc", clusterName, compName, svcName, namespace) +} + +// GenerateDefaultComponentServiceEndpoint generates default service endpoint of component +func GenerateDefaultComponentServiceEndpoint(clusterName, compName, namespace string) string { + return fmt.Sprintf("%s-%s.%s.svc", clusterName, compName, namespace) +} + +// GenerateComponentHeadlessServiceEndpoint generates headless service endpoint of component +func GenerateComponentHeadlessServiceEndpoint(clusterName, compName, svcName, namespace string) string { + return fmt.Sprintf("%s-%s-%s-headless.%s.svc", clusterName, compName, svcName, namespace) +} + +// GenerateDefalutComponentHeadlessServiceEndpoint generates default headless service endpoint of component +func GenerateDefalutComponentHeadlessServiceEndpoint(clusterName, compName, namespace string) string { + return fmt.Sprintf("%s-%s-headless.%s.svc", clusterName, compName, namespace) } // GenerateClusterComponentPattern generates cluster and component pattern -func GenerateClusterComponentPattern(clusterName, componentName string) string { - return fmt.Sprintf("%s-%s", clusterName, componentName) +func GenerateClusterComponentPattern(clusterName, compName string) string { + return fmt.Sprintf("%s-%s", clusterName, compName) } diff --git a/pkg/controller/component/component.go b/pkg/controller/component/component.go index c601fda3dd2..1d0418688a8 100644 --- a/pkg/controller/component/component.go +++ b/pkg/controller/component/component.go @@ -334,6 +334,7 @@ func buildComponent(reqCtx ictrlutil.RequestCtx, // make a copy of clusterCompDef clusterCompDefObj := clusterCompDef.DeepCopy() component := &SynthesizedComponent{ + Namespace: cluster.Namespace, ClusterDefName: clusterDef.Name, ClusterName: cluster.Name, ClusterUID: string(cluster.UID), diff --git a/pkg/controller/component/type.go b/pkg/controller/component/type.go index ede4d1dc47d..9896ef35fbd 100644 --- a/pkg/controller/component/type.go +++ b/pkg/controller/component/type.go @@ -35,6 +35,7 @@ type MonitorConfig struct { } type SynthesizedComponent struct { + Namespace string `json:"namespace,omitempty"` ClusterName string `json:"clusterName,omitempty"` ClusterUID string `json:"clusterUID,omitempty"` Name string `json:"name,omitempty"` // the name of the component diff --git a/pkg/controller/factory/builder.go b/pkg/controller/factory/builder.go index a017dde7eeb..5a710e2f46e 100644 --- a/pkg/controller/factory/builder.go +++ b/pkg/controller/factory/builder.go @@ -702,9 +702,9 @@ func BuildConnCredential(clusterDefinition *appsv1alpha1.ClusterDefinition, clus "$(UUID_B64)": uuidB64, "$(UUID_STR_B64)": uuidStrB64, "$(UUID_HEX)": uuidHex, - "$(SVC_FQDN)": constant.GenerateComponentServiceEndpoint(cluster.Name, component.Name, cluster.Namespace), + "$(SVC_FQDN)": constant.GenerateDefaultComponentServiceEndpoint(cluster.Name, component.Name, cluster.Namespace), "$(KB_CLUSTER_COMP_NAME)": constant.GenerateClusterComponentPattern(cluster.Name, component.Name), - "$(HEADLESS_SVC_FQDN)": constant.GenerateComponentHeadlessServiceEndpoint(cluster.Name, component.Name, cluster.Namespace), + "$(HEADLESS_SVC_FQDN)": constant.GenerateDefalutComponentHeadlessServiceEndpoint(cluster.Name, component.Name, cluster.Namespace), } if len(component.Services) > 0 { for _, p := range component.Services[0].Spec.Ports { @@ -722,6 +722,12 @@ func BuildConnCredential(clusterDefinition *appsv1alpha1.ClusterDefinition, clus return connCredential } +func BuildConnCredential4Component(comp *component.SynthesizedComponent, name string) *corev1.Secret { + secretName := constant.GenerateComponentConnCredential(comp.ClusterName, comp.Name, name) + labels := constant.GetKBWellKnownLabels(comp.ClusterDefName, comp.ClusterName, comp.Name) + return builder.NewSecretBuilder(comp.Namespace, secretName).AddLabelsInMap(labels).GetObject() +} + func BuildPDB(cluster *appsv1alpha1.Cluster, component *component.SynthesizedComponent) *policyv1.PodDisruptionBudget { wellKnownLabels := constant.GetKBWellKnownLabels(component.ClusterDefName, cluster.Name, component.Name) return builder.NewPDBBuilder(cluster.Namespace, constant.GenerateClusterComponentPattern(cluster.Name, component.Name)).