Skip to content

Commit 0408f66

Browse files
authored
Merge pull request #42563 from badmintoncryer/manage-master-user-password
r/aws_docdb_cluster: add support for `manage_master_user_password` argument
2 parents 502fb6e + 3d8602b commit 0408f66

File tree

4 files changed

+180
-44
lines changed

4 files changed

+180
-44
lines changed

.changelog/42563.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
resource/aws_docdb_cluster: Add `manage_master_user_password` argument and `master_user_secret` attribute
3+
```

internal/service/docdb/cluster.go

Lines changed: 94 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -186,24 +186,49 @@ func resourceCluster() *schema.Resource {
186186
ForceNew: true,
187187
ValidateFunc: verify.ValidARN,
188188
},
189+
"manage_master_user_password": {
190+
Type: schema.TypeBool,
191+
Optional: true,
192+
ConflictsWith: []string{"master_password", "master_password_wo"},
193+
},
189194
"master_password": {
190195
Type: schema.TypeString,
191196
Optional: true,
192197
Sensitive: true,
193-
ConflictsWith: []string{"master_password_wo"},
198+
ConflictsWith: []string{"manage_master_user_password", "master_password_wo"},
194199
},
195200
"master_password_wo": {
196201
Type: schema.TypeString,
197202
Optional: true,
198203
WriteOnly: true,
199-
ConflictsWith: []string{"master_password"},
204+
ConflictsWith: []string{"manage_master_user_password", "master_password"},
200205
RequiredWith: []string{"master_password_wo_version"},
201206
},
202207
"master_password_wo_version": {
203208
Type: schema.TypeInt,
204209
Optional: true,
205210
RequiredWith: []string{"master_password_wo"},
206211
},
212+
"master_user_secret": {
213+
Type: schema.TypeList,
214+
Computed: true,
215+
Elem: &schema.Resource{
216+
Schema: map[string]*schema.Schema{
217+
names.AttrKMSKeyID: {
218+
Type: schema.TypeString,
219+
Computed: true,
220+
},
221+
"secret_arn": {
222+
Type: schema.TypeString,
223+
Computed: true,
224+
},
225+
"secret_status": {
226+
Type: schema.TypeString,
227+
Computed: true,
228+
},
229+
},
230+
},
231+
},
207232
"master_username": {
208233
Type: schema.TypeString,
209234
Optional: true,
@@ -340,7 +365,7 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta any
340365
// ModifyDBInstance afterwadocdb to prevent Terraform operators from API
341366
// errors or needing to double apply.
342367
var requiresModifyDbCluster bool
343-
inputM := &docdb.ModifyDBClusterInput{
368+
inputMDBC := docdb.ModifyDBClusterInput{
344369
ApplyImmediately: aws.Bool(true),
345370
}
346371

@@ -352,7 +377,7 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta any
352377
}
353378

354379
if _, ok := d.GetOk("snapshot_identifier"); ok {
355-
input := &docdb.RestoreDBClusterFromSnapshotInput{
380+
input := docdb.RestoreDBClusterFromSnapshotInput{
356381
DBClusterIdentifier: aws.String(identifier),
357382
DeletionProtection: aws.Bool(d.Get(names.AttrDeletionProtection).(bool)),
358383
Engine: aws.String(d.Get(names.AttrEngine).(string)),
@@ -365,12 +390,12 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta any
365390
}
366391

367392
if v, ok := d.GetOk("backup_retention_period"); ok {
368-
inputM.BackupRetentionPeriod = aws.Int32(int32(v.(int)))
393+
inputMDBC.BackupRetentionPeriod = aws.Int32(int32(v.(int)))
369394
requiresModifyDbCluster = true
370395
}
371396

372397
if v, ok := d.GetOk("db_cluster_parameter_group_name"); ok {
373-
inputM.DBClusterParameterGroupName = aws.String(v.(string))
398+
inputMDBC.DBClusterParameterGroupName = aws.String(v.(string))
374399
requiresModifyDbCluster = true
375400
}
376401

@@ -390,13 +415,18 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta any
390415
input.KmsKeyId = aws.String(v.(string))
391416
}
392417

418+
if v, ok := d.GetOk("manage_master_user_password"); ok {
419+
inputMDBC.ManageMasterUserPassword = aws.Bool(v.(bool))
420+
requiresModifyDbCluster = true
421+
}
422+
393423
if v, ok := d.GetOk("master_password"); ok {
394-
inputM.MasterUserPassword = aws.String(v.(string))
424+
inputMDBC.MasterUserPassword = aws.String(v.(string))
395425
requiresModifyDbCluster = true
396426
}
397427

398428
if masterPasswordWO != "" {
399-
inputM.MasterUserPassword = aws.String(masterPasswordWO)
429+
inputMDBC.MasterUserPassword = aws.String(masterPasswordWO)
400430
requiresModifyDbCluster = true
401431
}
402432

@@ -405,12 +435,12 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta any
405435
}
406436

407437
if v, ok := d.GetOk("preferred_backup_window"); ok {
408-
inputM.PreferredBackupWindow = aws.String(v.(string))
438+
inputMDBC.PreferredBackupWindow = aws.String(v.(string))
409439
requiresModifyDbCluster = true
410440
}
411441

412442
if v, ok := d.GetOk(names.AttrPreferredMaintenanceWindow); ok {
413-
inputM.PreferredMaintenanceWindow = aws.String(v.(string))
443+
inputMDBC.PreferredMaintenanceWindow = aws.String(v.(string))
414444
requiresModifyDbCluster = true
415445
}
416446

@@ -423,15 +453,15 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta any
423453
}
424454

425455
_, err := tfresource.RetryWhenAWSErrMessageContains(ctx, propagationTimeout, func() (any, error) {
426-
return conn.RestoreDBClusterFromSnapshot(ctx, input)
456+
return conn.RestoreDBClusterFromSnapshot(ctx, &input)
427457
}, errCodeInvalidParameterValue, "IAM role ARN value is invalid or does not include the required permissions")
428458

429459
if err != nil {
430460
return sdkdiag.AppendErrorf(diags, "creating DocumentDB Cluster (restore from snapshot) (%s): %s", identifier, err)
431461
}
432462
} else if v, ok := d.GetOk("restore_to_point_in_time"); ok && len(v.([]any)) > 0 && v.([]any)[0] != nil {
433463
tfMap := v.([]any)[0].(map[string]any)
434-
input := &docdb.RestoreDBClusterToPointInTimeInput{
464+
input := docdb.RestoreDBClusterToPointInTimeInput{
435465
DBClusterIdentifier: aws.String(identifier),
436466
SourceDBClusterIdentifier: aws.String(tfMap["source_cluster_identifier"].(string)),
437467
DeletionProtection: aws.Bool(d.Get(names.AttrDeletionProtection).(bool)),
@@ -480,16 +510,19 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta any
480510
}
481511

482512
_, err := tfresource.RetryWhenAWSErrMessageContains(ctx, propagationTimeout, func() (any, error) {
483-
return conn.RestoreDBClusterToPointInTime(ctx, input)
513+
return conn.RestoreDBClusterToPointInTime(ctx, &input)
484514
}, errCodeInvalidParameterValue, "IAM role ARN value is invalid or does not include the required permissions")
485515
if err != nil {
486516
return sdkdiag.AppendErrorf(diags, "creating DocumentDB Cluster (restore to point-in-time) (%s): %s", identifier, err)
487517
}
488518
} else {
489519
// Secondary DocDB clusters part of a global cluster will not supply the master_password
490520
if _, ok := d.GetOk("global_cluster_identifier"); !ok {
491-
if _, ok := d.GetOk("master_password"); !ok && masterPasswordWO == "" {
492-
return sdkdiag.AppendErrorf(diags, `provider.aws: aws_docdb_cluster: %s: "master_password", "master_password_wo": required field is not set`, identifier)
521+
// If manage_master_user_password is true, we don't need master_password
522+
if v, ok := d.GetOk("manage_master_user_password"); !ok || !v.(bool) {
523+
if _, ok := d.GetOk("master_password"); !ok && masterPasswordWO == "" {
524+
return sdkdiag.AppendErrorf(diags, `provider.aws: aws_docdb_cluster: %s: "master_password", "master_password_wo": required field is not set`, identifier)
525+
}
493526
}
494527
}
495528

@@ -500,7 +533,7 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta any
500533
}
501534
}
502535

503-
input := &docdb.CreateDBClusterInput{
536+
input := docdb.CreateDBClusterInput{
504537
DBClusterIdentifier: aws.String(identifier),
505538
DeletionProtection: aws.Bool(d.Get(names.AttrDeletionProtection).(bool)),
506539
Engine: aws.String(d.Get(names.AttrEngine).(string)),
@@ -540,12 +573,16 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta any
540573
input.KmsKeyId = aws.String(v.(string))
541574
}
542575

543-
if v, ok := d.GetOk("master_password"); ok {
544-
input.MasterUserPassword = aws.String(v.(string))
545-
}
576+
if v, ok := d.GetOk("manage_master_user_password"); ok {
577+
input.ManageMasterUserPassword = aws.Bool(v.(bool))
578+
} else {
579+
if v, ok := d.GetOk("master_password"); ok {
580+
input.MasterUserPassword = aws.String(v.(string))
581+
}
546582

547-
if masterPasswordWO != "" {
548-
input.MasterUserPassword = aws.String(masterPasswordWO)
583+
if masterPasswordWO != "" {
584+
input.MasterUserPassword = aws.String(masterPasswordWO)
585+
}
549586
}
550587

551588
if v, ok := d.GetOk(names.AttrPort); ok {
@@ -573,7 +610,7 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta any
573610
}
574611

575612
_, err := tfresource.RetryWhenAWSErrMessageContains(ctx, propagationTimeout, func() (any, error) {
576-
return conn.CreateDBCluster(ctx, input)
613+
return conn.CreateDBCluster(ctx, &input)
577614
}, errCodeInvalidParameterValue, "IAM role ARN value is invalid or does not include the required permissions")
578615

579616
if err != nil {
@@ -588,9 +625,9 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta any
588625
}
589626

590627
if requiresModifyDbCluster {
591-
inputM.DBClusterIdentifier = aws.String(d.Id())
628+
inputMDBC.DBClusterIdentifier = aws.String(d.Id())
592629

593-
_, err := conn.ModifyDBCluster(ctx, inputM)
630+
_, err := conn.ModifyDBCluster(ctx, &inputMDBC)
594631

595632
if err != nil {
596633
return sdkdiag.AppendErrorf(diags, "modifying DocumentDB Cluster (%s): %s", d.Id(), err)
@@ -650,18 +687,28 @@ func resourceClusterRead(ctx context.Context, d *schema.ResourceData, meta any)
650687
d.Set(names.AttrEngine, dbc.Engine)
651688
d.Set(names.AttrHostedZoneID, dbc.HostedZoneId)
652689
d.Set(names.AttrKMSKeyID, dbc.KmsKeyId)
690+
if v := dbc.MasterUserSecret; v != nil {
691+
tfList := []any{map[string]any{
692+
names.AttrKMSKeyID: aws.ToString(v.KmsKeyId),
693+
"secret_arn": aws.ToString(v.SecretArn),
694+
"secret_status": aws.ToString(v.SecretStatus),
695+
}}
696+
if err := d.Set("master_user_secret", tfList); err != nil {
697+
return sdkdiag.AppendErrorf(diags, "setting master_user_secret: %s", err)
698+
}
699+
} else {
700+
d.Set("master_user_secret", nil)
701+
}
653702
d.Set("master_username", dbc.MasterUsername)
654703
d.Set(names.AttrPort, dbc.Port)
655704
d.Set("preferred_backup_window", dbc.PreferredBackupWindow)
656705
d.Set(names.AttrPreferredMaintenanceWindow, dbc.PreferredMaintenanceWindow)
657706
d.Set("reader_endpoint", dbc.ReaderEndpoint)
658707
d.Set(names.AttrStorageEncrypted, dbc.StorageEncrypted)
659708
d.Set(names.AttrStorageType, dbc.StorageType)
660-
var securityGroupIDs []string
661-
for _, v := range dbc.VpcSecurityGroups {
662-
securityGroupIDs = append(securityGroupIDs, aws.ToString(v.VpcSecurityGroupId))
663-
}
664-
d.Set(names.AttrVPCSecurityGroupIDs, securityGroupIDs)
709+
d.Set(names.AttrVPCSecurityGroupIDs, tfslices.ApplyToAll(dbc.VpcSecurityGroups, func(v awstypes.VpcSecurityGroupMembership) string {
710+
return aws.ToString(v.VpcSecurityGroupId)
711+
}))
665712

666713
return diags
667714
}
@@ -671,7 +718,7 @@ func resourceClusterUpdate(ctx context.Context, d *schema.ResourceData, meta any
671718
conn := meta.(*conns.AWSClient).DocDBClient(ctx)
672719

673720
if d.HasChangesExcept(names.AttrTags, names.AttrTagsAll, "global_cluster_identifier", "skip_final_snapshot") {
674-
input := &docdb.ModifyDBClusterInput{
721+
input := docdb.ModifyDBClusterInput{
675722
ApplyImmediately: aws.Bool(d.Get(names.AttrApplyImmediately).(bool)),
676723
DBClusterIdentifier: aws.String(d.Id()),
677724
}
@@ -700,6 +747,10 @@ func resourceClusterUpdate(ctx context.Context, d *schema.ResourceData, meta any
700747
input.EngineVersion = aws.String(d.Get(names.AttrEngineVersion).(string))
701748
}
702749

750+
if d.HasChange("manage_master_user_password") {
751+
input.ManageMasterUserPassword = aws.Bool(d.Get("manage_master_user_password").(bool))
752+
}
753+
703754
if d.HasChange("master_password") {
704755
input.MasterUserPassword = aws.String(d.Get("master_password").(string))
705756
}
@@ -736,9 +787,12 @@ func resourceClusterUpdate(ctx context.Context, d *schema.ResourceData, meta any
736787
}
737788
}
738789

739-
_, err := tfresource.RetryWhen(ctx, 5*time.Minute,
790+
const (
791+
timeout = 5 * time.Minute
792+
)
793+
_, err := tfresource.RetryWhen(ctx, timeout,
740794
func() (any, error) {
741-
return conn.ModifyDBCluster(ctx, input)
795+
return conn.ModifyDBCluster(ctx, &input)
742796
},
743797
func(err error) (bool, error) {
744798
if tfawserr.ErrMessageContains(err, errCodeInvalidParameterValue, "IAM role ARN value is invalid or does not include the required permissions") {
@@ -791,7 +845,7 @@ func resourceClusterDelete(ctx context.Context, d *schema.ResourceData, meta any
791845
conn := meta.(*conns.AWSClient).DocDBClient(ctx)
792846

793847
skipFinalSnapshot := d.Get("skip_final_snapshot").(bool)
794-
input := &docdb.DeleteDBClusterInput{
848+
input := docdb.DeleteDBClusterInput{
795849
DBClusterIdentifier: aws.String(d.Id()),
796850
SkipFinalSnapshot: aws.Bool(skipFinalSnapshot),
797851
}
@@ -813,7 +867,7 @@ func resourceClusterDelete(ctx context.Context, d *schema.ResourceData, meta any
813867
log.Printf("[DEBUG] Deleting DocumentDB Cluster: %s", d.Id())
814868
_, err := tfresource.RetryWhen(ctx, d.Timeout(schema.TimeoutDelete),
815869
func() (any, error) {
816-
return conn.DeleteDBCluster(ctx, input)
870+
return conn.DeleteDBCluster(ctx, &input)
817871
},
818872
func(err error) (bool, error) {
819873
if errs.IsAErrorMessageContains[*awstypes.InvalidDBClusterStateFault](err, "is not currently in the available state") {
@@ -876,12 +930,12 @@ func diffCloudWatchLogsExportConfiguration(old, new []any) ([]any, []any) {
876930
}
877931

878932
func removeClusterFromGlobalCluster(ctx context.Context, conn *docdb.Client, clusterARN, globalClusterID string, timeout time.Duration) error {
879-
input := &docdb.RemoveFromGlobalClusterInput{
933+
input := docdb.RemoveFromGlobalClusterInput{
880934
DbClusterIdentifier: aws.String(clusterARN),
881935
GlobalClusterIdentifier: aws.String(globalClusterID),
882936
}
883937

884-
_, err := conn.RemoveFromGlobalCluster(ctx, input)
938+
_, err := conn.RemoveFromGlobalCluster(ctx, &input)
885939

886940
if errs.IsA[*awstypes.DBClusterNotFoundFault](err) || errs.IsA[*awstypes.GlobalClusterNotFoundFault](err) ||
887941
tfawserr.ErrMessageContains(err, errCodeInvalidParameterValue, "is not found in global cluster") {
@@ -904,10 +958,10 @@ func removeClusterFromGlobalCluster(ctx context.Context, conn *docdb.Client, clu
904958
}
905959

906960
func findDBClusterByID(ctx context.Context, conn *docdb.Client, id string) (*awstypes.DBCluster, error) {
907-
input := &docdb.DescribeDBClustersInput{
961+
input := docdb.DescribeDBClustersInput{
908962
DBClusterIdentifier: aws.String(id),
909963
}
910-
output, err := findDBCluster(ctx, conn, input, tfslices.PredicateTrue[*awstypes.DBCluster]())
964+
output, err := findDBCluster(ctx, conn, &input, tfslices.PredicateTrue[*awstypes.DBCluster]())
911965

912966
if err != nil {
913967
return nil, err
@@ -924,9 +978,9 @@ func findDBClusterByID(ctx context.Context, conn *docdb.Client, id string) (*aws
924978
}
925979

926980
func findClusterByARN(ctx context.Context, conn *docdb.Client, arn string) (*awstypes.DBCluster, error) {
927-
input := &docdb.DescribeDBClustersInput{}
981+
input := docdb.DescribeDBClustersInput{}
928982

929-
return findDBCluster(ctx, conn, input, func(v *awstypes.DBCluster) bool {
983+
return findDBCluster(ctx, conn, &input, func(v *awstypes.DBCluster) bool {
930984
return aws.ToString(v.DBClusterArn) == arn
931985
})
932986
}

0 commit comments

Comments
 (0)