diff --git a/internal/service/redshift/cluster.go b/internal/service/redshift/cluster.go index 404faa01ade9..439577585f6a 100644 --- a/internal/service/redshift/cluster.go +++ b/internal/service/redshift/cluster.go @@ -5,6 +5,7 @@ package redshift import ( "context" + "errors" "fmt" "log" "strings" @@ -22,6 +23,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" "github.com/hashicorp/terraform-provider-aws/internal/flex" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" @@ -36,6 +38,7 @@ func ResourceCluster() *schema.Resource { ReadWithoutTimeout: resourceClusterRead, UpdateWithoutTimeout: resourceClusterUpdate, DeleteWithoutTimeout: resourceClusterDelete, + Importer: &schema.ResourceImporter{ StateContext: resourceClusterImport, }, @@ -400,16 +403,18 @@ func ResourceCluster() *schema.Resource { CustomizeDiff: customdiff.All( verify.SetTagsDiff, func(_ context.Context, diff *schema.ResourceDiff, v interface{}) error { - if diff.Id() == "" { - return nil - } - if diff.Get("availability_zone_relocation_enabled").(bool) { - return nil + azRelocationEnabled, multiAZ := diff.Get("availability_zone_relocation_enabled").(bool), diff.Get("multi_az").(bool) + + if azRelocationEnabled && multiAZ { + return errors.New("`availability_zone_relocation_enabled` and `multi_az` cannot be both true") } - o, n := diff.GetChange("availability_zone") - if o.(string) != n.(string) { - return fmt.Errorf("cannot change `availability_zone` if `availability_zone_relocation_enabled` is not true") + + if diff.Id() != "" { + if o, n := diff.GetChange("availability_zone"); !azRelocationEnabled && o.(string) != n.(string) { + return errors.New("cannot change `availability_zone` if `availability_zone_relocation_enabled` is not true") + } } + return nil }, ), @@ -421,7 +426,7 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta int conn := meta.(*conns.AWSClient).RedshiftConn(ctx) clusterID := d.Get("cluster_identifier").(string) - backupInput := &redshift.RestoreFromClusterSnapshotInput{ + inputR := &redshift.RestoreFromClusterSnapshotInput{ AllowVersionUpgrade: aws.Bool(d.Get("allow_version_upgrade").(bool)), AutomatedSnapshotRetentionPeriod: aws.Int64(int64(d.Get("automated_snapshot_retention_period").(int))), ClusterIdentifier: aws.String(clusterID), @@ -429,8 +434,7 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta int NodeType: aws.String(d.Get("node_type").(string)), PubliclyAccessible: aws.Bool(d.Get("publicly_accessible").(bool)), } - - input := &redshift.CreateClusterInput{ + inputC := &redshift.CreateClusterInput{ AllowVersionUpgrade: aws.Bool(d.Get("allow_version_upgrade").(bool)), AutomatedSnapshotRetentionPeriod: aws.Int64(int64(d.Get("automated_snapshot_retention_period").(int))), ClusterIdentifier: aws.String(clusterID), @@ -444,123 +448,118 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta int } if v, ok := d.GetOk("aqua_configuration_status"); ok { - backupInput.AquaConfigurationStatus = aws.String(v.(string)) - input.AquaConfigurationStatus = aws.String(v.(string)) + inputR.AquaConfigurationStatus = aws.String(v.(string)) + inputC.AquaConfigurationStatus = aws.String(v.(string)) } if v, ok := d.GetOk("availability_zone"); ok { - backupInput.AvailabilityZone = aws.String(v.(string)) - input.AvailabilityZone = aws.String(v.(string)) + inputR.AvailabilityZone = aws.String(v.(string)) + inputC.AvailabilityZone = aws.String(v.(string)) } - azRelocation, azRelocationOk := d.GetOk("availability_zone_relocation_enabled") - multiAZ, multiAZOk := d.GetOk("multi_az") - if azRelocationOk && multiAZOk && azRelocation.(bool) && azRelocation.(bool) == multiAZ.(bool) { - return sdkdiag.AppendErrorf(diags, "availability_zone_relocation_enabled and multi_az can be both true") - } - if azRelocationOk { - backupInput.AvailabilityZoneRelocation = aws.Bool(azRelocation.(bool)) - input.AvailabilityZoneRelocation = aws.Bool(azRelocation.(bool)) - } - - if multiAZOk { - backupInput.MultiAZ = aws.Bool(multiAZ.(bool)) - input.MultiAZ = aws.Bool(multiAZ.(bool)) + if v, ok := d.GetOk("availability_zone_relocation_enabled"); ok { + inputR.AvailabilityZoneRelocation = aws.Bool(v.(bool)) + inputC.AvailabilityZoneRelocation = aws.Bool(v.(bool)) } if v, ok := d.GetOk("cluster_parameter_group_name"); ok { - backupInput.ClusterParameterGroupName = aws.String(v.(string)) - input.ClusterParameterGroupName = aws.String(v.(string)) + inputR.ClusterParameterGroupName = aws.String(v.(string)) + inputC.ClusterParameterGroupName = aws.String(v.(string)) } if v, ok := d.GetOk("cluster_subnet_group_name"); ok { - backupInput.ClusterSubnetGroupName = aws.String(v.(string)) - input.ClusterSubnetGroupName = aws.String(v.(string)) + inputR.ClusterSubnetGroupName = aws.String(v.(string)) + inputC.ClusterSubnetGroupName = aws.String(v.(string)) } if v, ok := d.GetOk("default_iam_role_arn"); ok { - backupInput.DefaultIamRoleArn = aws.String(v.(string)) - input.DefaultIamRoleArn = aws.String(v.(string)) + inputR.DefaultIamRoleArn = aws.String(v.(string)) + inputC.DefaultIamRoleArn = aws.String(v.(string)) } if v, ok := d.GetOk("elastic_ip"); ok { - backupInput.ElasticIp = aws.String(v.(string)) - input.ElasticIp = aws.String(v.(string)) + inputR.ElasticIp = aws.String(v.(string)) + inputC.ElasticIp = aws.String(v.(string)) } if v, ok := d.GetOk("enhanced_vpc_routing"); ok { - backupInput.EnhancedVpcRouting = aws.Bool(v.(bool)) - input.EnhancedVpcRouting = aws.Bool(v.(bool)) + inputR.EnhancedVpcRouting = aws.Bool(v.(bool)) + inputC.EnhancedVpcRouting = aws.Bool(v.(bool)) } if v, ok := d.GetOk("iam_roles"); ok { - backupInput.IamRoles = flex.ExpandStringSet(v.(*schema.Set)) - input.IamRoles = flex.ExpandStringSet(v.(*schema.Set)) + inputR.IamRoles = flex.ExpandStringSet(v.(*schema.Set)) + inputC.IamRoles = flex.ExpandStringSet(v.(*schema.Set)) } if v, ok := d.GetOk("kms_key_id"); ok { - backupInput.KmsKeyId = aws.String(v.(string)) - input.KmsKeyId = aws.String(v.(string)) + inputR.KmsKeyId = aws.String(v.(string)) + inputC.KmsKeyId = aws.String(v.(string)) } if v, ok := d.GetOk("maintenance_track_name"); ok { - backupInput.MaintenanceTrackName = aws.String(v.(string)) - input.MaintenanceTrackName = aws.String(v.(string)) + inputR.MaintenanceTrackName = aws.String(v.(string)) + inputC.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)) + inputR.ManageMasterPassword = aws.Bool(v.(bool)) + inputC.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))) + inputR.ManualSnapshotRetentionPeriod = aws.Int64(int64(v.(int))) + inputC.ManualSnapshotRetentionPeriod = aws.Int64(int64(v.(int))) } if v, ok := d.GetOk("master_password"); ok { - input.MasterUserPassword = aws.String(v.(string)) + inputC.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)) + inputR.MasterPasswordSecretKmsKeyId = aws.String(v.(string)) + inputC.MasterPasswordSecretKmsKeyId = aws.String(v.(string)) + } + + if v, ok := d.GetOk("multi_az"); ok { + inputR.MultiAZ = aws.Bool(v.(bool)) + inputC.MultiAZ = aws.Bool(v.(bool)) } if v, ok := d.GetOk("number_of_nodes"); ok { - backupInput.NumberOfNodes = aws.Int64(int64(v.(int))) + inputR.NumberOfNodes = aws.Int64(int64(v.(int))) // NumberOfNodes set below for CreateCluster. } if v, ok := d.GetOk("preferred_maintenance_window"); ok { - backupInput.PreferredMaintenanceWindow = aws.String(v.(string)) - input.PreferredMaintenanceWindow = aws.String(v.(string)) + inputR.PreferredMaintenanceWindow = aws.String(v.(string)) + inputC.PreferredMaintenanceWindow = aws.String(v.(string)) } if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 { - backupInput.VpcSecurityGroupIds = flex.ExpandStringSet(v) - input.VpcSecurityGroupIds = flex.ExpandStringSet(v) + inputR.VpcSecurityGroupIds = flex.ExpandStringSet(v) + inputC.VpcSecurityGroupIds = flex.ExpandStringSet(v) } if v, ok := d.GetOk("snapshot_identifier"); ok { - backupInput.SnapshotIdentifier = aws.String(v.(string)) + inputR.SnapshotIdentifier = aws.String(v.(string)) } if v, ok := d.GetOk("snapshot_arn"); ok { - backupInput.SnapshotArn = aws.String(v.(string)) + inputR.SnapshotArn = aws.String(v.(string)) } - if backupInput.SnapshotArn != nil || backupInput.SnapshotIdentifier != nil { + if inputR.SnapshotArn != nil || inputR.SnapshotIdentifier != nil { if v, ok := d.GetOk("owner_account"); ok { - backupInput.OwnerAccount = aws.String(v.(string)) + inputR.OwnerAccount = aws.String(v.(string)) } if v, ok := d.GetOk("snapshot_cluster_identifier"); ok { - backupInput.SnapshotClusterIdentifier = aws.String(v.(string)) + inputR.SnapshotClusterIdentifier = aws.String(v.(string)) } - log.Printf("[DEBUG] Restoring Redshift Cluster: %s", backupInput) - output, err := conn.RestoreFromClusterSnapshotWithContext(ctx, backupInput) + log.Printf("[DEBUG] Restoring Redshift Cluster: %s", inputR) + output, err := conn.RestoreFromClusterSnapshotWithContext(ctx, inputR) if err != nil { return sdkdiag.AppendErrorf(diags, "restoring Redshift Cluster (%s) from snapshot: %s", clusterID, err) @@ -579,17 +578,17 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta int } if v, ok := d.GetOk("encrypted"); ok { - input.Encrypted = aws.Bool(v.(bool)) + inputC.Encrypted = aws.Bool(v.(bool)) } if v := d.Get("number_of_nodes").(int); v > 1 { - input.ClusterType = aws.String(clusterTypeMultiNode) - input.NumberOfNodes = aws.Int64(int64(d.Get("number_of_nodes").(int))) + inputC.ClusterType = aws.String(clusterTypeMultiNode) + inputC.NumberOfNodes = aws.Int64(int64(d.Get("number_of_nodes").(int))) } else { - input.ClusterType = aws.String(clusterTypeSingleNode) + inputC.ClusterType = aws.String(clusterTypeSingleNode) } - output, err := conn.CreateClusterWithContext(ctx, input) + output, err := conn.CreateClusterWithContext(ctx, inputC) if err != nil { return sdkdiag.AppendErrorf(diags, "creating Redshift Cluster (%s): %s", clusterID, err) @@ -663,11 +662,11 @@ func resourceClusterRead(ctx context.Context, d *schema.ResourceData, meta inter } d.Set("automated_snapshot_retention_period", rsc.AutomatedSnapshotRetentionPeriod) d.Set("availability_zone", rsc.AvailabilityZone) - azr, err := clusterAvailabilityZoneRelocationStatus(rsc) - if err != nil { - return sdkdiag.AppendErrorf(diags, "reading Redshift Cluster (%s): %s", d.Id(), err) + if v, err := clusterAvailabilityZoneRelocationStatus(rsc); err != nil { + return sdkdiag.AppendFromErr(diags, err) + } else { + d.Set("availability_zone_relocation_enabled", v) } - d.Set("availability_zone_relocation_enabled", azr) d.Set("cluster_identifier", rsc.ClusterIdentifier) d.Set("cluster_namespace_arn", rsc.ClusterNamespaceArn) if err := d.Set("cluster_nodes", flattenClusterNodes(rsc.ClusterNodes)); err != nil { @@ -687,6 +686,9 @@ func resourceClusterRead(ctx context.Context, d *schema.ResourceData, meta inter d.Set("default_iam_role_arn", rsc.DefaultIamRoleArn) d.Set("encrypted", rsc.Encrypted) d.Set("enhanced_vpc_routing", rsc.EnhancedVpcRouting) + d.Set("iam_roles", tfslices.ApplyToAll(rsc.IamRoles, func(v *redshift.ClusterIamRole) string { + return aws.StringValue(v.IamRoleArn) + })) d.Set("kms_key_id", rsc.KmsKeyId) if err := d.Set("logging", flattenLogging(loggingStatus)); err != nil { return sdkdiag.AppendErrorf(diags, "setting logging: %s", err) @@ -696,11 +698,11 @@ func resourceClusterRead(ctx context.Context, d *schema.ResourceData, meta inter d.Set("master_username", rsc.MasterUsername) d.Set("master_password_secret_arn", rsc.MasterPasswordSecretArn) d.Set("master_password_secret_kms_key_id", rsc.MasterPasswordSecretKmsKeyId) - maz, err := clusterMultiAZStatus(rsc) - if err != nil { - return sdkdiag.AppendErrorf(diags, "reading Redshift Cluster (%s): %s", d.Id(), err) + if v, err := clusterMultiAZStatus(rsc); err != nil { + return sdkdiag.AppendFromErr(diags, err) + } else { + d.Set("multi_az", v) } - d.Set("multi_az", maz) d.Set("node_type", rsc.NodeType) d.Set("number_of_nodes", rsc.NumberOfNodes) d.Set("preferred_maintenance_window", rsc.PreferredMaintenanceWindow) @@ -708,6 +710,9 @@ func resourceClusterRead(ctx context.Context, d *schema.ResourceData, meta inter if err := d.Set("snapshot_copy", flattenSnapshotCopy(rsc.ClusterSnapshotCopyStatus)); err != nil { return sdkdiag.AppendErrorf(diags, "setting snapshot_copy: %s", err) } + d.Set("vpc_security_group_ids", tfslices.ApplyToAll(rsc.VpcSecurityGroups, func(v *redshift.VpcSecurityGroupMembership) string { + return aws.StringValue(v.VpcSecurityGroupId) + })) d.Set("dns_name", nil) d.Set("endpoint", nil) @@ -724,20 +729,6 @@ func resourceClusterRead(ctx context.Context, d *schema.ResourceData, meta inter } } - var apiList []*string - - for _, iamRole := range rsc.IamRoles { - apiList = append(apiList, iamRole.IamRoleArn) - } - d.Set("iam_roles", aws.StringValueSlice(apiList)) - - apiList = nil - - for _, vpcSecurityGroup := range rsc.VpcSecurityGroups { - apiList = append(apiList, vpcSecurityGroup.VpcSecurityGroupId) - } - d.Set("vpc_security_group_ids", aws.StringValueSlice(apiList)) - setTagsOut(ctx, rsc.Tags) return diags @@ -747,7 +738,7 @@ func resourceClusterUpdate(ctx context.Context, d *schema.ResourceData, meta int var diags diag.Diagnostics conn := meta.(*conns.AWSClient).RedshiftConn(ctx) - if d.HasChangesExcept("aqua_configuration_status", "availability_zone", "iam_roles", "multi_az", "logging", "snapshot_copy", "tags", "tags_all") { + if d.HasChangesExcept("aqua_configuration_status", "availability_zone", "iam_roles", "logging", "multi_az", "snapshot_copy", "tags", "tags_all") { input := &redshift.ModifyClusterInput{ ClusterIdentifier: aws.String(d.Id()), } @@ -844,19 +835,10 @@ func resourceClusterUpdate(ctx context.Context, d *schema.ResourceData, meta int } } - if d.HasChanges("iam_roles", "default_iam_role_arn") { + if d.HasChanges("default_iam_role_arn", "iam_roles") { o, n := d.GetChange("iam_roles") - if o == nil { - o = new(schema.Set) - } - if n == nil { - n = new(schema.Set) - } - - os := o.(*schema.Set) - ns := n.(*schema.Set) - add := ns.Difference(os) - del := os.Difference(ns) + os, ns := o.(*schema.Set), n.(*schema.Set) + add, del := ns.Difference(os), os.Difference(ns) input := &redshift.ModifyClusterIamRolesInput{ AddIamRoles: flex.ExpandStringSet(add), @@ -865,7 +847,6 @@ func resourceClusterUpdate(ctx context.Context, d *schema.ResourceData, meta int DefaultIamRoleArn: aws.String(d.Get("default_iam_role_arn").(string)), } - log.Printf("[DEBUG] Modifying Redshift Cluster IAM Roles: %s", input) _, err := conn.ModifyClusterIamRolesWithContext(ctx, input) if err != nil { @@ -883,7 +864,6 @@ func resourceClusterUpdate(ctx context.Context, d *schema.ResourceData, meta int ClusterIdentifier: aws.String(d.Id()), } - log.Printf("[DEBUG] Modifying Redshift Cluster Aqua Configuration: %s", input) _, err := conn.ModifyAquaConfigurationWithContext(ctx, input) if err != nil { @@ -891,13 +871,13 @@ func resourceClusterUpdate(ctx context.Context, d *schema.ResourceData, meta int } if d.Get("apply_immediately").(bool) { - rebootInput := &redshift.RebootClusterInput{ + input := &redshift.RebootClusterInput{ ClusterIdentifier: aws.String(d.Id()), } _, err := tfresource.RetryWhenAWSErrCodeEquals(ctx, clusterInvalidClusterStateFaultTimeout, func() (interface{}, error) { - return conn.RebootClusterWithContext(ctx, rebootInput) + return conn.RebootClusterWithContext(ctx, input) }, redshift.ErrCodeInvalidClusterStateFault, ) @@ -907,7 +887,7 @@ func resourceClusterUpdate(ctx context.Context, d *schema.ResourceData, meta int } if _, err := waitClusterRebooted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { - return sdkdiag.AppendErrorf(diags, "waiting for Redshift Cluster (%s) Rebooted: %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "waiting for Redshift Cluster (%s) reboot: %s", d.Id(), err) } if _, err := waitClusterAquaApplied(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { @@ -923,7 +903,6 @@ func resourceClusterUpdate(ctx context.Context, d *schema.ResourceData, meta int ClusterIdentifier: aws.String(d.Id()), } - log.Printf("[DEBUG] Relocating Redshift Cluster: %s", input) _, err := conn.ModifyClusterWithContext(ctx, input) if err != nil { @@ -964,37 +943,34 @@ func resourceClusterUpdate(ctx context.Context, d *schema.ResourceData, meta int } if d.HasChange("multi_az") { - multiAZEnabled := d.Get("multi_az").(bool) - azRelocationEnabled := d.Get("availability_zone_relocation_enabled").(bool) - if multiAZEnabled && azRelocationEnabled { - return sdkdiag.AppendErrorf(diags, "modifying Redshift Cluster (%s): availability_zone_relocation_enabled and multi_az can't be both true", d.Id()) - } + azRelocationEnabled, multiAZ := d.Get("availability_zone_relocation_enabled").(bool), d.Get("multi_az").(bool) input := &redshift.ModifyClusterInput{ - MultiAZ: aws.Bool(azRelocationEnabled), ClusterIdentifier: aws.String(d.Id()), + MultiAZ: aws.Bool(multiAZ), } - log.Printf("[DEBUG] Changing cluster multi AZ configuration: %s", input) _, err := conn.ModifyClusterWithContext(ctx, input) if err != nil { - return sdkdiag.AppendErrorf(diags, "Changing cluster multi AZ configuration (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "modifying Redshift Cluster (%s) multi-AZ: %s", d.Id(), err) } if _, err = waitClusterUpdated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { return sdkdiag.AppendErrorf(diags, "waiting for Redshift Cluster (%s) update: %s", d.Id(), err) } - if !multiAZEnabled { - // Disabling MultiAZ, Redshift automatically enables the AZ Relocation. For that reason is necessary to align it with the current configuration + + if !multiAZ { + // Disabling MultiAZ, Redshift automatically enables AZ Relocation. + // For that reason is necessary to align it with the current configuration. input = &redshift.ModifyClusterInput{ AvailabilityZoneRelocation: aws.Bool(azRelocationEnabled), ClusterIdentifier: aws.String(d.Id()), } - log.Printf("[DEBUG] Changing cluster availability zone relocation configuration: %s\n", input) + _, err = conn.ModifyClusterWithContext(ctx, input) if err != nil { - return sdkdiag.AppendErrorf(diags, "Changing cluster multi AZ configuration (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "modifying Redshift Cluster (%s) AZ relocation: %s", d.Id(), err) } if _, err = waitClusterUpdated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil {