Skip to content

Commit

Permalink
Merge pull request #41366 from hashicorp/f-rds_db_instance_wo
Browse files Browse the repository at this point in the history
r/db_instance: add `password_wo` write-only attribute
  • Loading branch information
johnsonaj authored Feb 14, 2025
2 parents 8c66b18 + 36a355e commit c5241a7
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 9 deletions.
3 changes: 3 additions & 0 deletions .changelog/41366.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_db_instance: Add `password_wo` write-only attribute
```
6 changes: 5 additions & 1 deletion internal/service/rds/blue_green.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/aws/aws-sdk-go-v2/service/rds/types"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-provider-aws/internal/errs"
"github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
"github.com/hashicorp/terraform-provider-aws/names"
)
Expand Down Expand Up @@ -149,7 +150,10 @@ func (h *instanceHandler) modifyTarget(ctx context.Context, identifier string, d
DBInstanceIdentifier: aws.String(identifier),
}

needsModify := dbInstancePopulateModify(modifyInput, d)
needsModify, diags := dbInstancePopulateModify(modifyInput, d)
if diags.HasError() {
return fmt.Errorf("populating modify input: %s", sdkdiag.DiagnosticsString(diags))
}

if needsModify {
log.Printf("[DEBUG] %s: Updating Green environment", operation)
Expand Down
67 changes: 63 additions & 4 deletions internal/service/rds/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ func resourceInstance() *schema.Resource {
Delete: schema.DefaultTimeout(60 * time.Minute),
},

ValidateRawResourceConfigFuncs: []schema.ValidateRawResourceConfigFunc{
validation.PreferWriteOnlyAttribute(cty.GetAttrPath(names.AttrPassword), cty.GetAttrPath("password_wo")),
},

Schema: map[string]*schema.Schema{
names.AttrAddress: {
Type: schema.TypeString,
Expand Down Expand Up @@ -416,7 +420,7 @@ func resourceInstance() *schema.Resource {
"manage_master_user_password": {
Type: schema.TypeBool,
Optional: true,
ConflictsWith: []string{names.AttrPassword},
ConflictsWith: []string{names.AttrPassword, "password_wo"},
},
"master_user_secret": {
Type: schema.TypeList,
Expand Down Expand Up @@ -497,7 +501,19 @@ func resourceInstance() *schema.Resource {
Type: schema.TypeString,
Optional: true,
Sensitive: true,
ConflictsWith: []string{"manage_master_user_password"},
ConflictsWith: []string{"manage_master_user_password", "password_wo"},
},
"password_wo": {
Type: schema.TypeString,
Optional: true,
WriteOnly: true,
Sensitive: true,
ConflictsWith: []string{"manage_master_user_password", names.AttrPassword},
},
"password_wo_version": {
Type: schema.TypeInt,
Optional: true,
RequiredWith: []string{"password_wo"},
},
"performance_insights_enabled": {
Type: schema.TypeBool,
Expand Down Expand Up @@ -770,6 +786,13 @@ func resourceInstanceCreate(ctx context.Context, d *schema.ResourceData, meta in
}
}

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

if v, ok := d.GetOk("replicate_source_db"); ok {
sourceDBInstanceID := v.(string)
input := &rds.CreateDBInstanceReadReplicaInput{
Expand Down Expand Up @@ -1001,6 +1024,11 @@ func resourceInstanceCreate(ctx context.Context, d *schema.ResourceData, meta in
modifyDbInstanceInput.MasterUserPassword = aws.String(v.(string))
requiresModifyDbInstance = true
}

if passwordWO != "" {
modifyDbInstanceInput.MasterUserPassword = aws.String(passwordWO)
requiresModifyDbInstance = true
}
} else if v, ok := d.GetOk("s3_import"); ok {
if _, ok := d.GetOk(names.AttrAllocatedStorage); !ok {
diags = sdkdiag.AppendErrorf(diags, `"allocated_storage": required field is not set`)
Expand Down Expand Up @@ -1114,6 +1142,10 @@ func resourceInstanceCreate(ctx context.Context, d *schema.ResourceData, meta in
input.MasterUserPassword = aws.String(v.(string))
}

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

if v, ok := d.GetOk("performance_insights_enabled"); ok {
input.EnablePerformanceInsights = aws.Bool(v.(bool))
}
Expand Down Expand Up @@ -1357,6 +1389,11 @@ func resourceInstanceCreate(ctx context.Context, d *schema.ResourceData, meta in
requiresModifyDbInstance = true
}

if passwordWO != "" {
modifyDbInstanceInput.MasterUserPassword = aws.String(passwordWO)
requiresModifyDbInstance = true
}

if v, ok := d.GetOk("performance_insights_enabled"); ok {
modifyDbInstanceInput.EnablePerformanceInsights = aws.Bool(v.(bool))
requiresModifyDbInstance = true
Expand Down Expand Up @@ -1583,6 +1620,11 @@ func resourceInstanceCreate(ctx context.Context, d *schema.ResourceData, meta in
requiresModifyDbInstance = true
}

if passwordWO != "" {
modifyDbInstanceInput.MasterUserPassword = aws.String(passwordWO)
requiresModifyDbInstance = true
}

if v, ok := d.GetOk(names.AttrPort); ok {
input.Port = aws.Int32(int32(v.(int)))
}
Expand Down Expand Up @@ -1779,6 +1821,10 @@ func resourceInstanceCreate(ctx context.Context, d *schema.ResourceData, meta in
input.MasterUserPassword = aws.String(v.(string))
}

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

if v, ok := d.GetOk(names.AttrParameterGroupName); ok {
input.DBParameterGroupName = aws.String(v.(string))
}
Expand Down Expand Up @@ -2407,7 +2453,8 @@ func dbInstanceCreateReadReplica(ctx context.Context, conn *rds.Client, input *r
return outputRaw.(*rds.CreateDBInstanceReadReplicaOutput), nil
}

func dbInstancePopulateModify(input *rds.ModifyDBInstanceInput, d *schema.ResourceData) bool {
func dbInstancePopulateModify(input *rds.ModifyDBInstanceInput, d *schema.ResourceData) (bool, diag.Diagnostics) {
var diags diag.Diagnostics
needsModify := false

if d.HasChanges(names.AttrAllocatedStorage, names.AttrIOPS) {
Expand Down Expand Up @@ -2582,6 +2629,18 @@ func dbInstancePopulateModify(input *rds.ModifyDBInstanceInput, d *schema.Resour
}
}

if d.HasChange("password_wo_version") {
passwordWO, di := flex.GetWriteOnlyStringValue(d, cty.GetAttrPath("password_wo"))
diags = append(diags, di...)
if diags.HasError() {
return false, diags
}

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

if d.HasChanges("performance_insights_enabled", "performance_insights_kms_key_id", "performance_insights_retention_period") {
needsModify = true
input.EnablePerformanceInsights = aws.Bool(d.Get("performance_insights_enabled").(bool))
Expand Down Expand Up @@ -2646,7 +2705,7 @@ func dbInstancePopulateModify(input *rds.ModifyDBInstanceInput, d *schema.Resour
}
}

return needsModify
return needsModify, diags
}

func dbInstanceModify(ctx context.Context, conn *rds.Client, resourceID string, input *rds.ModifyDBInstanceInput, timeout time.Duration) error {
Expand Down
71 changes: 71 additions & 0 deletions internal/service/rds/instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ import (
"github.com/aws/aws-sdk-go-v2/service/rds/types"
"github.com/hashicorp/aws-sdk-go-base/v2/endpoints"
"github.com/hashicorp/aws-sdk-go-base/v2/tfawserr"
"github.com/hashicorp/go-version"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/id"
"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/plancheck"
"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"
"github.com/hashicorp/terraform-provider-aws/internal/envvar"
Expand Down Expand Up @@ -880,6 +882,58 @@ func TestAccRDSInstance_password(t *testing.T) {
})
}

func TestAccRDSInstance_passwordWriteOnly(t *testing.T) {
ctx := acctest.Context(t)
if testing.Short() {
t.Skip("skipping long-running test in short mode")
}

var v1, v2 types.DBInstance
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_db_instance.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, names.RDSServiceID),
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.SkipBelow(version.Must(version.NewVersion("1.11.0"))),
},
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckDBInstanceDestroy(ctx),
Steps: []resource.TestStep{
// Password should not be shown in error message
{
Config: testAccInstanceConfig_passwordWriteOnly(rName, "invalid", 1),
ExpectError: regexache.MustCompile(`MasterUserPassword is not a valid password because it is shorter than 8 characters`),
},
{
Config: testAccInstanceConfig_passwordWriteOnly(rName, "valid-password-1", 1),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckDBInstanceExists(ctx, resourceName, &v1),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
names.AttrApplyImmediately,
names.AttrFinalSnapshotIdentifier,
names.AttrPassword,
"skip_final_snapshot",
},
},
{
Config: testAccInstanceConfig_passwordWriteOnly(rName, "valid-password-2", 2),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckDBInstanceExists(ctx, resourceName, &v2),
testAccCheckDBInstanceNotRecreated(&v1, &v2),
),
},
},
})
}

func TestAccRDSInstance_ManageMasterPassword_basic(t *testing.T) {
ctx := acctest.Context(t)
var v types.DBInstance
Expand Down Expand Up @@ -9062,6 +9116,23 @@ resource "aws_db_instance" "test" {
`, rName, password))
}

func testAccInstanceConfig_passwordWriteOnly(rName, password string, passwordVersion int) string {
return acctest.ConfigCompose(
testAccInstanceConfig_orderableClassMySQL(),
fmt.Sprintf(`
resource "aws_db_instance" "test" {
allocated_storage = 5
engine = data.aws_rds_orderable_db_instance.test.engine
identifier = %[1]q
instance_class = data.aws_rds_orderable_db_instance.test.instance_class
password_wo = %[2]q
password_wo_version = %[3]d
username = "tfacctest"
skip_final_snapshot = true
}
`, rName, password, passwordVersion))
}

func testAccInstanceConfig_manageMasterPassword(rName string) string {
return acctest.ConfigCompose(
testAccInstanceConfig_orderableClassMySQL(),
Expand Down
8 changes: 4 additions & 4 deletions website/docs/r/db_instance.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ Syntax: "ddd:hh24:mi-ddd:hh24:mi". Eg: "Mon:00:00-Mon:03:00". See [RDS
Maintenance Window
docs](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_UpgradeDBInstance.Maintenance.html#AdjustingTheMaintenanceWindow)
for more information.
* `manage_master_user_password` - (Optional) Set to true to allow RDS to manage the master user password in Secrets Manager. Cannot be set if `password` is provided.
* `manage_master_user_password` - (Optional) Set to true to allow RDS to manage the master user password in Secrets Manager. Cannot be set if `password` or `password_wo` is provided.
* `master_user_secret_kms_key_id` - (Optional) The Amazon Web Services KMS key identifier is the key ARN, key ID, alias ARN, or alias name for the KMS key. To use a KMS key in a different Amazon Web Services account, specify the key ARN or alias ARN. If not specified, the default KMS key for your Amazon Web Services account is used.
* `max_allocated_storage` - (Optional) Specifies the maximum storage (in GiB) that Amazon RDS can automatically scale to for this DB instance. By default, Storage Autoscaling is disabled. To enable Storage Autoscaling, set `max_allocated_storage` to **greater than or equal to** `allocated_storage`. Setting `max_allocated_storage` to 0 explicitly disables Storage Autoscaling. When configured, changes to `allocated_storage` will be automatically ignored as the storage can dynamically scale.
* `monitoring_interval` - (Optional) The interval, in seconds, between points
Expand All @@ -373,9 +373,9 @@ Supported in Amazon RDS](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/
* `network_type` - (Optional) The network type of the DB instance. Valid values: `IPV4`, `DUAL`.
* `option_group_name` - (Optional) Name of the DB option group to associate.
* `parameter_group_name` - (Optional) Name of the DB parameter group to associate.
* `password` - (Required unless `manage_master_user_password` is set to true or unless a `snapshot_identifier` or `replicate_source_db`
is provided or `manage_master_user_password` is set.) 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`.
* `password` - (Optional required unless `manage_master_user_password` is set to true, `snapshot_identifier`, `replicate_source_db`, or `password_wo` is provided) 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`.
* `password_wo` - (Optional, Write-Only required unless `manage_master_user_password` is set to true, `snapshot_identifier`, `replicate_source_db`, or `password` is provided) 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`.
* `password_wo_version` - (Optional) Used together with `password_wo` to trigger an update. Increment this value when an update to `password_wo` is required.
* `performance_insights_enabled` - (Optional) Specifies whether Performance Insights are enabled. Defaults to false.
* `performance_insights_kms_key_id` - (Optional) The ARN for the KMS key to encrypt Performance Insights data. When specifying `performance_insights_kms_key_id`, `performance_insights_enabled` needs to be set to true. Once KMS key is set, it can never be changed.
* `performance_insights_retention_period` - (Optional) Amount of time in days to retain Performance Insights data. Valid values are `7`, `731` (2 years) or a multiple of `31`. When specifying `performance_insights_retention_period`, `performance_insights_enabled` needs to be set to true. Defaults to '7'.
Expand Down

0 comments on commit c5241a7

Please sign in to comment.