From a1ac771651a4de5bcd46d078d2e6218e6ba0a6b8 Mon Sep 17 00:00:00 2001 From: ravibagri4 Date: Wed, 21 Feb 2024 15:50:04 +0100 Subject: [PATCH] Added autoGeneratePassword flag for rds cluster --- apis/rds/v1beta1/zz_cluster_types.go | 7 + apis/rds/v1beta1/zz_generated.deepcopy.go | 5 + config/rds/config.go | 26 +++ config/rds/config_test.go | 182 ++++++++++++++++++ examples/rds/cluster.yaml | 10 +- package/crds/rds.aws.upbound.io_clusters.yaml | 15 +- 6 files changed, 233 insertions(+), 12 deletions(-) diff --git a/apis/rds/v1beta1/zz_cluster_types.go b/apis/rds/v1beta1/zz_cluster_types.go index bae79e955b..ba75b037fe 100755 --- a/apis/rds/v1beta1/zz_cluster_types.go +++ b/apis/rds/v1beta1/zz_cluster_types.go @@ -330,6 +330,13 @@ type ClusterParameters struct { // +kubebuilder:validation:Optional AllocatedStorage *float64 `json:"allocatedStorage,omitempty" tf:"allocated_storage,omitempty"` + // Password for the master DB user. Note that this may show up in + // logs, and it will be stored in the state file. Cannot be set if manage_master_user_password is set to true. + // If true, the password will be auto-generated and stored in the Secret referenced by the passwordSecretRef field. + // +upjet:crd:field:TFTag=- + // +kubebuilder:validation:Optional + AutoGeneratePassword *bool `json:"autoGeneratePassword,omitempty" tf:"-"` + // Enable to allow major engine version upgrades when changing engine versions. Defaults to false. // +kubebuilder:validation:Optional AllowMajorVersionUpgrade *bool `json:"allowMajorVersionUpgrade,omitempty" tf:"allow_major_version_upgrade,omitempty"` diff --git a/apis/rds/v1beta1/zz_generated.deepcopy.go b/apis/rds/v1beta1/zz_generated.deepcopy.go index 65bcc4cd95..579fcab2be 100644 --- a/apis/rds/v1beta1/zz_generated.deepcopy.go +++ b/apis/rds/v1beta1/zz_generated.deepcopy.go @@ -2424,6 +2424,11 @@ func (in *ClusterParameters) DeepCopyInto(out *ClusterParameters) { *out = new(float64) **out = **in } + if in.AutoGeneratePassword != nil { + in, out := &in.AutoGeneratePassword, &out.AutoGeneratePassword + *out = new(bool) + **out = **in + } if in.AllowMajorVersionUpgrade != nil { in, out := &in.AllowMajorVersionUpgrade, &out.AllowMajorVersionUpgrade *out = new(bool) diff --git a/config/rds/config.go b/config/rds/config.go index 5fa48917c2..0c102e92d6 100644 --- a/config/rds/config.go +++ b/config/rds/config.go @@ -59,8 +59,34 @@ func Configure(p *config.Provider) { if a, ok := attr["port"]; ok { conn["port"] = []byte(fmt.Sprintf("%v", a)) } + if a, ok := attr["password"].(string); ok { + conn["password"] = []byte(a) + } return conn, nil } + desc, _ := comments.New("If true, the password will be auto-generated and"+ + " stored in the Secret referenced by the masterPasswordSecretRef field.", + comments.WithTFTag("-")) + r.TerraformResource.Schema["auto_generate_password"] = &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Description: desc.String(), + } + r.InitializerFns = append(r.InitializerFns, + PasswordGenerator( + "spec.forProvider.masterPasswordSecretRef", + "spec.forProvider.autoGeneratePassword", + )) + r.TerraformResource.Schema["password"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Sensitive: true, + Description: "Password for the master DB user. If you set autoGeneratePassword to true, the Secret referenced here will be created or updated with generated password if it does not already contain one.", + } + r.TerraformResource.Schema["password"].Description = "Password for the " + + "master DB user. If you set autoGeneratePassword to true, the Secret" + + " referenced here will be created or updated with generated password" + + " if it does not already contain one." }) p.AddResourceConfigurator("aws_rds_cluster_instance", func(r *config.Resource) { diff --git a/config/rds/config_test.go b/config/rds/config_test.go index 5b525bda32..43e397eb06 100644 --- a/config/rds/config_test.go +++ b/config/rds/config_test.go @@ -56,6 +56,20 @@ func TestPasswordGenerator(t *testing.T) { err: errors.Wrap(errBoom, errGetPasswordSecret), }, }, + "CannotGetClusterSecret": { + reason: "An error should be returned if the referenced secret cannot be retrieved.", + args: args{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(errBoom), + }, + secretRefFieldPath: "", + toggleFieldPath: "", + mg: &fake.Managed{}, + }, + want: want{ + err: errors.Wrap(errBoom, errGetPasswordSecret), + }, + }, "SecretAlreadyFull": { reason: "Should be no-op if the Secret already has password.", args: args{ @@ -85,6 +99,35 @@ func TestPasswordGenerator(t *testing.T) { }, }, }, + "ClusterSecretAlreadyFull": { + reason: "Should be no-op if the Secret already has password.", + args: args{ + kube: &test.MockClient{ + MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error { + s, ok := obj.(*corev1.Secret) + if !ok { + return errors.New("needs to be secret") + } + s.Data = map[string][]byte{ + "password": []byte("foo"), + } + return nil + }, + }, + secretRefFieldPath: "parameterizable.parameters.masterPasswordSecretRef", + mg: &ujfake.Terraformed{ + Parameterizable: ujfake.Parameterizable{ + Parameters: map[string]any{ + "masterPasswordSecretRef": map[string]any{ + "name": "foo", + "namespace": "bar", + "key": "password", + }, + }, + }, + }, + }, + }, "NoSecretReference": { reason: "Should be no-op if the secret reference is not given.", args: args{ @@ -98,6 +141,19 @@ func TestPasswordGenerator(t *testing.T) { }, }, }, + "NoClusterSecretReference": { + reason: "Should be no-op if the secret reference is not given.", + args: args{ + secretRefFieldPath: "parameterizable.parameters.masterPasswordSecretRef", + mg: &ujfake.Terraformed{ + Parameterizable: ujfake.Parameterizable{ + Parameters: map[string]any{ + "another": "field", + }, + }, + }, + }, + }, "ToggleNotSet": { reason: "Should be no-op if the toggle is not set at all.", args: args{ @@ -119,6 +175,27 @@ func TestPasswordGenerator(t *testing.T) { }, }, }, + "ClusterToggleNotSet": { + reason: "Should be no-op if the toggle is not set at all.", + args: args{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil), + }, + secretRefFieldPath: "parameterizable.parameters.masterPasswordSecretRef", + toggleFieldPath: "parameterizable.parameters.autoGeneratePassword", + mg: &ujfake.Terraformed{ + Parameterizable: ujfake.Parameterizable{ + Parameters: map[string]any{ + "masterPasswordSecretRef": map[string]any{ + "name": "foo", + "namespace": "bar", + "key": "password", + }, + }, + }, + }, + }, + }, "ToggleFalse": { reason: "Should be no-op if the toggle is set to false.", args: args{ @@ -141,6 +218,28 @@ func TestPasswordGenerator(t *testing.T) { }, }, }, + "ClusterToggleFalse": { + reason: "Should be no-op if the toggle is set to false.", + args: args{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil), + }, + secretRefFieldPath: "parameterizable.parameters.masterPasswordSecretRef", + toggleFieldPath: "parameterizable.parameters.autoGeneratePassword", + mg: &ujfake.Terraformed{ + Parameterizable: ujfake.Parameterizable{ + Parameters: map[string]any{ + "masterPasswordSecretRef": map[string]any{ + "name": "foo", + "namespace": "bar", + "key": "password", + }, + "autoGeneratePassword": false, + }, + }, + }, + }, + }, "GenerateAndApply": { reason: "Should apply if we generate, set the content of an already existing secret.", args: args{ @@ -183,6 +282,48 @@ func TestPasswordGenerator(t *testing.T) { }, }, }, + "ClusterSecretGenerateAndApply": { + reason: "Should apply if we generate, set the content of an already existing secret.", + args: args{ + kube: &test.MockClient{ + MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error { + s, ok := obj.(*corev1.Secret) + if !ok { + return errors.New("needs to be secret") + } + s.CreationTimestamp = metav1.Time{Time: time.Now()} + return nil + }, + MockPatch: func(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { + s, ok := obj.(*corev1.Secret) + if !ok { + return errors.New("needs to be secret") + } + if len(s.Data["password"]) == 0 { + return errors.New("password is not set") + } + if len(s.OwnerReferences) != 0 { + return errors.New("owner references should not be set if secret already exists") + } + return nil + }, + }, + secretRefFieldPath: "parameterizable.parameters.masterPasswordSecretRef", + toggleFieldPath: "parameterizable.parameters.autoGeneratePassword", + mg: &ujfake.Terraformed{ + Parameterizable: ujfake.Parameterizable{ + Parameters: map[string]any{ + "masterPasswordSecretRef": map[string]any{ + "name": "foo", + "namespace": "bar", + "key": "password", + }, + "autoGeneratePassword": true, + }, + }, + }, + }, + }, "GenerateAndCreate": { reason: "Should create if we generate, set the content and there is no secret in place.", args: args{ @@ -224,6 +365,47 @@ func TestPasswordGenerator(t *testing.T) { }, }, }, + "ClusterSecretGenerateAndCreate": { + reason: "Should create if we generate, set the content and there is no secret in place.", + args: args{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, "")), + MockCreate: func(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { + s, ok := obj.(*corev1.Secret) + if !ok { + return errors.New("needs to be secret") + } + if len(s.Data["password"]) == 0 { + return errors.New("password is not set") + } + if len(s.OwnerReferences) == 1 && + s.OwnerReferences[0].Name == "foo-mgd" { + return nil + } + return errors.New("owner references should be set if secret is created") + }, + }, + secretRefFieldPath: "parameterizable.parameters.masterPasswordSecretRef", + toggleFieldPath: "parameterizable.parameters.autoGeneratePassword", + mg: &ujfake.Terraformed{ + Managed: fake.Managed{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-mgd", + }, + }, + Parameterizable: ujfake.Parameterizable{ + Parameters: map[string]any{ + "masterPasswordSecretRef": map[string]any{ + "name": "foo", + "namespace": "bar", + "key": "password", + }, + "autoGeneratePassword": true, + }, + }, + }, + }, + }, } for name, tc := range cases { t.Run(name, func(t *testing.T) { diff --git a/examples/rds/cluster.yaml b/examples/rds/cluster.yaml index 5a1442be9b..d9c896403e 100644 --- a/examples/rds/cluster.yaml +++ b/examples/rds/cluster.yaml @@ -9,6 +9,7 @@ spec: region: us-west-1 engine: aurora-postgresql masterUsername: cpadmin + autoGeneratePassword: true masterPasswordSecretRef: name: sample-cluster-password namespace: upbound-system @@ -17,12 +18,3 @@ spec: writeConnectionSecretToRef: name: sample-rds-cluster-secret namespace: upbound-system ---- -apiVersion: v1 -kind: Secret -metadata: - name: sample-cluster-password - namespace: upbound-system -type: Opaque -stringData: - password: TestPass0! diff --git a/package/crds/rds.aws.upbound.io_clusters.yaml b/package/crds/rds.aws.upbound.io_clusters.yaml index cfd0b891b2..21f857f6e5 100644 --- a/package/crds/rds.aws.upbound.io_clusters.yaml +++ b/package/crds/rds.aws.upbound.io_clusters.yaml @@ -80,6 +80,13 @@ spec: immediately, or during the next maintenance window. Default is false. See Amazon RDS Documentation for more information. type: boolean + autoGeneratePassword: + description: Password for the master DB user. Note that this may + show up in logs, and it will be stored in the state file. Cannot + be set if manage_master_user_password is set to true. If true, + the password will be auto-generated and stored in the Secret + referenced by the masterPasswordSecretRef field. + type: boolean availabilityZones: description: List of EC2 Availability Zones for the DB cluster storage where DB cluster instances can be created. We recommend @@ -355,9 +362,11 @@ spec: type: boolean masterPasswordSecretRef: description: Password for the master DB user. Note that this may - show up in logs, and it will be stored in the state file. Please - refer to the RDS Naming Constraints. Cannot be set if manage_master_user_password - is set to true. + show up in logs, and it will be stored in the state file. Cannot + be set if manage_master_user_password is set to true. Password + for the master DB user. If you set autoGeneratePassword to true, + the Secret referenced here will be created or updated with generated + password if it does not already contain one. properties: key: description: The key to select.