Skip to content

Commit

Permalink
cluster credential
Browse files Browse the repository at this point in the history
  • Loading branch information
leon-inf committed Oct 25, 2023
1 parent 2dc1799 commit 1b05365
Show file tree
Hide file tree
Showing 9 changed files with 339 additions and 41 deletions.
34 changes: 34 additions & 0 deletions apis/apps/v1alpha1/cluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ type ClusterSpec struct {
// +optional
Services []ClusterService `json:"services,omitempty"`

// credentials defines the credentials used to access a cluster.
// +kubebuilder:pruning:PreserveUnknownFields
// +optional
Credentials []ClusterCredential `json:"credentials,omitempty"`

// tenancy describes how pods are distributed across node.
// SharedNode means multiple pods may share the same node.
// DedicatedNode means each pod runs on their own dedicated node.
Expand Down Expand Up @@ -647,6 +652,35 @@ type ClusterComponentService struct {
Annotations map[string]string `json:"annotations,omitempty"`
}

type ClusterCredential struct {
// The name of the ConnectionCredential.
// Cannot be updated.
// +required
Name string `json:"name"`

// ServiceName specifies the name of service to use for accessing the cluster.
// Cannot be updated.
// +optional
ServiceName string `json:"serviceName,omitempty"`

// PortName specifies the name of the port to access the service.
// If the service has multiple ports, a specific port must be specified to use here.
// Otherwise, the unique port of the service will be used.
// Cannot be updated.
// +optional
PortName string `json:"portName,omitempty"`

// Cannot be updated.
// +optional
ComponentName string `json:"componentName,omitempty"`

// AccountName specifies the account used to access the component service.
// If specified, the account must be defined in @SystemAccounts.
// Cannot be updated.
// +optional
AccountName string `json:"accountName,omitempty"`
}

type ClassDefRef struct {
// Name refers to the name of the ComponentClassDefinition.
// +kubebuilder:validation:MaxLength=63
Expand Down
20 changes: 20 additions & 0 deletions apis/apps/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions config/crd/bases/apps.kubeblocks.io_clusters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,38 @@ spec:
rule: self.all(x, oldSelf.exists(y, y.name == x.name))
- message: component can not be removed dynamically
rule: oldSelf.all(x, self.exists(y, y.name == x.name))
credentials:
description: credentials defines the credentials used to access a
cluster.
items:
properties:
accountName:
description: AccountName specifies the account used to access
the component service. If specified, the account must be defined
in @SystemAccounts. Cannot be updated.
type: string
componentName:
description: Cannot be updated.
type: string
name:
description: The name of the ConnectionCredential. Cannot be
updated.
type: string
portName:
description: PortName specifies the name of the port to access
the service. If the service has multiple ports, a specific
port must be specified to use here. Otherwise, the unique
port of the service will be used. Cannot be updated.
type: string
serviceName:
description: ServiceName specifies the name of service to use
for accessing the cluster. Cannot be updated.
type: string
required:
- name
type: object
type: array
x-kubernetes-preserve-unknown-fields: true
monitor:
description: monitor specifies the configuration of monitor
properties:
Expand Down
4 changes: 2 additions & 2 deletions controllers/apps/cluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@ func (r *ClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
&ClusterAPINormalizationTransformer{},
// handle cluster services
&ClusterServiceTransformer{},
// TODO(component): create default cluster connection credential secret object
&ClusterCredentialTransformer{Client: r.Client},
// create default cluster connection credential secret object
&ClusterCredentialTransformer{},
// TODO(component): handle restore before ClusterComponentTransformer
&RestoreTransformer{Client: r.Client},
// create all cluster components objects
Expand Down
232 changes: 199 additions & 33 deletions controllers/apps/transformer_cluster_credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,67 +20,233 @@ 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"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
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"
intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil"
)

// ClusterCredentialTransformer creates the connection credential secret
type ClusterCredentialTransformer struct {
client.Client
}
type ClusterCredentialTransformer struct{}

var _ graph.Transformer = &ClusterCredentialTransformer{}

// Transform creates the connection credential secret
// TODO(xingran): ClusterCredentialTransformer needs to be refactored. It should not depend on clusterDefinition anymore.
func (c *ClusterCredentialTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error {
func (t *ClusterCredentialTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error {
transCtx, _ := ctx.(*clusterTransformContext)
cluster := transCtx.Cluster
if model.IsObjectDeleting(transCtx.OrigCluster) {
return nil
}

if t.isLegacyCluster(transCtx) {
return t.transformClusterCredentialLegacy(transCtx, dag)
}
return t.transformClusterCredential(transCtx, dag)
}

func (t *ClusterCredentialTransformer) isLegacyCluster(transCtx *clusterTransformContext) bool {
for _, comp := range transCtx.ComponentSpecs {
compDef, ok := transCtx.ComponentDefs[comp.ComponentDef]
if ok && (len(compDef.UID) > 0 || !compDef.CreationTimestamp.IsZero()) {
return false
}
}
return true
}

func (t *ClusterCredentialTransformer) transformClusterCredentialLegacy(transCtx *clusterTransformContext, dag *graph.DAG) error {
graphCli, _ := transCtx.Client.(model.GraphClient)
synthesizedComponent := t.buildSynthesizedComponentLegacy(transCtx)
if synthesizedComponent != nil {
secret := factory.BuildConnCredential(transCtx.ClusterDef, transCtx.Cluster, synthesizedComponent)
if secret != nil {
graphCli.Create(dag, secret)
}
}
return nil
}

var (
synthesizedComponent *component.SynthesizedComponent
err error
)
compSpecMap := cluster.Spec.GetDefNameMappingComponents()
func (t *ClusterCredentialTransformer) buildSynthesizedComponentLegacy(transCtx *clusterTransformContext) *component.SynthesizedComponent {
for _, compDef := range transCtx.ClusterDef.Spec.ComponentDefs {
if compDef.Service == nil {
continue
}
reqCtx := intctrlutil.RequestCtx{
Ctx: transCtx.Context,
Log: log.Log.WithName("cluster"),
for _, compSpec := range transCtx.ComponentSpecs {
if compDef.Name != compSpec.ComponentDefRef {
continue
}
return &component.SynthesizedComponent{
Name: compSpec.Name,
Services: []corev1.Service{{Spec: compDef.Service.ToSVCSpec()}},
}
}
}
return nil
}

func (t *ClusterCredentialTransformer) transformClusterCredential(transCtx *clusterTransformContext, dag *graph.DAG) error {
graphCli, _ := transCtx.Client.(model.GraphClient)
for _, credential := range transCtx.Cluster.Spec.Credentials {
secret, err := t.buildClusterCredential(transCtx, credential)
if err != nil {
return err
}
if err = t.createOrUpdate(transCtx, dag, graphCli, secret); err != nil {
return err
}
}
return nil
}

func (t *ClusterCredentialTransformer) buildClusterCredential(transCtx *clusterTransformContext, credential appsv1alpha1.ClusterCredential) (*corev1.Secret, error) {
cluster := transCtx.Cluster
secret := factory.BuildConnCredential4Cluster(cluster, credential.Name)

var compDef *appsv1alpha1.ComponentDefinition
if len(credential.ComponentName) > 0 {
// TODO(component): lookup comp def
}

data := make(map[string]string)
if len(credential.ServiceName) > 0 {
if err := t.buildServiceEndpoint(cluster, compDef, credential, &data); err != nil {
return nil, err
}
}
if len(credential.ComponentName) > 0 && len(credential.AccountName) > 0 {
if err := t.buildCredential(transCtx, cluster.Namespace, credential, &data); err != nil {
return nil, err
}
}
// TODO(component): define the format of conn-credential secret
secret.StringData = data

return secret, nil
}

func (t *ClusterCredentialTransformer) buildServiceEndpoint(cluster *appsv1alpha1.Cluster, compDef *appsv1alpha1.ComponentDefinition,
credential appsv1alpha1.ClusterCredential, data *map[string]string) error {
clusterSvc, compSvc, ports := t.lookupMatchedService(cluster, compDef, credential)
if clusterSvc == nil && compSvc == nil {
return fmt.Errorf("cluster credential references a service which is not definied: %s-%s", cluster.Name, credential.Name)
}
if len(ports) == 0 {
return fmt.Errorf("cluster credential references a service which doesn't define any ports: %s-%s", cluster.Name, credential.Name)
}
if len(credential.PortName) == 0 && len(ports) > 1 {
return fmt.Errorf("cluster credential should specify which port to use for the referenced service: %s-%s", cluster.Name, credential.Name)
}

if clusterSvc != nil {
t.buildEndpointFromClusterService(credential, clusterSvc, data)
} else {
t.buildEndpointFromComponentService(cluster, credential, compSvc, data)
}
return nil
}

func (t *ClusterCredentialTransformer) lookupMatchedService(cluster *appsv1alpha1.Cluster,
compDef *appsv1alpha1.ComponentDefinition, credential appsv1alpha1.ClusterCredential) (*appsv1alpha1.ClusterService, *appsv1alpha1.ComponentService, []corev1.ServicePort) {
for i, svc := range cluster.Spec.Services {
if svc.Name == credential.ServiceName {
return &cluster.Spec.Services[i], nil, cluster.Spec.Services[i].Service.Spec.Ports
}
comps := compSpecMap[compDef.Name]
if len(comps) > 0 {
synthesizedComponent = &component.SynthesizedComponent{
Name: comps[0].Name,
}
if len(credential.ComponentName) > 0 && compDef != nil {
for i, svc := range compDef.Spec.Services {
if svc.Name == credential.ServiceName {
return nil, &compDef.Spec.Services[i], compDef.Spec.Services[i].Ports
}
} else {
synthesizedComponent, err = component.BuildSynthesizedComponentWrapper(reqCtx, c.Client, cluster, nil)
if err != nil {
return err
}
}
return nil, nil, nil
}

func (t *ClusterCredentialTransformer) buildEndpointFromClusterService(credential appsv1alpha1.ClusterCredential,
service *appsv1alpha1.ClusterService, data *map[string]string) {
port := int32(0)
if len(credential.PortName) == 0 {
port = service.Service.Spec.Ports[0].Port
} else {
for _, servicePort := range service.Service.Spec.Ports {
if servicePort.Name == credential.PortName {
port = servicePort.Port
break
}
}
if synthesizedComponent != nil {
synthesizedComponent.Services = []corev1.Service{
{Spec: compDef.Service.ToSVCSpec()},
}
// TODO(component): define the service and port pattern
(*data)["service"] = service.Name
(*data)["port"] = fmt.Sprintf("%d", port)
}

func (t *ClusterCredentialTransformer) buildEndpointFromComponentService(cluster *appsv1alpha1.Cluster,
credential appsv1alpha1.ClusterCredential, service *appsv1alpha1.ComponentService, data *map[string]string) {
// TODO(component): service.ServiceName
serviceName := constant.GenerateComponentServiceEndpoint(cluster.Name, credential.ComponentName,
string(service.ServiceName), cluster.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
}
break
}
}
if synthesizedComponent != nil {
secret := factory.BuildConnCredential(transCtx.ClusterDef, cluster, synthesizedComponent)
if secret != nil {
// TODO(component): define the service and port pattern
(*data)["service"] = serviceName
(*data)["port"] = fmt.Sprintf("%d", port)
}

func (t *ClusterCredentialTransformer) buildCredential(ctx graph.TransformContext, namespace string,
credential appsv1alpha1.ClusterCredential, data *map[string]string) error {
key := types.NamespacedName{
Namespace: namespace,
Name: credential.AccountName, // TODO(component): secret name
}
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 *ClusterCredentialTransformer) 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
}
Loading

0 comments on commit 1b05365

Please sign in to comment.