From b931be3e62d964c731ce9aabc9db2ede83e1cc01 Mon Sep 17 00:00:00 2001 From: Leon Date: Fri, 3 Jan 2025 13:58:51 +0800 Subject: [PATCH 1/6] init --- apis/apps/v1/componentdefinition_types.go | 26 +- apis/apps/v1/types.go | 12 + apis/apps/v1/zz_generated.deepcopy.go | 28 +- .../bases/apps.kubeblocks.io_clusters.yaml | 19 ++ ...ps.kubeblocks.io_componentdefinitions.yaml | 37 +-- .../bases/apps.kubeblocks.io_components.yaml | 9 + .../apps/componentdefinition_controller.go | 6 +- .../transformer_cluster_sharding_account.go | 39 +-- .../apps/transformer_component_account.go | 205 +++++++++++--- ...transformer_component_account_provision.go | 267 +++++++++++------- .../crds/apps.kubeblocks.io_clusters.yaml | 19 ++ ...ps.kubeblocks.io_componentdefinitions.yaml | 37 +-- .../crds/apps.kubeblocks.io_components.yaml | 9 + docs/developer_docs/api-reference/cluster.md | 84 +++++- pkg/constant/labels.go | 18 +- .../builder/builder_component_definition.go | 13 - pkg/controller/component/component_version.go | 3 + pkg/controller/component/its_convertor.go | 8 +- .../component/synthesize_component.go | 31 +- .../apps/componentdefinition_factory.go | 4 +- pkg/testutil/apps/constant.go | 7 +- 21 files changed, 592 insertions(+), 289 deletions(-) diff --git a/apis/apps/v1/componentdefinition_types.go b/apis/apps/v1/componentdefinition_types.go index d602b67f9c0..d13980a9e79 100644 --- a/apis/apps/v1/componentdefinition_types.go +++ b/apis/apps/v1/componentdefinition_types.go @@ -1212,12 +1212,12 @@ type SystemAccount struct { // +optional InitAccount bool `json:"initAccount,omitempty"` - // Defines the statement used to create the account with the necessary privileges. + // Defines the statements used to create, delete, and update the account. // // This field is immutable once set. // // +optional - Statement string `json:"statement,omitempty"` + Statement *SystemAccountStatement `json:"statement,omitempty"` // Specifies the policy for generating the account's password. // @@ -1225,13 +1225,29 @@ type SystemAccount struct { // // +optional PasswordGenerationPolicy PasswordConfig `json:"passwordGenerationPolicy"` +} + +type SystemAccountStatement struct { + // The statement to create a new account with the necessary privileges. + // + // This field is immutable once set. + // + // +optional + Create string `json:"create,omitempty"` + + // The statement to delete a account. + // + // This field is immutable once set. + // + // +optional + Delete string `json:"delete,omitempty"` - // Refers to the secret from which data will be copied to create the new account. + // The statement to update an existing account. // // This field is immutable once set. // // +optional - SecretRef *ProvisionSecretRef `json:"secretRef,omitempty"` + Update string `json:"update,omitempty"` } type TLS struct { @@ -1736,7 +1752,7 @@ type ComponentLifecycleActions struct { // The container executing this action has access to following variables: // // - KB_ACCOUNT_NAME: The name of the system account to be created. - // - KB_ACCOUNT_PASSWORD: The password for the system account. // TODO: how to pass the password securely? + // - KB_ACCOUNT_PASSWORD: The password for the system account. // - KB_ACCOUNT_STATEMENT: The statement used to create the system account. // // Note: This field is immutable once it has been set. diff --git a/apis/apps/v1/types.go b/apis/apps/v1/types.go index 0cc99cfc120..1ddbb0b8ce9 100644 --- a/apis/apps/v1/types.go +++ b/apis/apps/v1/types.go @@ -347,6 +347,12 @@ type ComponentSystemAccount struct { // +kubebuilder:validation:Required Name string `json:"name"` + // Specifies whether the system account is disabled. + // + // +kubebuilder:default=false + // +optional + Disabled *bool `json:"disabled,omitempty"` + // Specifies the policy for generating the account's password. // // This field is immutable once set. @@ -429,6 +435,12 @@ type ProvisionSecretRef struct { // // +kubebuilder:validation:Required Namespace string `json:"namespace"` + + // The key in the secret data that contains the password. + // + // +kubebuilder:default="password" + // +optional + Password string `json:"password,omitempty"` } // ClusterComponentConfig represents a config with its source bound. diff --git a/apis/apps/v1/zz_generated.deepcopy.go b/apis/apps/v1/zz_generated.deepcopy.go index 1e9b6d2c9df..2cea284832e 100644 --- a/apis/apps/v1/zz_generated.deepcopy.go +++ b/apis/apps/v1/zz_generated.deepcopy.go @@ -1553,6 +1553,11 @@ func (in *ComponentStatus) DeepCopy() *ComponentStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ComponentSystemAccount) DeepCopyInto(out *ComponentSystemAccount) { *out = *in + if in.Disabled != nil { + in, out := &in.Disabled, &out.Disabled + *out = new(bool) + **out = **in + } if in.PasswordConfig != nil { in, out := &in.PasswordConfig, &out.PasswordConfig *out = new(PasswordConfig) @@ -3224,12 +3229,12 @@ func (in *SidecarDefinitionStatus) DeepCopy() *SidecarDefinitionStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SystemAccount) DeepCopyInto(out *SystemAccount) { *out = *in - out.PasswordGenerationPolicy = in.PasswordGenerationPolicy - if in.SecretRef != nil { - in, out := &in.SecretRef, &out.SecretRef - *out = new(ProvisionSecretRef) + if in.Statement != nil { + in, out := &in.Statement, &out.Statement + *out = new(SystemAccountStatement) **out = **in } + out.PasswordGenerationPolicy = in.PasswordGenerationPolicy } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SystemAccount. @@ -3242,6 +3247,21 @@ func (in *SystemAccount) DeepCopy() *SystemAccount { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SystemAccountStatement) DeepCopyInto(out *SystemAccountStatement) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SystemAccountStatement. +func (in *SystemAccountStatement) DeepCopy() *SystemAccountStatement { + if in == nil { + return nil + } + out := new(SystemAccountStatement) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TLS) DeepCopyInto(out *TLS) { *out = *in diff --git a/config/crd/bases/apps.kubeblocks.io_clusters.yaml b/config/crd/bases/apps.kubeblocks.io_clusters.yaml index 2d4cc34c940..97c5f6892a8 100644 --- a/config/crd/bases/apps.kubeblocks.io_clusters.yaml +++ b/config/crd/bases/apps.kubeblocks.io_clusters.yaml @@ -5276,6 +5276,10 @@ spec: ComponentDefinition. items: properties: + disabled: + default: false + description: Specifies whether the system account is disabled. + type: boolean name: description: The name of the system account. type: string @@ -5334,6 +5338,11 @@ spec: namespace: description: The namespace where the secret is located. type: string + password: + default: password + description: The key in the secret data that contains + the password. + type: string required: - name - namespace @@ -13977,6 +13986,11 @@ spec: ComponentDefinition. items: properties: + disabled: + default: false + description: Specifies whether the system account + is disabled. + type: boolean name: description: The name of the system account. type: string @@ -14036,6 +14050,11 @@ spec: description: The namespace where the secret is located. type: string + password: + default: password + description: The key in the secret data that contains + the password. + type: string required: - name - namespace diff --git a/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml b/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml index fe6bec4e932..ec919134eab 100644 --- a/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml +++ b/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml @@ -4502,7 +4502,7 @@ spec: - KB_ACCOUNT_NAME: The name of the system account to be created. - - KB_ACCOUNT_PASSWORD: The password for the system account. // TODO: how to pass the password securely? + - KB_ACCOUNT_PASSWORD: The password for the system account. - KB_ACCOUNT_STATEMENT: The statement used to create the system account. @@ -16727,30 +16727,35 @@ spec: Cannot be updated. type: string type: object - secretRef: + statement: description: |- - Refers to the secret from which data will be copied to create the new account. + Defines the statements used to create, delete, and update the account. This field is immutable once set. properties: - name: - description: The unique identifier of the secret. + create: + description: |- + The statement to create a new account with the necessary privileges. + + + This field is immutable once set. type: string - namespace: - description: The namespace where the secret is located. + delete: + description: |- + The statement to delete a account. + + + This field is immutable once set. type: string - required: - - name - - namespace - type: object - statement: - description: |- - Defines the statement used to create the account with the necessary privileges. + update: + description: |- + The statement to update an existing account. - This field is immutable once set. - type: string + This field is immutable once set. + type: string + type: object required: - name type: object diff --git a/config/crd/bases/apps.kubeblocks.io_components.yaml b/config/crd/bases/apps.kubeblocks.io_components.yaml index 9da4fd1e563..6629aa574ab 100644 --- a/config/crd/bases/apps.kubeblocks.io_components.yaml +++ b/config/crd/bases/apps.kubeblocks.io_components.yaml @@ -5471,6 +5471,10 @@ spec: description: Overrides system accounts defined in referenced ComponentDefinition. items: properties: + disabled: + default: false + description: Specifies whether the system account is disabled. + type: boolean name: description: The name of the system account. type: string @@ -5529,6 +5533,11 @@ spec: namespace: description: The namespace where the secret is located. type: string + password: + default: password + description: The key in the secret data that contains the + password. + type: string required: - name - namespace diff --git a/controllers/apps/componentdefinition_controller.go b/controllers/apps/componentdefinition_controller.go index ed7518649f8..25040a3a4cd 100644 --- a/controllers/apps/componentdefinition_controller.go +++ b/controllers/apps/componentdefinition_controller.go @@ -343,7 +343,7 @@ func (r *ComponentDefinitionReconciler) validateConfigs(cli client.Client, rctx func (r *ComponentDefinitionReconciler) validateSystemAccounts(cli client.Client, rctx intctrlutil.RequestCtx, cmpd *appsv1.ComponentDefinition) error { for _, v := range cmpd.Spec.SystemAccounts { - if v.SecretRef == nil && !v.InitAccount && (cmpd.Spec.LifecycleActions == nil || cmpd.Spec.LifecycleActions.AccountProvision == nil) { + if !v.InitAccount && (cmpd.Spec.LifecycleActions == nil || cmpd.Spec.LifecycleActions.AccountProvision == nil) { return fmt.Errorf(`the AccountProvision action is needed to provision system account %s`, v.Name) } } @@ -351,8 +351,8 @@ func (r *ComponentDefinitionReconciler) validateSystemAccounts(cli client.Client return fmt.Errorf("duplicate system accounts are not allowed") } for _, account := range cmpd.Spec.SystemAccounts { - if !account.InitAccount && len(account.Statement) == 0 && account.SecretRef == nil { - return fmt.Errorf("the Statement or SecretRef must be provided to create system account: %s", account.Name) + if !account.InitAccount && (account.Statement == nil || len(account.Statement.Create) == 0) { + return fmt.Errorf("the create statement must be provided to provision system account: %s", account.Name) } } return nil diff --git a/controllers/apps/transformer_cluster_sharding_account.go b/controllers/apps/transformer_cluster_sharding_account.go index 3718ed19153..5e87e47fd24 100644 --- a/controllers/apps/transformer_cluster_sharding_account.go +++ b/controllers/apps/transformer_cluster_sharding_account.go @@ -118,17 +118,7 @@ func (t *clusterShardingAccountTransformer) newSystemAccountSecret(transCtx *clu if err != nil { return nil, err } - - var password []byte - switch { - case account.SecretRef != nil: - var err error - if password, err = t.getPasswordFromSecret(transCtx, account); err != nil { - return nil, err - } - default: - password = t.buildPassword(transCtx, account, sharding.Name) - } + password := t.buildPassword(transCtx, account, sharding.Name) return t.newAccountSecretWithPassword(transCtx, sharding, accountName, password) } @@ -152,7 +142,6 @@ func (t *clusterShardingAccountTransformer) definedSystemAccount(transCtx *clust if compAccount.PasswordConfig != nil { account.PasswordGenerationPolicy = *compAccount.PasswordConfig } - account.SecretRef = compAccount.SecretRef } return *account } @@ -165,21 +154,6 @@ func (t *clusterShardingAccountTransformer) definedSystemAccount(transCtx *clust return appsv1.SystemAccount{}, fmt.Errorf("system account %s not found in component definition %s", accountName, compDef.Name) } -func (t *clusterShardingAccountTransformer) getPasswordFromSecret(ctx graph.TransformContext, account appsv1.SystemAccount) ([]byte, error) { - secretKey := types.NamespacedName{ - Namespace: account.SecretRef.Namespace, - Name: account.SecretRef.Name, - } - secret := &corev1.Secret{} - if err := ctx.GetClient().Get(ctx.GetContext(), secretKey, secret); err != nil { - return nil, err - } - if len(secret.Data) == 0 || len(secret.Data[constant.AccountPasswdForSecret]) == 0 { - return nil, fmt.Errorf("referenced account secret has no required credential field") - } - return secret.Data[constant.AccountPasswdForSecret], nil -} - func (t *clusterShardingAccountTransformer) buildPassword(transCtx *clusterTransformContext, account appsv1.SystemAccount, shardingName string) []byte { password := []byte(factory.GetRestoreSystemAccountPassword(transCtx.Cluster.Annotations, shardingName, account.Name)) if len(password) == 0 { @@ -227,20 +201,21 @@ func (t *clusterShardingAccountTransformer) rewriteSystemAccount(transCtx *clust var ( cluster = transCtx.Cluster ) - account := appsv1.ComponentSystemAccount{ + newAccount := appsv1.ComponentSystemAccount{ Name: accountName, SecretRef: &appsv1.ProvisionSecretRef{ Name: shardingAccountSecretName(cluster.Name, sharding.Name, accountName), Namespace: cluster.Namespace, }, } - for i := range sharding.Template.SystemAccounts { - if sharding.Template.SystemAccounts[i].Name == accountName { - sharding.Template.SystemAccounts[i] = account + for i, account := range sharding.Template.SystemAccounts { + if account.Name == accountName { + newAccount.Disabled = account.Disabled + sharding.Template.SystemAccounts[i] = newAccount return } } - sharding.Template.SystemAccounts = []appsv1.ComponentSystemAccount{account} + sharding.Template.SystemAccounts = []appsv1.ComponentSystemAccount{newAccount} } func shardingAccountSecretName(cluster, sharding, account string) string { diff --git a/controllers/apps/transformer_component_account.go b/controllers/apps/transformer_component_account.go index 71412527443..fff21d859c4 100644 --- a/controllers/apps/transformer_component_account.go +++ b/controllers/apps/transformer_component_account.go @@ -24,9 +24,11 @@ import ( "reflect" "strings" + "golang.org/x/crypto/bcrypt" + "golang.org/x/exp/maps" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" @@ -40,6 +42,11 @@ import ( ctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) +const ( + systemAccountLabel = "apps.kubeblocks.io/system-account" + systemAccountHashAnnotation = "apps.kubeblocks.io/system-account-hash" +) + // componentAccountTransformer handles component system accounts. type componentAccountTransformer struct{} @@ -55,56 +62,78 @@ func (t *componentAccountTransformer) Transform(ctx graph.TransformContext, dag return nil } - synthesizeComp := transCtx.SynthesizeComponent + synthesizedComp := transCtx.SynthesizeComponent graphCli, _ := transCtx.Client.(model.GraphClient) - for _, account := range synthesizeComp.SystemAccounts { - existSecret, err := t.checkAccountSecretExist(ctx, synthesizeComp, account) - if err != nil { + // exist account objects + secrets, err := listSystemAccountObjects(ctx, synthesizedComp) + if err != nil { + return err + } + runningNameSet := sets.New(maps.Keys(secrets)...) + + // proto accounts + accounts := synthesizeSystemAccounts(transCtx.CompDef.Spec.SystemAccounts, transCtx.Component.Spec.SystemAccounts, false) + protoNameSet := sets.New(maps.Keys(accounts)...) + + createSet, deleteSet, updateSet := setDiff(runningNameSet, protoNameSet) + + for _, name := range sets.List(createSet) { + if err := t.createAccount(transCtx, dag, graphCli, accounts[name]); err != nil { return err } - secret, err := t.buildAccountSecret(transCtx, synthesizeComp, account) - if err != nil { + } + + for _, name := range sets.List(deleteSet) { + t.deleteAccount(transCtx, dag, graphCli, secrets[name]) + } + + for _, name := range sets.List(updateSet) { + if err := t.updateAccount(transCtx, dag, graphCli, accounts[name], secrets[name]); err != nil { return err } + } - if existSecret == nil { - graphCli.Create(dag, secret, inUniversalContext4G()) - continue - } + return nil +} - // just update existed account secret metadata if needed - existSecretCopy := existSecret.DeepCopy() - ctrlutil.MergeMetadataMapInplace(secret.Labels, &existSecretCopy.Labels) - ctrlutil.MergeMetadataMapInplace(secret.Annotations, &existSecretCopy.Annotations) - if !reflect.DeepEqual(existSecret, existSecretCopy) { - graphCli.Update(dag, existSecret, existSecretCopy, inUniversalContext4G()) - } +func (t *componentAccountTransformer) createAccount(transCtx *componentTransformContext, + dag *graph.DAG, graphCli model.GraphClient, account synthesizedSystemAccount) error { + secret, err := t.buildAccountSecret(transCtx, transCtx.SynthesizeComponent, account) + if err != nil { + return err } - // TODO: (good-first-issue) if an account is deleted from the Spec, the secret and account should be deleted + graphCli.Create(dag, secret, inUniversalContext4G()) return nil } -func (t *componentAccountTransformer) checkAccountSecretExist(ctx graph.TransformContext, - synthesizeComp *component.SynthesizedComponent, account appsv1.SystemAccount) (*corev1.Secret, error) { - secretKey := types.NamespacedName{ - Namespace: synthesizeComp.Namespace, - Name: constant.GenerateAccountSecretName(synthesizeComp.ClusterName, synthesizeComp.Name, account.Name), +func (t *componentAccountTransformer) deleteAccount(transCtx *componentTransformContext, + dag *graph.DAG, graphCli model.GraphClient, secret *corev1.Secret) { + graphCli.Delete(dag, secret, inUniversalContext4G()) +} + +func (t *componentAccountTransformer) updateAccount(transCtx *componentTransformContext, + dag *graph.DAG, graphCli model.GraphClient, account synthesizedSystemAccount, running *corev1.Secret) error { + secret, err := t.buildAccountSecret(transCtx, transCtx.SynthesizeComponent, account) + if err != nil { + return err } - secret := &corev1.Secret{} - err := ctx.GetClient().Get(ctx.GetContext(), secretKey, secret) - switch { - case err == nil: - return secret, nil - case apierrors.IsNotFound(err): - return nil, nil - default: - return nil, err + + runningCopy := running.DeepCopy() + if account.SecretRef != nil { + // sync password from the external secret + runningCopy.Data[constant.AccountPasswdForSecret] = secret.Data[constant.AccountPasswdForSecret] } + ctrlutil.MergeMetadataMapInplace(secret.Labels, &runningCopy.Labels) + ctrlutil.MergeMetadataMapInplace(secret.Annotations, &runningCopy.Annotations) + if !reflect.DeepEqual(running, runningCopy) { + graphCli.Update(dag, running, runningCopy, inUniversalContext4G()) + } + return nil } func (t *componentAccountTransformer) buildAccountSecret(ctx *componentTransformContext, - synthesizeComp *component.SynthesizedComponent, account appsv1.SystemAccount) (*corev1.Secret, error) { + synthesizeComp *component.SynthesizedComponent, account synthesizedSystemAccount) (*corev1.Secret, error) { var password []byte switch { case account.SecretRef != nil: @@ -115,10 +144,10 @@ func (t *componentAccountTransformer) buildAccountSecret(ctx *componentTransform default: password = t.buildPassword(ctx, account) } - return t.buildAccountSecretWithPassword(ctx, synthesizeComp, account, password) + return t.buildAccountSecretWithPassword(ctx, synthesizeComp, account, password, account.SecretRef != nil) } -func (t *componentAccountTransformer) getPasswordFromSecret(ctx graph.TransformContext, account appsv1.SystemAccount) ([]byte, error) { +func (t *componentAccountTransformer) getPasswordFromSecret(ctx graph.TransformContext, account synthesizedSystemAccount) ([]byte, error) { secretKey := types.NamespacedName{ Namespace: account.SecretRef.Namespace, Name: account.SecretRef.Name, @@ -127,13 +156,18 @@ func (t *componentAccountTransformer) getPasswordFromSecret(ctx graph.TransformC if err := ctx.GetClient().Get(ctx.GetContext(), secretKey, secret); err != nil { return nil, err } - if len(secret.Data) == 0 || len(secret.Data[constant.AccountPasswdForSecret]) == 0 { - return nil, fmt.Errorf("referenced account secret has no required credential field") + + passwordKey := constant.AccountPasswdForSecret + if len(account.SecretRef.Password) > 0 { + passwordKey = account.SecretRef.Password + } + if len(secret.Data) == 0 || len(secret.Data[passwordKey]) == 0 { + return nil, fmt.Errorf("referenced account secret has no required credential field: %s", passwordKey) } - return secret.Data[constant.AccountPasswdForSecret], nil + return secret.Data[passwordKey], nil } -func (t *componentAccountTransformer) buildPassword(ctx *componentTransformContext, account appsv1.SystemAccount) []byte { +func (t *componentAccountTransformer) buildPassword(ctx *componentTransformContext, account synthesizedSystemAccount) []byte { // get restore password if exists during recovery. password := factory.GetRestoreSystemAccountPassword(ctx.SynthesizeComponent.Annotations, ctx.SynthesizeComponent.Name, account.Name) if account.InitAccount && password == "" { @@ -147,7 +181,7 @@ func (t *componentAccountTransformer) buildPassword(ctx *componentTransformConte return []byte(password) } -func (t *componentAccountTransformer) generatePassword(account appsv1.SystemAccount) []byte { +func (t *componentAccountTransformer) generatePassword(account synthesizedSystemAccount) []byte { config := account.PasswordGenerationPolicy passwd, _ := common.GeneratePassword((int)(config.Length), (int)(config.NumDigits), (int)(config.NumSymbols), false, config.Seed) switch config.LetterCase { @@ -160,21 +194,108 @@ func (t *componentAccountTransformer) generatePassword(account appsv1.SystemAcco } func (t *componentAccountTransformer) buildAccountSecretWithPassword(ctx *componentTransformContext, - synthesizeComp *component.SynthesizedComponent, account appsv1.SystemAccount, password []byte) (*corev1.Secret, error) { + synthesizeComp *component.SynthesizedComponent, account synthesizedSystemAccount, password []byte, signature bool) (*corev1.Secret, error) { secretName := constant.GenerateAccountSecretName(synthesizeComp.ClusterName, synthesizeComp.Name, account.Name) secret := builder.NewSecretBuilder(synthesizeComp.Namespace, secretName). // Priority: static < dynamic < built-in AddLabelsInMap(synthesizeComp.StaticLabels). AddLabelsInMap(synthesizeComp.DynamicLabels). AddLabelsInMap(constant.GetCompLabels(synthesizeComp.ClusterName, synthesizeComp.Name)). + AddLabels(systemAccountLabel, account.Name). AddAnnotationsInMap(synthesizeComp.StaticAnnotations). AddAnnotationsInMap(synthesizeComp.DynamicAnnotations). PutData(constant.AccountNameForSecret, []byte(account.Name)). PutData(constant.AccountPasswdForSecret, password). SetImmutable(true). GetObject() + if signature { + if err := signatureSystemAccountPassword(secret); err != nil { + return nil, err + } + } if err := setCompOwnershipNFinalizer(ctx.Component, secret); err != nil { return nil, err } return secret, nil } + +func listSystemAccountObjects(ctx graph.TransformContext, + synthesizedComp *component.SynthesizedComponent) (map[string]*corev1.Secret, error) { + opts := []client.ListOption{ + client.InNamespace(synthesizedComp.Namespace), + client.MatchingLabels(constant.GetCompLabels(synthesizedComp.ClusterName, synthesizedComp.Name)), + } + secretList := &corev1.SecretList{} + if err := ctx.GetClient().List(ctx.GetContext(), secretList, opts...); err != nil { + return nil, err + } + + m := make(map[string]*corev1.Secret) + for i, secret := range secretList.Items { + if accountName, ok := secret.Labels[systemAccountLabel]; ok { + m[accountName] = &secretList.Items[i] + } + } + return m, nil +} + +func signatureSystemAccountPassword(secret *corev1.Secret) error { + password := secret.Data[constant.AccountPasswdForSecret] + hashedPassword, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost) + if err != nil { + return err + } + if secret.Annotations == nil { + secret.Annotations = map[string]string{} + } + secret.Annotations[systemAccountHashAnnotation] = string(hashedPassword) + return nil +} + +func verifySystemAccountPassword(secret *corev1.Secret, hashedPassword []byte) bool { + password := secret.Data[constant.AccountPasswdForSecret] + err := bcrypt.CompareHashAndPassword(hashedPassword, password) + return err == nil +} + +type synthesizedSystemAccount struct { + appsv1.SystemAccount + Disabled *bool + SecretRef *appsv1.ProvisionSecretRef +} + +func synthesizeSystemAccounts(compDefAccounts []appsv1.SystemAccount, + compAccounts []appsv1.ComponentSystemAccount, keepDisabled bool) map[string]synthesizedSystemAccount { + accounts := make(map[string]synthesizedSystemAccount) + for _, account := range compDefAccounts { + accounts[account.Name] = synthesizedSystemAccount{ + SystemAccount: account, + } + } + + merge := func(account synthesizedSystemAccount, compAccount appsv1.ComponentSystemAccount) synthesizedSystemAccount { + if compAccount.PasswordConfig != nil { + account.PasswordGenerationPolicy = *compAccount.PasswordConfig + } + account.Disabled = compAccount.Disabled + account.SecretRef = compAccount.SecretRef + return account + } + + for i := range compAccounts { + account, ok := accounts[compAccounts[i].Name] + if !ok { + continue // ignore it silently + } + accounts[compAccounts[i].Name] = merge(account, compAccounts[i]) + } + + if !keepDisabled { + for _, name := range maps.Keys(accounts) { + if accounts[name].Disabled != nil && *accounts[name].Disabled { + delete(accounts, name) + } + } + } + return accounts +} diff --git a/controllers/apps/transformer_component_account_provision.go b/controllers/apps/transformer_component_account_provision.go index 3408711a905..b3d5762f5b5 100644 --- a/controllers/apps/transformer_component_account_provision.go +++ b/controllers/apps/transformer_component_account_provision.go @@ -20,12 +20,15 @@ along with this program. If not, see . package apps import ( + "fmt" + "reflect" "slices" "strings" + "golang.org/x/exp/maps" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" @@ -59,59 +62,148 @@ func (t *componentAccountProvisionTransformer) Transform(ctx graph.TransformCont return nil } - if len(transCtx.SynthesizeComponent.SystemAccounts) == 0 { + comp := transCtx.Component + compDef := transCtx.CompDef + + // provision accounts only when the component is running + if comp.Status.Phase != appsv1.RunningComponentPhase { return nil } - if transCtx.Component.Status.Phase != appsv1.RunningComponentPhase { + + // has no lifecycle actions defined, skip the account provision + lifecycleActions := compDef.Spec.LifecycleActions + if lifecycleActions == nil || lifecycleActions.AccountProvision == nil { return nil } - // TODO: (good-first-issue) if the component's account is deleted by user, we should re-provision it - cond, provisioned := t.isProvisioned(transCtx) - if provisioned { - return nil + + accounts := synthesizeSystemAccounts(compDef.Spec.SystemAccounts, comp.Spec.SystemAccounts, true) + + secrets, err1 := listSystemAccountObjects(ctx, transCtx.SynthesizeComponent) + if err1 != nil { + return err1 } + protoNameSet := sets.New(maps.Keys(secrets)...) - lifecycleActions := transCtx.CompDef.Spec.LifecycleActions - if lifecycleActions == nil || lifecycleActions.AccountProvision == nil { + cond := t.provisionCond(transCtx) + provisionedNameSet := sets.New(strings.Split(cond.Message, ",")...) + + createSet, deleteSet, updateSet := setDiff(provisionedNameSet, protoNameSet) + if len(createSet) == 0 && len(deleteSet) == 0 && len(updateSet) == 0 { return nil } - lfa, err := t.lifecycleAction(transCtx) - if err != nil { - return err - } - for _, account := range transCtx.SynthesizeComponent.SystemAccounts { - // The secret of initAccount should be rendered into the config file, - // or injected into the container through specific account&password environment variables name supported by the engine. - // When the engine starts up, it will automatically load and create this account. - if account.InitAccount { - continue + lfa, err2 := t.lifecycleAction(transCtx) + if err2 != nil { + return err2 + } + + var err3 error + condCopy := cond.DeepCopy() + for _, name := range sets.List(createSet) { + if err := t.createAccount(transCtx, lfa, &cond, accounts[name], secrets[name]); err != nil { + if err3 == nil { + err3 = err + } } - if t.isAccountProvisioned(cond, account) { - continue + } + + for _, name := range sets.List(deleteSet) { + if err := t.deleteAccount(transCtx, lfa, &cond, accounts[name]); err != nil { + if err3 == nil { + err3 = err + } } - if transCtx.SynthesizeComponent.Annotations[constant.RestoreFromBackupAnnotationKey] == "" { - // TODO: restore account secret from backup. - // provision account when the component is not recovered from backup - if err = t.provisionAccount(transCtx, cond, lfa, account); err != nil { - t.markProvisionAsFailed(transCtx, &cond, err) - return err + } + + for _, name := range sets.List(updateSet) { + if err := t.updateAccount(transCtx, lfa, &cond, accounts[name], secrets[name]); err != nil { + if err3 == nil { + err3 = err } } - t.markAccountProvisioned(&cond, account) } - t.markProvisioned(transCtx, cond) - return nil + t.provisionCondDone(transCtx, condCopy, &cond, err3) + + return err3 +} + +func (t *componentAccountProvisionTransformer) lifecycleAction(transCtx *componentTransformContext) (lifecycle.Lifecycle, error) { + synthesizedComp := transCtx.SynthesizeComponent + pods, err := component.ListOwnedPods(transCtx.Context, transCtx.Client, + synthesizedComp.Namespace, synthesizedComp.ClusterName, synthesizedComp.Name) + if err != nil { + return nil, err + } + lfa, err := lifecycle.New(transCtx.SynthesizeComponent, nil, pods...) + if err != nil { + return nil, err + } + return lfa, nil +} + +func (t *componentAccountProvisionTransformer) createAccount(transCtx *componentTransformContext, + lfa lifecycle.Lifecycle, cond *metav1.Condition, account synthesizedSystemAccount, secret *corev1.Secret) error { + // The secret of initAccount should be rendered into the config file, + // or injected into the container through specific account&password environment variables name supported by the engine. + // When the engine starts up, it will automatically load and create this account. + if account.InitAccount { + return nil + } + + var err error + if transCtx.SynthesizeComponent.Annotations[constant.RestoreFromBackupAnnotationKey] == "" { + // TODO: restore account secret from backup. + // provision account when the component is not recovered from backup + err = t.provision(transCtx, lfa, account.Statement.Create, secret) + } + + if err == nil { + // TODO: how about the password restored from backup? + t.addOrUpdateProvisionedAccount(cond, account.Name, secret.Annotations[systemAccountHashAnnotation]) + } + return err +} + +func (t *componentAccountProvisionTransformer) deleteAccount(transCtx *componentTransformContext, + lfa lifecycle.Lifecycle, cond *metav1.Condition, account synthesizedSystemAccount) error { + err := lfa.AccountProvision(transCtx.Context, transCtx.Client, nil, account.Statement.Delete, account.Name, "") + if lifecycle.IgnoreNotDefined(err) == nil { + t.removeProvisionedAccount(cond, account.Name) + } + return lifecycle.IgnoreNotDefined(err) +} + +func (t *componentAccountProvisionTransformer) updateAccount(transCtx *componentTransformContext, + lfa lifecycle.Lifecycle, cond *metav1.Condition, account synthesizedSystemAccount, secret *corev1.Secret) error { + hashedPassword := t.getHashedPasswordFromCond(cond, account.Name) + if verifySystemAccountPassword(secret, []byte(hashedPassword)) { + return nil // the password is not changed + } + + // TODO: how to notify other apps to update the new password? + + err := t.provision(transCtx, lfa, account.Statement.Update, secret) + if err == nil { + t.addOrUpdateProvisionedAccount(cond, account.Name, secret.Annotations[systemAccountHashAnnotation]) + } + return err } -func (t *componentAccountProvisionTransformer) isProvisioned(transCtx *componentTransformContext) (metav1.Condition, bool) { +func (t *componentAccountProvisionTransformer) provision(transCtx *componentTransformContext, + lfa lifecycle.Lifecycle, statement string, secret *corev1.Secret) error { + username, password := secret.Data[constant.AccountNameForSecret], secret.Data[constant.AccountPasswdForSecret] + if len(username) == 0 || len(password) == 0 { + return nil + } + err := lfa.AccountProvision(transCtx.Context, transCtx.Client, nil, statement, string(username), string(password)) + return lifecycle.IgnoreNotDefined(err) +} + +func (t *componentAccountProvisionTransformer) provisionCond(transCtx *componentTransformContext) metav1.Condition { for _, cond := range transCtx.Component.Status.Conditions { if cond.Type == accountProvisionConditionType { - if cond.Status == metav1.ConditionTrue { - return cond, true - } - return cond, false + return cond } } return metav1.Condition{ @@ -121,21 +213,23 @@ func (t *componentAccountProvisionTransformer) isProvisioned(transCtx *component LastTransitionTime: metav1.Now(), Reason: accountProvisionConditionReasonInProgress, Message: "", - }, false + } } -func (t *componentAccountProvisionTransformer) markProvisionAsFailed(transCtx *componentTransformContext, cond *metav1.Condition, err error) { - cond.Status = metav1.ConditionFalse +func (t *componentAccountProvisionTransformer) provisionCondDone(transCtx *componentTransformContext, + condCopy, cond *metav1.Condition, err error) { + if err == nil { + cond.Status = metav1.ConditionTrue + cond.Reason = accountProvisionConditionReasonDone + } else { + cond.Status = metav1.ConditionFalse + // cond.Reason = err.Error() // TODO: error + } cond.ObservedGeneration = transCtx.Component.Generation - cond.LastTransitionTime = metav1.Now() - // cond.Reason = err.Error() // TODO: error -} -func (t *componentAccountProvisionTransformer) markProvisioned(transCtx *componentTransformContext, cond metav1.Condition) { - cond.Status = metav1.ConditionTrue - cond.ObservedGeneration = transCtx.Component.Generation - cond.LastTransitionTime = metav1.Now() - cond.Reason = accountProvisionConditionReasonDone + if !reflect.DeepEqual(cond, condCopy) { + cond.LastTransitionTime = metav1.Now() + } conditions := transCtx.Component.Status.Conditions if conditions == nil { @@ -145,77 +239,42 @@ func (t *componentAccountProvisionTransformer) markProvisioned(transCtx *compone for i, c := range conditions { if c.Type == cond.Type { existed = true - conditions[i] = cond + conditions[i] = *cond } } if !existed { - conditions = append(conditions, cond) + conditions = append(conditions, *cond) } transCtx.Component.Status.Conditions = conditions } -func (t *componentAccountProvisionTransformer) isAccountProvisioned(cond metav1.Condition, account appsv1.SystemAccount) bool { - if len(cond.Message) == 0 { - return false - } - accounts := strings.Split(cond.Message, ",") - return slices.Contains(accounts, account.Name) -} - -func (t *componentAccountProvisionTransformer) markAccountProvisioned(cond *metav1.Condition, account appsv1.SystemAccount) { - if len(cond.Message) == 0 { - cond.Message = account.Name - return - } +func (t *componentAccountProvisionTransformer) addOrUpdateProvisionedAccount(cond *metav1.Condition, account, hashedPassword string) { accounts := strings.Split(cond.Message, ",") - if slices.Contains(accounts, account.Name) { - return + idx := slices.Index(accounts, account) + if idx >= 0 { + accounts[idx] = fmt.Sprintf("%s:%s", account, hashedPassword) + } else { + accounts = append(accounts, fmt.Sprintf("%s:%s", account, hashedPassword)) } - accounts = append(accounts, account.Name) cond.Message = strings.Join(accounts, ",") } -func (t *componentAccountProvisionTransformer) lifecycleAction(transCtx *componentTransformContext) (lifecycle.Lifecycle, error) { - synthesizedComp := transCtx.SynthesizeComponent - pods, err := component.ListOwnedPods(transCtx.Context, transCtx.Client, - synthesizedComp.Namespace, synthesizedComp.ClusterName, synthesizedComp.Name) - if err != nil { - return nil, err - } - lfa, err := lifecycle.New(transCtx.SynthesizeComponent, nil, pods...) - if err != nil { - return nil, err - } - return lfa, nil -} - -func (t *componentAccountProvisionTransformer) provisionAccount(transCtx *componentTransformContext, - _ metav1.Condition, lfa lifecycle.Lifecycle, account appsv1.SystemAccount) error { - - synthesizedComp := transCtx.SynthesizeComponent - secret, err := t.getAccountSecret(transCtx, synthesizedComp, account) - if err != nil { - return err - } - - username, password := secret.Data[constant.AccountNameForSecret], secret.Data[constant.AccountPasswdForSecret] - if len(username) == 0 || len(password) == 0 { - return nil - } - - err = lfa.AccountProvision(transCtx.Context, transCtx.Client, nil, account.Statement, string(username), string(password)) - return lifecycle.IgnoreNotDefined(err) +func (t *componentAccountProvisionTransformer) removeProvisionedAccount(cond *metav1.Condition, account string) { + accounts := strings.Split(cond.Message, ",") + accounts = slices.DeleteFunc(accounts, func(s string) bool { + return s == account + }) + cond.Message = strings.Join(accounts, ",") } -func (t *componentAccountProvisionTransformer) getAccountSecret(ctx graph.TransformContext, - synthesizeComp *component.SynthesizedComponent, account appsv1.SystemAccount) (*corev1.Secret, error) { - secretKey := types.NamespacedName{ - Namespace: synthesizeComp.Namespace, - Name: constant.GenerateAccountSecretName(synthesizeComp.ClusterName, synthesizeComp.Name, account.Name), - } - secret := &corev1.Secret{} - if err := ctx.GetClient().Get(ctx.GetContext(), secretKey, secret); err != nil { - return nil, err +func (t *componentAccountProvisionTransformer) getHashedPasswordFromCond(cond *metav1.Condition, account string) string { + accounts := strings.Split(cond.Message, ",") + idx := slices.Index(accounts, account) + if idx >= 0 { + val := strings.Split(accounts[idx], ":") + if len(val) == 2 { + return val[1] + } } - return secret, nil + return "" } diff --git a/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml b/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml index 2d4cc34c940..97c5f6892a8 100644 --- a/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml +++ b/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml @@ -5276,6 +5276,10 @@ spec: ComponentDefinition. items: properties: + disabled: + default: false + description: Specifies whether the system account is disabled. + type: boolean name: description: The name of the system account. type: string @@ -5334,6 +5338,11 @@ spec: namespace: description: The namespace where the secret is located. type: string + password: + default: password + description: The key in the secret data that contains + the password. + type: string required: - name - namespace @@ -13977,6 +13986,11 @@ spec: ComponentDefinition. items: properties: + disabled: + default: false + description: Specifies whether the system account + is disabled. + type: boolean name: description: The name of the system account. type: string @@ -14036,6 +14050,11 @@ spec: description: The namespace where the secret is located. type: string + password: + default: password + description: The key in the secret data that contains + the password. + type: string required: - name - namespace diff --git a/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml b/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml index fe6bec4e932..ec919134eab 100644 --- a/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml +++ b/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml @@ -4502,7 +4502,7 @@ spec: - KB_ACCOUNT_NAME: The name of the system account to be created. - - KB_ACCOUNT_PASSWORD: The password for the system account. // TODO: how to pass the password securely? + - KB_ACCOUNT_PASSWORD: The password for the system account. - KB_ACCOUNT_STATEMENT: The statement used to create the system account. @@ -16727,30 +16727,35 @@ spec: Cannot be updated. type: string type: object - secretRef: + statement: description: |- - Refers to the secret from which data will be copied to create the new account. + Defines the statements used to create, delete, and update the account. This field is immutable once set. properties: - name: - description: The unique identifier of the secret. + create: + description: |- + The statement to create a new account with the necessary privileges. + + + This field is immutable once set. type: string - namespace: - description: The namespace where the secret is located. + delete: + description: |- + The statement to delete a account. + + + This field is immutable once set. type: string - required: - - name - - namespace - type: object - statement: - description: |- - Defines the statement used to create the account with the necessary privileges. + update: + description: |- + The statement to update an existing account. - This field is immutable once set. - type: string + This field is immutable once set. + type: string + type: object required: - name type: object diff --git a/deploy/helm/crds/apps.kubeblocks.io_components.yaml b/deploy/helm/crds/apps.kubeblocks.io_components.yaml index 9da4fd1e563..6629aa574ab 100644 --- a/deploy/helm/crds/apps.kubeblocks.io_components.yaml +++ b/deploy/helm/crds/apps.kubeblocks.io_components.yaml @@ -5471,6 +5471,10 @@ spec: description: Overrides system accounts defined in referenced ComponentDefinition. items: properties: + disabled: + default: false + description: Specifies whether the system account is disabled. + type: boolean name: description: The name of the system account. type: string @@ -5529,6 +5533,11 @@ spec: namespace: description: The namespace where the secret is located. type: string + password: + default: password + description: The key in the secret data that contains the + password. + type: string required: - name - namespace diff --git a/docs/developer_docs/api-reference/cluster.md b/docs/developer_docs/api-reference/cluster.md index eebf2a02817..744a765a7a9 100644 --- a/docs/developer_docs/api-reference/cluster.md +++ b/docs/developer_docs/api-reference/cluster.md @@ -5771,6 +5771,7 @@ and other administrative tasks.

The container executing this action has access to following variables:

Note: This field is immutable once it has been set.

@@ -6435,6 +6436,18 @@ string +disabled
+ +bool + + + +(Optional) +

Specifies whether the system account is disabled.

+ + + + passwordConfig
@@ -8661,7 +8674,7 @@ Defaults to 3. Minimum value is 1.

ProvisionSecretRef

-(Appears on:ComponentSystemAccount, SystemAccount) +(Appears on:ComponentSystemAccount)

ProvisionSecretRef represents the reference to a secret.

@@ -8696,6 +8709,18 @@ string

The namespace where the secret is located.

+ + +password
+ +string + + + +(Optional) +

The key in the secret data that contains the password.

+ +

ReplicaRole @@ -11086,12 +11111,14 @@ bool statement
-string + +SystemAccountStatement + (Optional) -

Defines the statement used to create the account with the necessary privileges.

+

Defines the statements used to create, delete, and update the account.

This field is immutable once set.

@@ -11110,18 +11137,59 @@ PasswordConfig

This field is immutable once set.

+ + +

SystemAccountStatement +

+

+(Appears on:SystemAccount) +

+
+
+ + + + + + + + + + + + + + + + diff --git a/pkg/constant/labels.go b/pkg/constant/labels.go index 05a867e870c..12163c1ca2e 100644 --- a/pkg/constant/labels.go +++ b/pkg/constant/labels.go @@ -82,15 +82,6 @@ func GetCompLabelsWithDef(clusterName, compName, compDef string, labels ...map[s return withShardingNameLabel(m, labels...) } -func GetConfigurationLabels(clusterName, compName, cmTplName string) map[string]string { - return map[string]string{ - AppManagedByLabelKey: AppName, - AppInstanceLabelKey: clusterName, - KBAppComponentLabelKey: compName, - CMTemplateNameLabelKey: cmTplName, - } -} - func withShardingNameLabel(labels map[string]string, extraLabels ...map[string]string) map[string]string { for _, m := range extraLabels { if m != nil { @@ -102,3 +93,12 @@ func withShardingNameLabel(labels map[string]string, extraLabels ...map[string]s } return labels } + +func GetConfigurationLabels(clusterName, compName, cmTplName string) map[string]string { + return map[string]string{ + AppManagedByLabelKey: AppName, + AppInstanceLabelKey: clusterName, + KBAppComponentLabelKey: compName, + CMTemplateNameLabelKey: cmTplName, + } +} diff --git a/pkg/controller/builder/builder_component_definition.go b/pkg/controller/builder/builder_component_definition.go index 92497251145..bd439f84dfe 100644 --- a/pkg/controller/builder/builder_component_definition.go +++ b/pkg/controller/builder/builder_component_definition.go @@ -192,19 +192,6 @@ func (builder *ComponentDefinitionBuilder) SetReplicasLimit(minReplicas, maxRepl return builder } -func (builder *ComponentDefinitionBuilder) AddSystemAccount(accountName string, initAccount bool, statement string) *ComponentDefinitionBuilder { - account := appsv1.SystemAccount{ - Name: accountName, - InitAccount: initAccount, - Statement: statement, - } - if builder.get().Spec.SystemAccounts == nil { - builder.get().Spec.SystemAccounts = make([]appsv1.SystemAccount, 0) - } - builder.get().Spec.SystemAccounts = append(builder.get().Spec.SystemAccounts, account) - return builder -} - func (builder *ComponentDefinitionBuilder) SetUpdateStrategy(strategy *appsv1.UpdateStrategy) *ComponentDefinitionBuilder { builder.get().Spec.UpdateStrategy = strategy return builder diff --git a/pkg/controller/component/component_version.go b/pkg/controller/component/component_version.go index d34b6abd16b..16d642d23df 100644 --- a/pkg/controller/component/component_version.go +++ b/pkg/controller/component/component_version.go @@ -216,6 +216,9 @@ func actionsToResolveImage(compDef *appsv1.ComponentDefinition) map[string]*apps if compDef.Spec.LifecycleActions.RoleProbe != nil { actions[normalize("roleProbe")] = &compDef.Spec.LifecycleActions.RoleProbe.Action } + if compDef.Spec.LifecycleActions.AvailableProbe != nil { + actions[normalize("availableProbe")] = &compDef.Spec.LifecycleActions.AvailableProbe.Action + } return actions } diff --git a/pkg/controller/component/its_convertor.go b/pkg/controller/component/its_convertor.go index af8af9b5d16..90ddf863833 100644 --- a/pkg/controller/component/its_convertor.go +++ b/pkg/controller/component/its_convertor.go @@ -302,8 +302,8 @@ func (c *itsCredentialConvertor) convert(args ...any) (any, error) { return nil, err } - credential := func(sysAccount kbappsv1.SystemAccount) *workloads.Credential { - secretName := constant.GenerateAccountSecretName(synthesizeComp.ClusterName, synthesizeComp.Name, sysAccount.Name) + credential := func(sysAccount string) *workloads.Credential { + secretName := constant.GenerateAccountSecretName(synthesizeComp.ClusterName, synthesizeComp.Name, sysAccount) return &workloads.Credential{ Username: workloads.CredentialVar{ ValueFrom: &corev1.EnvVarSource{ @@ -329,9 +329,9 @@ func (c *itsCredentialConvertor) convert(args ...any) (any, error) { } // use first init account as the default credential - for index, sysAccount := range synthesizeComp.SystemAccounts { + for _, sysAccount := range synthesizeComp.SystemAccounts { if sysAccount.InitAccount { - return credential(synthesizeComp.SystemAccounts[index]), nil + return credential(sysAccount.Name), nil } } return nil, nil diff --git a/pkg/controller/component/synthesize_component.go b/pkg/controller/component/synthesize_component.go index f3f102ccd8f..91bd46d386d 100644 --- a/pkg/controller/component/synthesize_component.go +++ b/pkg/controller/component/synthesize_component.go @@ -95,7 +95,7 @@ func BuildSynthesizedComponent(ctx context.Context, cli client.Reader, MinReadySeconds: compDefObj.Spec.MinReadySeconds, PolicyRules: compDefObj.Spec.PolicyRules, LifecycleActions: compDefObj.Spec.LifecycleActions, - SystemAccounts: mergeSystemAccounts(compDefObj.Spec.SystemAccounts, comp.Spec.SystemAccounts), + SystemAccounts: compDefObj.Spec.SystemAccounts, Replicas: comp.Spec.Replicas, Resources: comp.Spec.Resources, TLSConfig: comp.Spec.TLSConfig, @@ -210,35 +210,6 @@ func mergeUserDefinedEnv(synthesizedComp *SynthesizedComponent, comp *appsv1.Com return nil } -func mergeSystemAccounts(compDefAccounts []appsv1.SystemAccount, - compAccounts []appsv1.ComponentSystemAccount) []appsv1.SystemAccount { - if len(compAccounts) == 0 { - return compDefAccounts - } - - override := func(compAccount appsv1.ComponentSystemAccount, idx int) { - if compAccount.PasswordConfig != nil { - compDefAccounts[idx].PasswordGenerationPolicy = *compAccount.PasswordConfig - } - compDefAccounts[idx].SecretRef = compAccount.SecretRef - } - - tbl := make(map[string]int) - for i, account := range compDefAccounts { - tbl[account.Name] = i - } - - for _, account := range compAccounts { - idx, ok := tbl[account.Name] - if !ok { - continue // ignore it silently - } - override(account, idx) - } - - return compDefAccounts -} - func buildSchedulingPolicy(synthesizedComp *SynthesizedComponent, comp *appsv1.Component) { if comp.Spec.SchedulingPolicy != nil { schedulingPolicy := comp.Spec.SchedulingPolicy diff --git a/pkg/testutil/apps/componentdefinition_factory.go b/pkg/testutil/apps/componentdefinition_factory.go index 8871c693202..72d57b55472 100644 --- a/pkg/testutil/apps/componentdefinition_factory.go +++ b/pkg/testutil/apps/componentdefinition_factory.go @@ -267,7 +267,9 @@ func (f *MockComponentDefinitionFactory) AddSystemAccount(accountName string, in account := kbappsv1.SystemAccount{ Name: accountName, InitAccount: initAccount, - Statement: statement, + Statement: &kbappsv1.SystemAccountStatement{ + Create: statement, + }, } if f.Get().Spec.SystemAccounts == nil { f.Get().Spec.SystemAccounts = make([]kbappsv1.SystemAccount, 0) diff --git a/pkg/testutil/apps/constant.go b/pkg/testutil/apps/constant.go index c18fa35603d..46ee1fd9e39 100644 --- a/pkg/testutil/apps/constant.go +++ b/pkg/testutil/apps/constant.go @@ -196,8 +196,11 @@ var ( }, }, { - Name: "admin", - Statement: "CREATE USER $(USERNAME) IDENTIFIED BY '$(PASSWORD)'; GRANT ALL PRIVILEGES ON *.* TO $(USERNAME);", + Name: "admin", + Statement: &appsv1.SystemAccountStatement{ + Create: "CREATE USER $(USERNAME) IDENTIFIED BY '$(PASSWORD)'; GRANT ALL PRIVILEGES ON *.* TO $(USERNAME);", + Delete: "DROP USER '$(USERNAME)'@'%';", + }, PasswordGenerationPolicy: appsv1.PasswordConfig{ Length: 10, NumDigits: 5, From 791e7a4278c39000116ed6f42a7b12a7c3972b78 Mon Sep 17 00:00:00 2001 From: Leon Date: Tue, 7 Jan 2025 14:01:05 +0800 Subject: [PATCH 2/6] update --- apis/apps/v1/componentdefinition_types.go | 4 +- ...ps.kubeblocks.io_componentdefinitions.yaml | 4 +- .../apps/componentdefinition_controller.go | 16 +++-- ...transformer_component_account_provision.go | 62 +++++++++++++++---- ...ps.kubeblocks.io_componentdefinitions.yaml | 4 +- docs/developer_docs/api-reference/cluster.md | 4 +- .../component/lifecycle/lfa_account.go | 4 +- 7 files changed, 69 insertions(+), 29 deletions(-) diff --git a/apis/apps/v1/componentdefinition_types.go b/apis/apps/v1/componentdefinition_types.go index d13980a9e79..b7015ef4771 100644 --- a/apis/apps/v1/componentdefinition_types.go +++ b/apis/apps/v1/componentdefinition_types.go @@ -1751,9 +1751,9 @@ type ComponentLifecycleActions struct { // // The container executing this action has access to following variables: // - // - KB_ACCOUNT_NAME: The name of the system account to be created. + // - KB_ACCOUNT_NAME: The name of the system account to be manipulated. // - KB_ACCOUNT_PASSWORD: The password for the system account. - // - KB_ACCOUNT_STATEMENT: The statement used to create the system account. + // - KB_ACCOUNT_STATEMENT: The statement used to manipulate the system account. // // Note: This field is immutable once it has been set. // diff --git a/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml b/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml index ec919134eab..a3568885f25 100644 --- a/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml +++ b/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml @@ -4501,9 +4501,9 @@ spec: The container executing this action has access to following variables: - - KB_ACCOUNT_NAME: The name of the system account to be created. + - KB_ACCOUNT_NAME: The name of the system account to be manipulated. - KB_ACCOUNT_PASSWORD: The password for the system account. - - KB_ACCOUNT_STATEMENT: The statement used to create the system account. + - KB_ACCOUNT_STATEMENT: The statement used to manipulate the system account. Note: This field is immutable once it has been set. diff --git a/controllers/apps/componentdefinition_controller.go b/controllers/apps/componentdefinition_controller.go index 25040a3a4cd..6dc6d24d18d 100644 --- a/controllers/apps/componentdefinition_controller.go +++ b/controllers/apps/componentdefinition_controller.go @@ -342,19 +342,23 @@ func (r *ComponentDefinitionReconciler) validateConfigs(cli client.Client, rctx func (r *ComponentDefinitionReconciler) validateSystemAccounts(cli client.Client, rctx intctrlutil.RequestCtx, cmpd *appsv1.ComponentDefinition) error { - for _, v := range cmpd.Spec.SystemAccounts { - if !v.InitAccount && (cmpd.Spec.LifecycleActions == nil || cmpd.Spec.LifecycleActions.AccountProvision == nil) { - return fmt.Errorf(`the AccountProvision action is needed to provision system account %s`, v.Name) - } - } if !checkUniqueItemWithValue(cmpd.Spec.SystemAccounts, "Name", nil) { return fmt.Errorf("duplicate system accounts are not allowed") } + + hasNonInitAccount := false for _, account := range cmpd.Spec.SystemAccounts { - if !account.InitAccount && (account.Statement == nil || len(account.Statement.Create) == 0) { + if account.InitAccount { + continue + } + hasNonInitAccount = true + if account.Statement == nil || len(account.Statement.Create) == 0 { return fmt.Errorf("the create statement must be provided to provision system account: %s", account.Name) } } + if hasNonInitAccount && (cmpd.Spec.LifecycleActions == nil || cmpd.Spec.LifecycleActions.AccountProvision == nil) { + return fmt.Errorf("the AccountProvision action is needed to provision system accounts") + } return nil } diff --git a/controllers/apps/transformer_component_account_provision.go b/controllers/apps/transformer_component_account_provision.go index b3d5762f5b5..72974187426 100644 --- a/controllers/apps/transformer_component_account_provision.go +++ b/controllers/apps/transformer_component_account_provision.go @@ -85,7 +85,7 @@ func (t *componentAccountProvisionTransformer) Transform(ctx graph.TransformCont protoNameSet := sets.New(maps.Keys(secrets)...) cond := t.provisionCond(transCtx) - provisionedNameSet := sets.New(strings.Split(cond.Message, ",")...) + provisionedNameSet := t.getProvisionedAccounts(cond) createSet, deleteSet, updateSet := setDiff(provisionedNameSet, protoNameSet) if len(createSet) == 0 && len(deleteSet) == 0 && len(updateSet) == 0 { @@ -160,13 +160,17 @@ func (t *componentAccountProvisionTransformer) createAccount(transCtx *component if err == nil { // TODO: how about the password restored from backup? - t.addOrUpdateProvisionedAccount(cond, account.Name, secret.Annotations[systemAccountHashAnnotation]) + t.updateProvisionedAccount(cond, account.Name, secret.Annotations[systemAccountHashAnnotation]) } return err } func (t *componentAccountProvisionTransformer) deleteAccount(transCtx *componentTransformContext, lfa lifecycle.Lifecycle, cond *metav1.Condition, account synthesizedSystemAccount) error { + if account.Statement == nil || len(account.Statement.Delete) == 0 { + return fmt.Errorf("has no delete statement defined for system account: %s", account.Name) + } + err := lfa.AccountProvision(transCtx.Context, transCtx.Client, nil, account.Statement.Delete, account.Name, "") if lifecycle.IgnoreNotDefined(err) == nil { t.removeProvisionedAccount(cond, account.Name) @@ -176,16 +180,23 @@ func (t *componentAccountProvisionTransformer) deleteAccount(transCtx *component func (t *componentAccountProvisionTransformer) updateAccount(transCtx *componentTransformContext, lfa lifecycle.Lifecycle, cond *metav1.Condition, account synthesizedSystemAccount, secret *corev1.Secret) error { - hashedPassword := t.getHashedPasswordFromCond(cond, account.Name) + hashedPassword := t.hashedPasswordFromCond(cond, account.Name) + if hashedPassword == "" { + return nil // does not support password update? + } if verifySystemAccountPassword(secret, []byte(hashedPassword)) { return nil // the password is not changed } + if account.Statement == nil || len(account.Statement.Update) == 0 { + return fmt.Errorf("has no update statement defined for system account: %s", account.Name) + } + // TODO: how to notify other apps to update the new password? err := t.provision(transCtx, lfa, account.Statement.Update, secret) if err == nil { - t.addOrUpdateProvisionedAccount(cond, account.Name, secret.Annotations[systemAccountHashAnnotation]) + t.updateProvisionedAccount(cond, account.Name, secret.Annotations[systemAccountHashAnnotation]) } return err } @@ -225,11 +236,11 @@ func (t *componentAccountProvisionTransformer) provisionCondDone(transCtx *compo cond.Status = metav1.ConditionFalse // cond.Reason = err.Error() // TODO: error } - cond.ObservedGeneration = transCtx.Component.Generation if !reflect.DeepEqual(cond, condCopy) { cond.LastTransitionTime = metav1.Now() } + cond.ObservedGeneration = transCtx.Component.Generation conditions := transCtx.Component.Status.Conditions if conditions == nil { @@ -248,9 +259,26 @@ func (t *componentAccountProvisionTransformer) provisionCondDone(transCtx *compo transCtx.Component.Status.Conditions = conditions } -func (t *componentAccountProvisionTransformer) addOrUpdateProvisionedAccount(cond *metav1.Condition, account, hashedPassword string) { - accounts := strings.Split(cond.Message, ",") - idx := slices.Index(accounts, account) +func (t *componentAccountProvisionTransformer) getProvisionedAccounts(cond metav1.Condition) sets.Set[string] { + accounts := sets.New[string]() + if len(cond.Message) > 0 { + for _, e := range strings.Split(cond.Message, ",") { + if len(e) > 0 { + accounts.Insert(strings.Split(e, ":")[0]) + } + } + } + return accounts +} + +func (t *componentAccountProvisionTransformer) updateProvisionedAccount(cond *metav1.Condition, account, hashedPassword string) { + accounts := make([]string, 0) + if len(cond.Message) > 0 { + accounts = strings.Split(cond.Message, ",") + } + idx := slices.IndexFunc(accounts, func(s string) bool { + return strings.HasPrefix(s, fmt.Sprintf("%s:", account)) + }) if idx >= 0 { accounts[idx] = fmt.Sprintf("%s:%s", account, hashedPassword) } else { @@ -260,16 +288,24 @@ func (t *componentAccountProvisionTransformer) addOrUpdateProvisionedAccount(con } func (t *componentAccountProvisionTransformer) removeProvisionedAccount(cond *metav1.Condition, account string) { - accounts := strings.Split(cond.Message, ",") + accounts := make([]string, 0) + if len(cond.Message) > 0 { + accounts = strings.Split(cond.Message, ",") + } accounts = slices.DeleteFunc(accounts, func(s string) bool { - return s == account + return strings.HasPrefix(s, fmt.Sprintf("%s:", account)) }) cond.Message = strings.Join(accounts, ",") } -func (t *componentAccountProvisionTransformer) getHashedPasswordFromCond(cond *metav1.Condition, account string) string { - accounts := strings.Split(cond.Message, ",") - idx := slices.Index(accounts, account) +func (t *componentAccountProvisionTransformer) hashedPasswordFromCond(cond *metav1.Condition, account string) string { + accounts := make([]string, 0) + if len(cond.Message) > 0 { + accounts = strings.Split(cond.Message, ",") + } + idx := slices.IndexFunc(accounts, func(s string) bool { + return strings.HasPrefix(s, fmt.Sprintf("%s:", account)) + }) if idx >= 0 { val := strings.Split(accounts[idx], ":") if len(val) == 2 { diff --git a/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml b/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml index ec919134eab..a3568885f25 100644 --- a/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml +++ b/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml @@ -4501,9 +4501,9 @@ spec: The container executing this action has access to following variables: - - KB_ACCOUNT_NAME: The name of the system account to be created. + - KB_ACCOUNT_NAME: The name of the system account to be manipulated. - KB_ACCOUNT_PASSWORD: The password for the system account. - - KB_ACCOUNT_STATEMENT: The statement used to create the system account. + - KB_ACCOUNT_STATEMENT: The statement used to manipulate the system account. Note: This field is immutable once it has been set. diff --git a/docs/developer_docs/api-reference/cluster.md b/docs/developer_docs/api-reference/cluster.md index 744a765a7a9..11b4d786f1e 100644 --- a/docs/developer_docs/api-reference/cluster.md +++ b/docs/developer_docs/api-reference/cluster.md @@ -5770,9 +5770,9 @@ This action is designed to create system accounts that are utilized for replicat and other administrative tasks.

The container executing this action has access to following variables:

    -
  • KB_ACCOUNT_NAME: The name of the system account to be created.
  • +
  • KB_ACCOUNT_NAME: The name of the system account to be manipulated.
  • KB_ACCOUNT_PASSWORD: The password for the system account.
  • -
  • KB_ACCOUNT_STATEMENT: The statement used to create the system account.
  • +
  • KB_ACCOUNT_STATEMENT: The statement used to manipulate the system account.

Note: This field is immutable once it has been set.

diff --git a/pkg/controller/component/lifecycle/lfa_account.go b/pkg/controller/component/lifecycle/lfa_account.go index bdf438c1eb8..a70b9893ca1 100644 --- a/pkg/controller/component/lifecycle/lfa_account.go +++ b/pkg/controller/component/lifecycle/lfa_account.go @@ -46,9 +46,9 @@ func (a *accountProvision) name() string { func (a *accountProvision) parameters(ctx context.Context, cli client.Reader) (map[string]string, error) { // The container executing this action has access to following variables: // - // - KB_ACCOUNT_NAME: The name of the system account to be created. + // - KB_ACCOUNT_NAME: The name of the system account to be manipulated. // - KB_ACCOUNT_PASSWORD: The password for the system account. - // - KB_ACCOUNT_STATEMENT: The statement used to create the system account. + // - KB_ACCOUNT_STATEMENT: The statement used to manipulate the system account. return map[string]string{ accountName: a.user, accountPassword: a.password, From f1d249456fbdee05f86257ef84c683d41b0cae0d Mon Sep 17 00:00:00 2001 From: Leon Date: Tue, 7 Jan 2025 17:42:18 +0800 Subject: [PATCH 3/6] cannot disable init system accounts --- .../apps/transformer_component_account.go | 20 +++++++++++++------ ...transformer_component_account_provision.go | 2 +- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/controllers/apps/transformer_component_account.go b/controllers/apps/transformer_component_account.go index fff21d859c4..51b6ff794d3 100644 --- a/controllers/apps/transformer_component_account.go +++ b/controllers/apps/transformer_component_account.go @@ -73,7 +73,11 @@ func (t *componentAccountTransformer) Transform(ctx graph.TransformContext, dag runningNameSet := sets.New(maps.Keys(secrets)...) // proto accounts - accounts := synthesizeSystemAccounts(transCtx.CompDef.Spec.SystemAccounts, transCtx.Component.Spec.SystemAccounts, false) + accounts, err := synthesizeSystemAccounts(transCtx.CompDef.Spec.SystemAccounts, + transCtx.Component.Spec.SystemAccounts, false) + if err != nil { + return err + } protoNameSet := sets.New(maps.Keys(accounts)...) createSet, deleteSet, updateSet := setDiff(runningNameSet, protoNameSet) @@ -265,7 +269,7 @@ type synthesizedSystemAccount struct { } func synthesizeSystemAccounts(compDefAccounts []appsv1.SystemAccount, - compAccounts []appsv1.ComponentSystemAccount, keepDisabled bool) map[string]synthesizedSystemAccount { + compAccounts []appsv1.ComponentSystemAccount, keepDisabled bool) (map[string]synthesizedSystemAccount, error) { accounts := make(map[string]synthesizedSystemAccount) for _, account := range compDefAccounts { accounts[account.Name] = synthesizedSystemAccount{ @@ -285,17 +289,21 @@ func synthesizeSystemAccounts(compDefAccounts []appsv1.SystemAccount, for i := range compAccounts { account, ok := accounts[compAccounts[i].Name] if !ok { - continue // ignore it silently + return nil, fmt.Errorf("system account %s not defined in component definition", compAccounts[i].Name) } - accounts[compAccounts[i].Name] = merge(account, compAccounts[i]) + accounts[account.Name] = merge(account, compAccounts[i]) } if !keepDisabled { for _, name := range maps.Keys(accounts) { - if accounts[name].Disabled != nil && *accounts[name].Disabled { + account := accounts[name] + if account.Disabled != nil && *account.Disabled { + if account.InitAccount { + return nil, fmt.Errorf("cannot disable init system account: %s", name) + } delete(accounts, name) } } } - return accounts + return accounts, nil } diff --git a/controllers/apps/transformer_component_account_provision.go b/controllers/apps/transformer_component_account_provision.go index 72974187426..8c7aef35459 100644 --- a/controllers/apps/transformer_component_account_provision.go +++ b/controllers/apps/transformer_component_account_provision.go @@ -76,7 +76,7 @@ func (t *componentAccountProvisionTransformer) Transform(ctx graph.TransformCont return nil } - accounts := synthesizeSystemAccounts(compDef.Spec.SystemAccounts, comp.Spec.SystemAccounts, true) + accounts, _ := synthesizeSystemAccounts(compDef.Spec.SystemAccounts, comp.Spec.SystemAccounts, true) secrets, err1 := listSystemAccountObjects(ctx, transCtx.SynthesizeComponent) if err1 != nil { From c28695cb37582dd1b6c2787c23692202a3a4c06e Mon Sep 17 00:00:00 2001 From: Leon Date: Tue, 7 Jan 2025 17:48:48 +0800 Subject: [PATCH 4/6] update init account's password --- ...transformer_component_account_provision.go | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/controllers/apps/transformer_component_account_provision.go b/controllers/apps/transformer_component_account_provision.go index 8c7aef35459..51fc68838ea 100644 --- a/controllers/apps/transformer_component_account_provision.go +++ b/controllers/apps/transformer_component_account_provision.go @@ -144,18 +144,19 @@ func (t *componentAccountProvisionTransformer) lifecycleAction(transCtx *compone func (t *componentAccountProvisionTransformer) createAccount(transCtx *componentTransformContext, lfa lifecycle.Lifecycle, cond *metav1.Condition, account synthesizedSystemAccount, secret *corev1.Secret) error { - // The secret of initAccount should be rendered into the config file, - // or injected into the container through specific account&password environment variables name supported by the engine. - // When the engine starts up, it will automatically load and create this account. - if account.InitAccount { - return nil - } + var ( + err error + ) - var err error - if transCtx.SynthesizeComponent.Annotations[constant.RestoreFromBackupAnnotationKey] == "" { + // The secret of an initial account should be injected into the container through + // specific account&password environment variables name supported by the engine. + // When the engine starts up, it will automatically load and create this account. + if !account.InitAccount { // TODO: restore account secret from backup. - // provision account when the component is not recovered from backup - err = t.provision(transCtx, lfa, account.Statement.Create, secret) + if transCtx.SynthesizeComponent.Annotations[constant.RestoreFromBackupAnnotationKey] == "" { + // provision account when the component is not recovered from backup + err = t.provision(transCtx, lfa, account.Statement.Create, secret) + } } if err == nil { @@ -182,7 +183,7 @@ func (t *componentAccountProvisionTransformer) updateAccount(transCtx *component lfa lifecycle.Lifecycle, cond *metav1.Condition, account synthesizedSystemAccount, secret *corev1.Secret) error { hashedPassword := t.hashedPasswordFromCond(cond, account.Name) if hashedPassword == "" { - return nil // does not support password update? + return nil // passwords that generated by KB or restored from backup, do not support updating? } if verifySystemAccountPassword(secret, []byte(hashedPassword)) { return nil // the password is not changed From 675f06f24badee7f89e4655ad523a4ac9f32e9b2 Mon Sep 17 00:00:00 2001 From: Leon Date: Wed, 8 Jan 2025 13:42:27 +0800 Subject: [PATCH 5/6] test cases --- controllers/apps/component_controller_test.go | 417 +++++++++++++----- .../apps/transformer_cluster_component.go | 1 + .../apps/transformer_component_account.go | 34 +- pkg/testutil/apps/cluster_factory.go | 4 +- pkg/testutil/apps/kb_agent_util.go | 51 --- pkg/testutil/dataprotection/backup_utils.go | 2 +- 6 files changed, 348 insertions(+), 161 deletions(-) diff --git a/controllers/apps/component_controller_test.go b/controllers/apps/component_controller_test.go index b98dda0faed..7ebc97f9b0c 100644 --- a/controllers/apps/component_controller_test.go +++ b/controllers/apps/component_controller_test.go @@ -963,98 +963,6 @@ var _ = Describe("Component Controller", func() { })).Should(Succeed()) } - testCompSystemAccount := func(compName, compDefName string) { - createClusterObj(compName, compDefName, nil) - - By("check root account") - rootSecretKey := types.NamespacedName{ - Namespace: compObj.Namespace, - Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "root"), - } - Eventually(testapps.CheckObj(&testCtx, rootSecretKey, func(g Gomega, secret *corev1.Secret) { - g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountNameForSecret, []byte("root"))) - g.Expect(secret.Data).Should(HaveKey(constant.AccountPasswdForSecret)) - })).Should(Succeed()) - - By("check admin account") - adminSecretKey := types.NamespacedName{ - Namespace: compObj.Namespace, - Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "admin"), - } - Eventually(testapps.CheckObj(&testCtx, adminSecretKey, func(g Gomega, secret *corev1.Secret) { - g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountNameForSecret, []byte("admin"))) - g.Expect(secret.Data).Should(HaveKey(constant.AccountPasswdForSecret)) - })).Should(Succeed()) - - By("mock component as Running") - mockCompRunning(compName) - - By("wait accounts to be provisioned") - Eventually(testapps.CheckObj(&testCtx, compKey, func(g Gomega, comp *kbappsv1.Component) { - g.Expect(len(comp.Status.Conditions) > 0).Should(BeTrue()) - var cond *metav1.Condition - for i, c := range comp.Status.Conditions { - if c.Type == accountProvisionConditionType { - cond = &comp.Status.Conditions[i] - break - } - } - g.Expect(cond).ShouldNot(BeNil()) - g.Expect(cond.Status).Should(BeEquivalentTo(metav1.ConditionTrue)) - g.Expect(cond.Message).ShouldNot(ContainSubstring("root")) - g.Expect(cond.Message).Should(ContainSubstring("admin")) - })).Should(Succeed()) - } - - testCompSystemAccountOverride := func(compName, compDefName string) { - passwordConfig := &kbappsv1.PasswordConfig{ - Length: 29, - } - secret := corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: testCtx.DefaultNamespace, - Name: "sysaccount-override", - }, - StringData: map[string]string{ - constant.AccountPasswdForSecret: "sysaccount-override", - }, - } - secretRef := func() *kbappsv1.ProvisionSecretRef { - Expect(testCtx.CreateObj(testCtx.Ctx, &secret)).Should(Succeed()) - return &kbappsv1.ProvisionSecretRef{ - Name: secret.Name, - Namespace: testCtx.DefaultNamespace, - } - } - - createClusterObj(compName, compDefName, func(f *testapps.MockClusterFactory) { - f.AddSystemAccount("root", passwordConfig, nil). - AddSystemAccount("admin", nil, secretRef()). - AddSystemAccount("not-exist", nil, nil) - }) - - By("check root account") - rootSecretKey := types.NamespacedName{ - Namespace: compObj.Namespace, - Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "root"), - } - Eventually(testapps.CheckObj(&testCtx, rootSecretKey, func(g Gomega, secret *corev1.Secret) { - g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountNameForSecret, []byte("root"))) - g.Expect(secret.Data).Should(HaveKey(constant.AccountPasswdForSecret)) - g.Expect(secret.Data[constant.AccountPasswdForSecret]).Should(HaveLen(int(passwordConfig.Length))) - })).Should(Succeed()) - - By("check admin account") - adminSecretKey := types.NamespacedName{ - Namespace: compObj.Namespace, - Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "admin"), - } - Eventually(testapps.CheckObj(&testCtx, adminSecretKey, func(g Gomega, secret *corev1.Secret) { - g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountNameForSecret, []byte("admin"))) - g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountPasswdForSecret, secret.Data[constant.AccountPasswdForSecret])) - })).Should(Succeed()) - } - testCompVars := func(compName, compDefName string) { compDefKey := client.ObjectKeyFromObject(compDefObj) Eventually(testapps.GetAndChangeObj(&testCtx, compDefKey, func(compDef *kbappsv1.ComponentDefinition) { @@ -1426,6 +1334,293 @@ var _ = Describe("Component Controller", func() { checkRBACResourcesExistence(saName, true) } + testCompSystemAccount := func(compName, compDefName string) { + secret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testCtx.DefaultNamespace, + Name: "sysaccount-admin", + }, + StringData: map[string]string{ + constant.AccountPasswdForSecret: "sysaccount-admin", + }, + } + secretRef := func() *kbappsv1.ProvisionSecretRef { + Expect(testCtx.CreateObj(testCtx.Ctx, &secret)).Should(Succeed()) + return &kbappsv1.ProvisionSecretRef{ + Name: secret.Name, + Namespace: testCtx.DefaultNamespace, + } + } + + createClusterObj(compName, compDefName, func(f *testapps.MockClusterFactory) { + f.AddSystemAccount("admin", false, nil, secretRef()) + }) + + By("check root account") + var rootHashedPassword string + rootSecretKey := types.NamespacedName{ + Namespace: compObj.Namespace, + Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "root"), + } + Eventually(testapps.CheckObj(&testCtx, rootSecretKey, func(g Gomega, secret *corev1.Secret) { + g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountNameForSecret, []byte("root"))) + g.Expect(secret.Data).Should(HaveKey(constant.AccountPasswdForSecret)) + rootHashedPassword = secret.Annotations[systemAccountHashAnnotation] + g.Expect(rootHashedPassword).Should(BeEmpty()) // kb generated password + })).Should(Succeed()) + + By("check admin account") + var adminHashedPassword string + adminSecretKey := types.NamespacedName{ + Namespace: compObj.Namespace, + Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "admin"), + } + Eventually(testapps.CheckObj(&testCtx, adminSecretKey, func(g Gomega, secret *corev1.Secret) { + g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountNameForSecret, []byte("admin"))) + g.Expect(secret.Data).Should(HaveKey(constant.AccountPasswdForSecret)) + adminHashedPassword = secret.Annotations[systemAccountHashAnnotation] + g.Expect(adminHashedPassword).ShouldNot(BeEmpty()) // user-provided + })).Should(Succeed()) + + By("mock component as Running") + mockCompRunning(compName) + + By("wait accounts to be provisioned") + Eventually(testapps.CheckObj(&testCtx, compKey, func(g Gomega, comp *kbappsv1.Component) { + g.Expect(len(comp.Status.Conditions) > 0).Should(BeTrue()) + var cond *metav1.Condition + for i, c := range comp.Status.Conditions { + if c.Type == accountProvisionConditionType { + cond = &comp.Status.Conditions[i] + break + } + } + g.Expect(cond).ShouldNot(BeNil()) + g.Expect(cond.Status).Should(BeEquivalentTo(metav1.ConditionTrue)) + g.Expect(cond.Message).Should(ContainSubstring(fmt.Sprintf("%s:%s", "root", rootHashedPassword))) + g.Expect(cond.Message).Should(ContainSubstring(fmt.Sprintf("%s:%s", "admin", adminHashedPassword))) + })).Should(Succeed()) + } + + testCompSystemAccountOverride := func(compName, compDefName string) { + passwordConfig := &kbappsv1.PasswordConfig{ + Length: 29, + } + secret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testCtx.DefaultNamespace, + Name: "sysaccount-override", + }, + StringData: map[string]string{ + constant.AccountPasswdForSecret: "sysaccount-override", + }, + } + secretRef := func() *kbappsv1.ProvisionSecretRef { + Expect(testCtx.CreateObj(testCtx.Ctx, &secret)).Should(Succeed()) + return &kbappsv1.ProvisionSecretRef{ + Name: secret.Name, + Namespace: testCtx.DefaultNamespace, + } + } + + createClusterObj(compName, compDefName, func(f *testapps.MockClusterFactory) { + f.AddSystemAccount("root", false, passwordConfig, nil). + AddSystemAccount("admin", false, nil, secretRef()) + }) + + By("check root account") + rootSecretKey := types.NamespacedName{ + Namespace: compObj.Namespace, + Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "root"), + } + Eventually(testapps.CheckObj(&testCtx, rootSecretKey, func(g Gomega, secret *corev1.Secret) { + g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountNameForSecret, []byte("root"))) + g.Expect(secret.Data).Should(HaveKey(constant.AccountPasswdForSecret)) + g.Expect(secret.Data[constant.AccountPasswdForSecret]).Should(HaveLen(int(passwordConfig.Length))) + })).Should(Succeed()) + + By("check admin account") + adminSecretKey := types.NamespacedName{ + Namespace: compObj.Namespace, + Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "admin"), + } + Eventually(testapps.CheckObj(&testCtx, adminSecretKey, func(g Gomega, secret *corev1.Secret) { + g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountNameForSecret, []byte("admin"))) + g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountPasswdForSecret, secret.Data[constant.AccountPasswdForSecret])) + })).Should(Succeed()) + } + + testCompSystemAccountDisable := func(compName, compDefName string) { + passwordConfig := &kbappsv1.PasswordConfig{ + Length: 29, + } + + createClusterObj(compName, compDefName, func(f *testapps.MockClusterFactory) { + f.AddSystemAccount("root", false, passwordConfig, nil). + // disable the admin account + AddSystemAccount("admin", true, passwordConfig, nil) + }) + + By("check root account") + rootSecretKey := types.NamespacedName{ + Namespace: compObj.Namespace, + Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "root"), + } + rootSecret := &corev1.Secret{} + Eventually(testapps.CheckObjExists(&testCtx, rootSecretKey, rootSecret, true)).Should(Succeed()) + + By("check admin account") + adminSecretKey := types.NamespacedName{ + Namespace: compObj.Namespace, + Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "admin"), + } + adminSecret := &corev1.Secret{} + Consistently(testapps.CheckObjExists(&testCtx, adminSecretKey, adminSecret, false)).Should(Succeed()) + } + + testCompSystemAccountDisableAfterProvision := func(compName, compDefName string) { + passwordConfig := &kbappsv1.PasswordConfig{ + Length: 29, + } + + createClusterObj(compName, compDefName, func(f *testapps.MockClusterFactory) { + f.AddSystemAccount("root", false, passwordConfig, nil). + AddSystemAccount("admin", false, passwordConfig, nil) + }) + + By("check the root account") + rootSecretKey := types.NamespacedName{ + Namespace: compObj.Namespace, + Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "root"), + } + rootSecret := &corev1.Secret{} + Eventually(testapps.CheckObjExists(&testCtx, rootSecretKey, rootSecret, true)).Should(Succeed()) + + By("check the admin account") + adminSecretKey := types.NamespacedName{ + Namespace: compObj.Namespace, + Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "admin"), + } + adminSecret := &corev1.Secret{} + Eventually(testapps.CheckObjExists(&testCtx, adminSecretKey, adminSecret, true)).Should(Succeed()) + + By("disable the admin account") + Expect(testapps.GetAndChangeObj(&testCtx, clusterKey, func(cluster *kbappsv1.Cluster) { + for i, comp := range cluster.Spec.ComponentSpecs { + if comp.Name == compName { + for j, account := range cluster.Spec.ComponentSpecs[i].SystemAccounts { + if account.Name == "admin" { + cluster.Spec.ComponentSpecs[i].SystemAccounts[j].Disabled = ptr.To(true) + } + } + } + } + })()).Should(Succeed()) + + By("check the admin account is disabled") + Eventually(testapps.CheckObjExists(&testCtx, adminSecretKey, adminSecret, false)).Should(Succeed()) + } + + testCompSystemAccountUpdate := func(compName, compDefName string) { + passwordConfig := &kbappsv1.PasswordConfig{ + Length: 29, + } + secret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testCtx.DefaultNamespace, + Name: "sysaccount-update", + }, + StringData: map[string]string{ + "sysaccount-update": "sysaccount-update", + }, + } + secretRef := func() *kbappsv1.ProvisionSecretRef { + Expect(testCtx.CreateObj(testCtx.Ctx, &secret)).Should(Succeed()) + return &kbappsv1.ProvisionSecretRef{ + Name: secret.Name, + Namespace: testCtx.DefaultNamespace, + Password: "sysaccount-update", + } + } + + createClusterObj(compName, compDefName, func(f *testapps.MockClusterFactory) { + f.AddSystemAccount("root", false, passwordConfig, nil). + AddSystemAccount("admin", false, nil, secretRef()) + }) + + By("check root account") + var rootHashedPassword string + rootSecretKey := types.NamespacedName{ + Namespace: compObj.Namespace, + Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "root"), + } + Eventually(testapps.CheckObj(&testCtx, rootSecretKey, func(g Gomega, secret *corev1.Secret) { + g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountNameForSecret, []byte("root"))) + g.Expect(secret.Data).Should(HaveKey(constant.AccountPasswdForSecret)) + g.Expect(secret.Data[constant.AccountPasswdForSecret]).Should(HaveLen(int(passwordConfig.Length))) + rootHashedPassword = secret.Annotations[systemAccountHashAnnotation] + g.Expect(rootHashedPassword).Should(BeEmpty()) // kb generated password + })).Should(Succeed()) + + By("check admin account") + var adminHashedPassword string + adminSecretKey := types.NamespacedName{ + Namespace: compObj.Namespace, + Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "admin"), + } + Eventually(testapps.CheckObj(&testCtx, adminSecretKey, func(g Gomega, secret *corev1.Secret) { + g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountNameForSecret, []byte("admin"))) + g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountPasswdForSecret, []byte("sysaccount-update"))) + adminHashedPassword = secret.Annotations[systemAccountHashAnnotation] + g.Expect(adminHashedPassword).ShouldNot(BeEmpty()) // user-provided + })).Should(Succeed()) + + By("mock component as Running") + mockCompRunning(compName) + + By("update the password of admin account") + Expect(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(&secret), func(obj *corev1.Secret) { + if obj.StringData == nil { + obj.StringData = map[string]string{} + } + obj.StringData["sysaccount-update"] = "sysaccount-update-new" + })()).Should(Succeed()) + + By("trigger the component to reconcile") + Expect(testapps.GetAndChangeObj(&testCtx, compKey, func(comp *kbappsv1.Component) { + if comp.Annotations == nil { + comp.Annotations = map[string]string{} + } + comp.Annotations["reconcile"] = time.Now().String() + })()).Should(Succeed()) + + By("check the admin account updated") + var updatedAdminHashedPassword string + Eventually(testapps.CheckObj(&testCtx, adminSecretKey, func(g Gomega, secret *corev1.Secret) { + g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountNameForSecret, []byte("admin"))) + g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountPasswdForSecret, []byte("sysaccount-update-new"))) + updatedAdminHashedPassword = secret.Annotations[systemAccountHashAnnotation] + g.Expect(updatedAdminHashedPassword).ShouldNot(BeEmpty()) // user-provided + g.Expect(updatedAdminHashedPassword).ShouldNot(Equal(adminHashedPassword)) + })).Should(Succeed()) + + By("wait accounts to be updated") + Eventually(testapps.CheckObj(&testCtx, compKey, func(g Gomega, comp *kbappsv1.Component) { + g.Expect(len(comp.Status.Conditions) > 0).Should(BeTrue()) + var cond *metav1.Condition + for i, c := range comp.Status.Conditions { + if c.Type == accountProvisionConditionType { + cond = &comp.Status.Conditions[i] + break + } + } + g.Expect(cond).ShouldNot(BeNil()) + g.Expect(cond.Status).Should(BeEquivalentTo(metav1.ConditionTrue)) + g.Expect(cond.Message).Should(ContainSubstring(fmt.Sprintf("%s:%s", "root", rootHashedPassword))) + g.Expect(cond.Message).Should(ContainSubstring(fmt.Sprintf("%s:%s", "admin", updatedAdminHashedPassword))) + })).Should(Succeed()) + } + testThreeReplicas := func(compName, compDefName string) { const replicas = 3 @@ -1621,14 +1816,6 @@ var _ = Describe("Component Controller", func() { testCompService(defaultCompName, compDefName) }) - It("with component system accounts", func() { - testCompSystemAccount(defaultCompName, compDefName) - }) - - It("with component system accounts - override", func() { - testCompSystemAccountOverride(defaultCompName, compDefName) - }) - It("with component vars", func() { testCompVars(defaultCompName, compDefName) }) @@ -1662,6 +1849,36 @@ var _ = Describe("Component Controller", func() { }) }) + Context("system account", func() { + BeforeEach(func() { + createAllDefinitionObjects() + }) + + AfterEach(func() { + cleanEnv() + }) + + It("provisioning", func() { + testCompSystemAccount(defaultCompName, compDefName) + }) + + It("override", func() { + testCompSystemAccountOverride(defaultCompName, compDefName) + }) + + It("disable", func() { + testCompSystemAccountDisable(defaultCompName, compDefName) + }) + + It("disable - after provision", func() { + testCompSystemAccountDisableAfterProvision(defaultCompName, compDefName) + }) + + It("update", func() { + testCompSystemAccountUpdate(defaultCompName, compDefName) + }) + }) + Context("h-scaling", func() { BeforeEach(func() { createAllDefinitionObjects() diff --git a/controllers/apps/transformer_cluster_component.go b/controllers/apps/transformer_cluster_component.go index 49db7eedb8e..97365facfbf 100644 --- a/controllers/apps/transformer_cluster_component.go +++ b/controllers/apps/transformer_cluster_component.go @@ -196,6 +196,7 @@ func copyAndMergeComponent(oldCompObj, newCompObj *appsv1.Component) *appsv1.Com compObjCopy.Spec.VolumeClaimTemplates = compProto.Spec.VolumeClaimTemplates compObjCopy.Spec.Volumes = compProto.Spec.Volumes compObjCopy.Spec.Services = compProto.Spec.Services + compObjCopy.Spec.SystemAccounts = compProto.Spec.SystemAccounts compObjCopy.Spec.Replicas = compProto.Spec.Replicas compObjCopy.Spec.Configs = compProto.Spec.Configs compObjCopy.Spec.ServiceAccountName = compProto.Spec.ServiceAccountName diff --git a/controllers/apps/transformer_component_account.go b/controllers/apps/transformer_component_account.go index 51b6ff794d3..dcb8616f0b7 100644 --- a/controllers/apps/transformer_component_account.go +++ b/controllers/apps/transformer_component_account.go @@ -107,6 +107,9 @@ func (t *componentAccountTransformer) createAccount(transCtx *componentTransform if err != nil { return err } + if err = t.buildAccountHash(account, nil, secret); err != nil { + return err + } graphCli.Create(dag, secret, inUniversalContext4G()) return nil } @@ -122,6 +125,9 @@ func (t *componentAccountTransformer) updateAccount(transCtx *componentTransform if err != nil { return err } + if err = t.buildAccountHash(account, running, secret); err != nil { + return err + } runningCopy := running.DeepCopy() if account.SecretRef != nil { @@ -136,6 +142,23 @@ func (t *componentAccountTransformer) updateAccount(transCtx *componentTransform return nil } +func (t *componentAccountTransformer) buildAccountHash(account synthesizedSystemAccount, running, secret *corev1.Secret) error { + if account.SecretRef == nil { + return nil + } + if running != nil { + hashedPassword := running.Annotations[systemAccountHashAnnotation] + if verifySystemAccountPassword(secret, []byte(hashedPassword)) { + if secret.Annotations == nil { + secret.Annotations = map[string]string{} + } + secret.Annotations[systemAccountHashAnnotation] = hashedPassword + return nil // have the same password + } + } + return signatureSystemAccountPassword(secret) +} + func (t *componentAccountTransformer) buildAccountSecret(ctx *componentTransformContext, synthesizeComp *component.SynthesizedComponent, account synthesizedSystemAccount) (*corev1.Secret, error) { var password []byte @@ -148,7 +171,7 @@ func (t *componentAccountTransformer) buildAccountSecret(ctx *componentTransform default: password = t.buildPassword(ctx, account) } - return t.buildAccountSecretWithPassword(ctx, synthesizeComp, account, password, account.SecretRef != nil) + return t.buildAccountSecretWithPassword(ctx, synthesizeComp, account, password) } func (t *componentAccountTransformer) getPasswordFromSecret(ctx graph.TransformContext, account synthesizedSystemAccount) ([]byte, error) { @@ -198,7 +221,7 @@ func (t *componentAccountTransformer) generatePassword(account synthesizedSystem } func (t *componentAccountTransformer) buildAccountSecretWithPassword(ctx *componentTransformContext, - synthesizeComp *component.SynthesizedComponent, account synthesizedSystemAccount, password []byte, signature bool) (*corev1.Secret, error) { + synthesizeComp *component.SynthesizedComponent, account synthesizedSystemAccount, password []byte) (*corev1.Secret, error) { secretName := constant.GenerateAccountSecretName(synthesizeComp.ClusterName, synthesizeComp.Name, account.Name) secret := builder.NewSecretBuilder(synthesizeComp.Namespace, secretName). // Priority: static < dynamic < built-in @@ -210,13 +233,8 @@ func (t *componentAccountTransformer) buildAccountSecretWithPassword(ctx *compon AddAnnotationsInMap(synthesizeComp.DynamicAnnotations). PutData(constant.AccountNameForSecret, []byte(account.Name)). PutData(constant.AccountPasswdForSecret, password). - SetImmutable(true). + // SetImmutable(true). GetObject() - if signature { - if err := signatureSystemAccountPassword(secret); err != nil { - return nil, err - } - } if err := setCompOwnershipNFinalizer(ctx.Component, secret); err != nil { return nil, err } diff --git a/pkg/testutil/apps/cluster_factory.go b/pkg/testutil/apps/cluster_factory.go index d9c31b53543..ef5961b25fa 100644 --- a/pkg/testutil/apps/cluster_factory.go +++ b/pkg/testutil/apps/cluster_factory.go @@ -21,6 +21,7 @@ package apps import ( corev1 "k8s.io/api/core/v1" + "k8s.io/utils/ptr" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" ) @@ -206,11 +207,12 @@ func (factory *MockClusterFactory) AddComponentService(serviceName string, servi }) } -func (factory *MockClusterFactory) AddSystemAccount(name string, passwordConfig *appsv1.PasswordConfig, secretRef *appsv1.ProvisionSecretRef) *MockClusterFactory { +func (factory *MockClusterFactory) AddSystemAccount(name string, disabled bool, passwordConfig *appsv1.PasswordConfig, secretRef *appsv1.ProvisionSecretRef) *MockClusterFactory { return factory.lastComponentRef(func(comp *appsv1.ClusterComponentSpec) { comp.SystemAccounts = append(comp.SystemAccounts, appsv1.ComponentSystemAccount{ Name: name, + Disabled: ptr.To(disabled), PasswordConfig: passwordConfig, SecretRef: secretRef, }) diff --git a/pkg/testutil/apps/kb_agent_util.go b/pkg/testutil/apps/kb_agent_util.go index c869bc9cfdf..a813cd20bf6 100644 --- a/pkg/testutil/apps/kb_agent_util.go +++ b/pkg/testutil/apps/kb_agent_util.go @@ -85,57 +85,6 @@ func MockKBAgentClient4HScale(testCtx *testutil.TestContext, clusterKey types.Na }) } -func MockKBAgentClient4Workload(testCtx *testutil.TestContext, pods []*corev1.Pod) { - const ( - memberJoinCompletedLabel = "test.kubeblock.io/memberjoin-completed" - memberLeaveCompletedLabel = "test.kubeblock.io/memberleave-completed" - ) - - rsp := kbagentproto.ActionResponse{Message: "mock success"} - handleMemberLeave := func(podName string) (kbagentproto.ActionResponse, error) { - for _, pod := range pods { - if pod.Name != podName { - continue - } - pod.Labels[memberLeaveCompletedLabel] = "true" - err := testCtx.Cli.Update(testCtx.Ctx, pod) - if err != nil { - return kbagentproto.ActionResponse{}, err - } - } - return rsp, nil - } - - handleMemberJoin := func(podName string) (kbagentproto.ActionResponse, error) { - for _, pod := range pods { - if pod.Name != podName { - continue - } - pod.Labels[memberJoinCompletedLabel] = "true" - err := testCtx.Cli.Update(testCtx.Ctx, pod) - if err != nil { - return kbagentproto.ActionResponse{}, err - } - } - return rsp, nil - } - - MockKBAgentClient(func(recorder *kbacli.MockClientMockRecorder) { - recorder.Action(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, req kbagentproto.ActionRequest) (kbagentproto.ActionResponse, error) { - switch req.Action { - case "memberLeave": - podName := req.Parameters["KB_LEAVE_MEMBER_POD_NAME"] - return handleMemberLeave(podName) - case "memberJoin": - podName := req.Parameters["KB_JOIN_MEMBER_POD_NAME"] - return handleMemberJoin(podName) - default: - return rsp, nil - } - }).AnyTimes() - }) -} - func MockKBAgentContainer() corev1.Container { return corev1.Container{ Name: kbagent.ContainerName, diff --git a/pkg/testutil/dataprotection/backup_utils.go b/pkg/testutil/dataprotection/backup_utils.go index 29f3f7e8ca6..aaecfeec62a 100644 --- a/pkg/testutil/dataprotection/backup_utils.go +++ b/pkg/testutil/dataprotection/backup_utils.go @@ -187,7 +187,7 @@ func NewFakeCluster(testCtx *testutil.TestContext) *BackupClusterInfo { cluster := testapps.NewClusterFactory(testCtx.DefaultNamespace, ClusterName, ""). AddLabels(constant.AppInstanceLabelKey, ClusterName). AddComponent("test-cmp", "test-cmpd"). - AddSystemAccount("test-account", nil, nil). + AddSystemAccount("test-account", false, nil, nil). Create(testCtx).GetObject() podName := ClusterName + "-" + ComponentName From 62fd592b3e0609057bbe2b90bbf2260df57fa5af Mon Sep 17 00:00:00 2001 From: Leon Date: Wed, 8 Jan 2025 15:42:56 +0800 Subject: [PATCH 6/6] add update statement --- pkg/testutil/apps/constant.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/testutil/apps/constant.go b/pkg/testutil/apps/constant.go index 46ee1fd9e39..825030be311 100644 --- a/pkg/testutil/apps/constant.go +++ b/pkg/testutil/apps/constant.go @@ -200,6 +200,7 @@ var ( Statement: &appsv1.SystemAccountStatement{ Create: "CREATE USER $(USERNAME) IDENTIFIED BY '$(PASSWORD)'; GRANT ALL PRIVILEGES ON *.* TO $(USERNAME);", Delete: "DROP USER '$(USERNAME)'@'%';", + Update: "ALTER USER '$(USERNAME)'@'%' IDENTIFIED BY '$(PASSWORD)';", }, PasswordGenerationPolicy: appsv1.PasswordConfig{ Length: 10,
FieldDescription
-secretRef
+create
- -ProvisionSecretRef - +string
(Optional) -

Refers to the secret from which data will be copied to create the new account.

+

The statement to create a new account with the necessary privileges.

+

This field is immutable once set.

+
+delete
+ +string + +
+(Optional) +

The statement to delete a account.

+

This field is immutable once set.

+
+update
+ +string + +
+(Optional) +

The statement to update an existing account.

This field is immutable once set.