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/aws_redshift_cluster: support managed master passwords #34182

Merged
merged 1 commit into from
Nov 1, 2023
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/34182.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_redshift_cluster: Add the `manage_master_password` and `master_password_secret_kms_key_id` arguments to support managed admin credentials
```
45 changes: 43 additions & 2 deletions internal/service/redshift/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,11 @@ func ResourceCluster() *schema.Resource {
Optional: true,
Default: "current",
},
"manage_master_password": {
Type: schema.TypeBool,
Optional: true,
ConflictsWith: []string{"master_password"},
},
"manual_snapshot_retention_period": {
Type: schema.TypeInt,
Optional: true,
Expand All @@ -272,6 +277,17 @@ func ResourceCluster() *schema.Resource {
validation.StringMatch(regexache.MustCompile(`^.*[0-9].*`), "must contain at least one number"),
validation.StringMatch(regexache.MustCompile(`^[^\@\/'" ]*$`), "cannot contain [/@\"' ]"),
),
ConflictsWith: []string{"manage_master_password"},
},
"master_password_secret_arn": {
Type: schema.TypeString,
Computed: true,
},
"master_password_secret_kms_key_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: verify.ValidKMSKeyID,
},
"master_username": {
Type: schema.TypeString,
Expand Down Expand Up @@ -415,7 +431,6 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta int
ClusterVersion: aws.String(d.Get("cluster_version").(string)),
DBName: aws.String(d.Get("database_name").(string)),
MasterUsername: aws.String(d.Get("master_username").(string)),
MasterUserPassword: aws.String(d.Get("master_password").(string)),
NodeType: aws.String(d.Get("node_type").(string)),
Port: aws.Int64(int64(d.Get("port").(int))),
PubliclyAccessible: aws.Bool(d.Get("publicly_accessible").(bool)),
Expand Down Expand Up @@ -477,11 +492,25 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta int
input.MaintenanceTrackName = aws.String(v.(string))
}

if v, ok := d.GetOk("manage_master_password"); ok {
backupInput.ManageMasterPassword = aws.Bool(v.(bool))
input.ManageMasterPassword = aws.Bool(v.(bool))
}

if v, ok := d.GetOk("manual_snapshot_retention_period"); ok {
backupInput.ManualSnapshotRetentionPeriod = aws.Int64(int64(v.(int)))
input.ManualSnapshotRetentionPeriod = aws.Int64(int64(v.(int)))
}

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

if v, ok := d.GetOk("master_password_secret_kms_key_id"); ok {
backupInput.MasterPasswordSecretKmsKeyId = aws.String(v.(string))
input.MasterPasswordSecretKmsKeyId = aws.String(v.(string))
}

if v, ok := d.GetOk("number_of_nodes"); ok {
backupInput.NumberOfNodes = aws.Int64(int64(v.(int)))
// NumberOfNodes set below for CreateCluster.
Expand Down Expand Up @@ -524,7 +553,9 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta int
d.SetId(aws.StringValue(output.Cluster.ClusterIdentifier))
} else {
if _, ok := d.GetOk("master_password"); !ok {
return sdkdiag.AppendErrorf(diags, `provider.aws: aws_redshift_cluster: %s: "master_password": required field is not set`, d.Get("cluster_identifier").(string))
if _, ok := d.GetOk("manage_master_password"); !ok {
return sdkdiag.AppendErrorf(diags, `provider.aws: aws_redshift_cluster: %s: one of "manage_master_password" or "master_password" is required`, d.Get("cluster_identifier").(string))
}
}

if _, ok := d.GetOk("master_username"); !ok {
Expand Down Expand Up @@ -647,6 +678,8 @@ func resourceClusterRead(ctx context.Context, d *schema.ResourceData, meta inter
d.Set("maintenance_track_name", rsc.MaintenanceTrackName)
d.Set("manual_snapshot_retention_period", rsc.ManualSnapshotRetentionPeriod)
d.Set("master_username", rsc.MasterUsername)
d.Set("master_password_secret_arn", rsc.MasterPasswordSecretArn)
d.Set("master_password_secret_kms_key_id", rsc.MasterPasswordSecretKmsKeyId)
d.Set("node_type", rsc.NodeType)
d.Set("number_of_nodes", rsc.NumberOfNodes)
d.Set("preferred_maintenance_window", rsc.PreferredMaintenanceWindow)
Expand Down Expand Up @@ -755,6 +788,14 @@ func resourceClusterUpdate(ctx context.Context, d *schema.ResourceData, meta int
input.MasterUserPassword = aws.String(d.Get("master_password").(string))
}

if d.HasChange("master_password_secret_kms_key_id") {
input.MasterPasswordSecretKmsKeyId = aws.String(d.Get("master_password_secret_kms_key_id").(string))
}

if d.HasChange("manage_master_password") {
input.ManageMasterPassword = aws.Bool(d.Get("manage_master_password").(bool))
}

if d.HasChange("preferred_maintenance_window") {
input.PreferredMaintenanceWindow = aws.String(d.Get("preferred_maintenance_window").(string))
}
Expand Down
53 changes: 53 additions & 0 deletions internal/service/redshift/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,42 @@ func TestAccRedshiftCluster_restoreFromSnapshotARN(t *testing.T) {
})
}

func TestAccRedshiftCluster_manageMasterPassword(t *testing.T) {
ctx := acctest.Context(t)
var v redshift.Cluster
resourceName := "aws_redshift_cluster.test"
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, redshift.EndpointsID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckClusterDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccClusterConfig_manageMasterPassword(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckClusterExists(ctx, resourceName, &v),
resource.TestCheckResourceAttrPair(resourceName, "availability_zone", "data.aws_availability_zones.available", "names.0"),
resource.TestCheckResourceAttr(resourceName, "manage_master_password", "true"),
resource.TestCheckResourceAttrSet(resourceName, "master_password_secret_arn"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
"final_snapshot_identifier",
"manage_master_password",
"skip_final_snapshot",
"apply_immediately",
},
},
},
})
}

func testAccCheckClusterDestroy(ctx context.Context) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := acctest.Provider.Meta().(*conns.AWSClient).RedshiftConn(ctx)
Expand Down Expand Up @@ -1734,3 +1770,20 @@ resource "aws_redshift_cluster" "test2" {
}
`, rName))
}

func testAccClusterConfig_manageMasterPassword(rName string) string {
// "InvalidVPCNetworkStateFault: The requested AZ us-west-2a is not a valid AZ."
return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptInExclude("usw2-az2"), fmt.Sprintf(`
resource "aws_redshift_cluster" "test" {
cluster_identifier = %[1]q
availability_zone = data.aws_availability_zones.available.names[0]
database_name = "mydb"
master_username = "foo_test"
manage_master_password = true
node_type = "dc2.large"
automated_snapshot_retention_period = 0
allow_version_upgrade = false
skip_final_snapshot = true
}
`, rName))
}
31 changes: 27 additions & 4 deletions website/docs/r/redshift_cluster.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Provides a Redshift Cluster Resource.

## Example Usage

### Basic Usage

```terraform
resource "aws_redshift_cluster" "example" {
cluster_identifier = "tf-redshift-cluster"
Expand All @@ -28,22 +30,42 @@ resource "aws_redshift_cluster" "example" {
}
```

### With Managed Credentials

```terraform
resource "aws_redshift_cluster" "example" {
cluster_identifier = "tf-redshift-cluster"
database_name = "mydb"
master_username = "exampleuser"
node_type = "dc1.large"
cluster_type = "single-node"

manage_master_password = true
}
```

## Argument Reference

For more detailed documentation about each argument, refer to
the [AWS official documentation](http://docs.aws.amazon.com/cli/latest/reference/redshift/index.html#cli-aws-redshift).

This argument supports the following arguments:
This resource supports the following arguments:

* `cluster_identifier` - (Required) The Cluster Identifier. Must be a lower case string.
* `database_name` - (Optional) The name of the first database to be created when the cluster is created.
If you do not provide a name, Amazon Redshift will create a default database called `dev`.
* `default_iam_role_arn` - (Optional) The Amazon Resource Name (ARN) for the IAM role that was set as default for the cluster when the cluster was created.
* `node_type` - (Required) The node type to be provisioned for the cluster.
* `cluster_type` - (Optional) The cluster type to use. Either `single-node` or `multi-node`.
* `master_password` - (Required unless a `snapshot_identifier` 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. Password must contain at least 8 chars and
contain at least one uppercase letter, one lowercase letter, and one number.
* `manage_master_password` - (Optional) Whether to use AWS SecretsManager to manage the cluster admin credentials.
Conflicts with `master_password`.
One of `master_password` or `manage_master_password` is required unless `snapshot_identifier` is provided.
* `master_password` - (Optional) Password for the master DB user.
Conflicts with `manage_master_password`.
One of `master_password` or `manage_master_password` is required unless `snapshot_identifier` is provided.
Note that this may show up in logs, and it will be stored in the state file.
Password must contain at least 8 characters and contain at least one uppercase letter, one lowercase letter, and one number.
* `master_password_secret_kms_key_id` - (Optional) ID of the KMS key used to encrypt the cluster admin credentials secret.
* `master_username` - (Required unless a `snapshot_identifier` is provided) Username for the master DB user.
* `vpc_security_group_ids` - (Optional) A list of Virtual Private Cloud (VPC) security groups to be associated with the cluster.
* `cluster_subnet_group_name` - (Optional) The name of a cluster subnet group to be associated with this cluster. If this parameter is not provided the resulting cluster will be deployed outside virtual private cloud (VPC).
Expand Down Expand Up @@ -117,6 +139,7 @@ This resource exports the following attributes in addition to the arguments abov
* `encrypted` - Whether the data in the cluster is encrypted
* `vpc_security_group_ids` - The VPC security group Ids associated with the cluster
* `dns_name` - The DNS name of the cluster
* `master_password_secret_arn` - ARN of the cluster admin credentials secret
* `port` - The Port the cluster responds on
* `cluster_version` - The version of Redshift engine software
* `cluster_parameter_group_name` - The name of the parameter group to be associated with this cluster
Expand Down
Loading