Skip to content

Commit

Permalink
impl connection credential transformer
Browse files Browse the repository at this point in the history
  • Loading branch information
leon-inf committed Oct 20, 2023
1 parent 9287c99 commit ac372a7
Show file tree
Hide file tree
Showing 8 changed files with 239 additions and 23 deletions.
10 changes: 8 additions & 2 deletions apis/apps/v1alpha1/componentdefinition_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
15 changes: 10 additions & 5 deletions config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down
176 changes: 175 additions & 1 deletion controllers/apps/transformer_component_credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,20 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
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
Expand All @@ -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
}
15 changes: 10 additions & 5 deletions deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down
34 changes: 26 additions & 8 deletions pkg/constant/pattern.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
1 change: 1 addition & 0 deletions pkg/controller/component/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
1 change: 1 addition & 0 deletions pkg/controller/component/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 8 additions & 2 deletions pkg/controller/factory/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)).
Expand Down

0 comments on commit ac372a7

Please sign in to comment.