Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

r/docdb_cluster: add master_password_wo write-only attribute #41413

Merged
merged 6 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/41413.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_docdb_cluster: Add `master_password_wo` write-only attribute
```
60 changes: 54 additions & 6 deletions internal/service/docdb/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/aws/aws-sdk-go-v2/service/docdb"
awstypes "github.com/aws/aws-sdk-go-v2/service/docdb/types"
"github.com/hashicorp/aws-sdk-go-base/v2/tfawserr"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
Expand Down Expand Up @@ -57,6 +58,10 @@ func resourceCluster() *schema.Resource {
Delete: schema.DefaultTimeout(120 * time.Minute),
},

ValidateRawResourceConfigFuncs: []schema.ValidateRawResourceConfigFunc{
validation.PreferWriteOnlyAttribute(cty.GetAttrPath("master_password"), cty.GetAttrPath("master_password_wo")),
},

Schema: map[string]*schema.Schema{
names.AttrAllowMajorVersionUpgrade: {
Type: schema.TypeBool,
Expand Down Expand Up @@ -186,9 +191,21 @@ func resourceCluster() *schema.Resource {
ValidateFunc: verify.ValidARN,
},
"master_password": {
Type: schema.TypeString,
Optional: true,
Sensitive: true,
Type: schema.TypeString,
Optional: true,
Sensitive: true,
ConflictsWith: []string{"master_password_wo"},
},
"master_password_wo": {
Type: schema.TypeString,
Optional: true,
WriteOnly: true,
ConflictsWith: []string{"master_password"},
},
"master_password_wo_version": {
Type: schema.TypeInt,
Optional: true,
RequiredWith: []string{"master_password_wo"},
},
"master_username": {
Type: schema.TypeString,
Expand Down Expand Up @@ -332,6 +349,13 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta int
ApplyImmediately: aws.Bool(true),
}

// get write-only value from configuration
masterPasswordWO, di := flex.GetWriteOnlyStringValue(d, cty.GetAttrPath("master_password_wo"))
diags = append(diags, di...)
if diags.HasError() {
return diags
}

if _, ok := d.GetOk("snapshot_identifier"); ok {
input := &docdb.RestoreDBClusterFromSnapshotInput{
DBClusterIdentifier: aws.String(identifier),
Expand Down Expand Up @@ -376,6 +400,11 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta int
requiresModifyDbCluster = true
}

if masterPasswordWO != "" {
inputM.MasterUserPassword = aws.String(masterPasswordWO)
requiresModifyDbCluster = true
}

if v, ok := d.GetOk(names.AttrPort); ok {
input.Port = aws.Int32(int32(v.(int)))
}
Expand Down Expand Up @@ -464,8 +493,8 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta int
} else {
// Secondary DocDB clusters part of a global cluster will not supply the master_password
if _, ok := d.GetOk("global_cluster_identifier"); !ok {
if _, ok := d.GetOk("master_password"); !ok {
return sdkdiag.AppendErrorf(diags, `provider.aws: aws_docdb_cluster: %s: "master_password": required field is not set`, identifier)
if _, ok := d.GetOk("master_password"); !ok && masterPasswordWO == "" {
return sdkdiag.AppendErrorf(diags, `provider.aws: aws_docdb_cluster: %s: "master_password", "master_password_wo": required field is not set`, identifier)
}
}

Expand All @@ -481,7 +510,6 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta int
DeletionProtection: aws.Bool(d.Get(names.AttrDeletionProtection).(bool)),
Engine: aws.String(d.Get(names.AttrEngine).(string)),
MasterUsername: aws.String(d.Get("master_username").(string)),
MasterUserPassword: aws.String(d.Get("master_password").(string)),
Tags: getTagsIn(ctx),
}

Expand Down Expand Up @@ -517,6 +545,14 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta int
input.KmsKeyId = aws.String(v.(string))
}

if v, ok := d.GetOk("master_password"); ok {
input.MasterUserPassword = aws.String(v.(string))
}

if masterPasswordWO != "" {
input.MasterUserPassword = aws.String(masterPasswordWO)
}

if v, ok := d.GetOk(names.AttrPort); ok {
input.Port = aws.Int32(int32(v.(int)))
}
Expand Down Expand Up @@ -673,6 +709,18 @@ func resourceClusterUpdate(ctx context.Context, d *schema.ResourceData, meta int
input.MasterUserPassword = aws.String(d.Get("master_password").(string))
}

if d.HasChange("master_password_wo_version") {
masterPasswordWO, di := flex.GetWriteOnlyStringValue(d, cty.GetAttrPath("master_password_wo"))
diags = append(diags, di...)
if diags.HasError() {
return diags
}

if masterPasswordWO != "" {
input.MasterUserPassword = aws.String(masterPasswordWO)
}
}

if d.HasChange("preferred_backup_window") {
input.PreferredBackupWindow = aws.String(d.Get("preferred_backup_window").(string))
}
Expand Down
57 changes: 57 additions & 0 deletions internal/service/docdb/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ import (
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/docdb"
awstypes "github.com/aws/aws-sdk-go-v2/service/docdb/types"
"github.com/hashicorp/go-version"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
"github.com/hashicorp/terraform-plugin-testing/tfversion"
"github.com/hashicorp/terraform-provider-aws/internal/acctest"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
tfdocdb "github.com/hashicorp/terraform-provider-aws/internal/service/docdb"
Expand Down Expand Up @@ -925,6 +927,37 @@ func TestAccDocDBCluster_storageType(t *testing.T) {
})
}

func TestAccDocDBCluster_passwordWriteOnly(t *testing.T) {
ctx := acctest.Context(t)
var dbCluster awstypes.DBCluster
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_docdb_cluster.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, names.DocDBServiceID),
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.SkipBelow(version.Must(version.NewVersion("1.11.0"))),
},
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckClusterDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccClusterConfig_passwordWriteOnly(rName, "avoid-plaintext-passwords", 1),
Check: resource.ComposeTestCheckFunc(
testAccCheckClusterExists(ctx, resourceName, &dbCluster),
),
},
{
Config: testAccClusterConfig_passwordWriteOnly(rName, "avoid-plaintext-updated", 2),
Check: resource.ComposeTestCheckFunc(
testAccCheckClusterExists(ctx, resourceName, &dbCluster),
),
},
},
})
}

func testAccCheckClusterDestroy(ctx context.Context) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := acctest.Provider.Meta().(*conns.AWSClient).DocDBClient(ctx)
Expand Down Expand Up @@ -1499,3 +1532,27 @@ resource "aws_docdb_cluster" "test" {
}
`, rName, storageType))
}

func testAccClusterConfig_passwordWriteOnly(rName, password string, passworVersion int) string {
return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(`
resource "aws_docdb_cluster" "test" {
cluster_identifier = %[1]q

availability_zones = [
data.aws_availability_zones.available.names[0],
data.aws_availability_zones.available.names[1],
data.aws_availability_zones.available.names[2]
]

master_password_wo = %[2]q
master_password_wo_version = %[3]d
master_username = "tfacctest"
skip_final_snapshot = true

enabled_cloudwatch_logs_exports = [
"audit",
"profiler",
]
}
`, rName, password, passworVersion))
}
7 changes: 5 additions & 2 deletions website/docs/r/docdb_cluster.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,11 @@ This resource supports the following arguments:
made.
* `global_cluster_identifier` - (Optional) The global cluster identifier specified on [`aws_docdb_global_cluster`](/docs/providers/aws/r/docdb_global_cluster.html).
* `kms_key_id` - (Optional) The ARN for the KMS encryption key. When specifying `kms_key_id`, `storage_encrypted` needs to be set to true.
* `master_password` - (Required unless a `snapshot_identifier` or unless a `global_cluster_identifier` is provided when the cluster is the "secondary" cluster of a global database) 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 DocumentDB Naming Constraints.
* `master_password` - (Optional, required unless a `snapshot_identifier` or unless a `global_cluster_identifier` is provided when the cluster is the "secondary" cluster of a global database) 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 DocumentDB Naming Constraints. Conflicts with `master_password_wo`.
* `master_password_wo` - (Optional, Write-Only, required unless a `snapshot_identifier` or unless a `global_cluster_identifier` is provided when the cluster is the "secondary" cluster of a global database) Password for the master DB user. Note that this may
show up in logs. Please refer to the DocumentDB Naming Constraints. Conflicts with `master_password`.
* `master_password_wo_version` - (Optional) Used together with `master_password_wo` to trigger an update. Increment this value when an update to the `master_password_wo` is required.
* `master_username` - (Required unless a `snapshot_identifier` or unless a `global_cluster_identifier` is provided when the cluster is the "secondary" cluster of a global database) Username for the master DB user.
* `port` - (Optional) The port on which the DB accepts connections
* `preferred_backup_window` - (Optional) The daily time range during which automated backups are created if automated backups are enabled using the BackupRetentionPeriod parameter.Time in UTC
Expand Down
Loading