terraform/builtin/providers/aws/resource_aws_redshift_clust...

955 lines
28 KiB
Go

package aws
import (
"fmt"
"log"
"regexp"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/redshift"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsRedshiftCluster() *schema.Resource {
return &schema.Resource{
Create: resourceAwsRedshiftClusterCreate,
Read: resourceAwsRedshiftClusterRead,
Update: resourceAwsRedshiftClusterUpdate,
Delete: resourceAwsRedshiftClusterDelete,
Importer: &schema.ResourceImporter{
State: resourceAwsRedshiftClusterImport,
},
Schema: map[string]*schema.Schema{
"database_name": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validateRedshiftClusterDbName,
},
"cluster_identifier": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateRedshiftClusterIdentifier,
},
"cluster_type": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"node_type": {
Type: schema.TypeString,
Required: true,
},
"master_username": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRedshiftClusterMasterUsername,
},
"master_password": {
Type: schema.TypeString,
Optional: true,
Sensitive: true,
ValidateFunc: validateRedshiftClusterMasterPassword,
},
"cluster_security_groups": {
Type: schema.TypeSet,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"vpc_security_group_ids": {
Type: schema.TypeSet,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"cluster_subnet_group_name": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
},
"availability_zone": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"preferred_maintenance_window": {
Type: schema.TypeString,
Optional: true,
Computed: true,
StateFunc: func(val interface{}) string {
if val == nil {
return ""
}
return strings.ToLower(val.(string))
},
ValidateFunc: validateOnceAWeekWindowFormat,
},
"cluster_parameter_group_name": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"automated_snapshot_retention_period": {
Type: schema.TypeInt,
Optional: true,
Default: 1,
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
value := v.(int)
if value > 35 {
es = append(es, fmt.Errorf(
"backup retention period cannot be more than 35 days"))
}
return
},
},
"port": {
Type: schema.TypeInt,
Optional: true,
Default: 5439,
},
"cluster_version": {
Type: schema.TypeString,
Optional: true,
Default: "1.0",
},
"allow_version_upgrade": {
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"number_of_nodes": {
Type: schema.TypeInt,
Optional: true,
Default: 1,
},
"publicly_accessible": {
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"encrypted": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"enhanced_vpc_routing": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"kms_key_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ValidateFunc: validateArn,
},
"elastic_ip": {
Type: schema.TypeString,
Optional: true,
},
"final_snapshot_identifier": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRedshiftClusterFinalSnapshotIdentifier,
},
"skip_final_snapshot": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"endpoint": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"cluster_public_key": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"cluster_revision_number": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"iam_roles": {
Type: schema.TypeSet,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"enable_logging": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"bucket_name": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"s3_key_prefix": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"snapshot_identifier": {
Type: schema.TypeString,
Optional: true,
},
"snapshot_cluster_identifier": {
Type: schema.TypeString,
Optional: true,
},
"owner_account": {
Type: schema.TypeString,
Optional: true,
},
"tags": tagsSchema(),
},
}
}
func resourceAwsRedshiftClusterImport(
d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
// Neither skip_final_snapshot nor final_snapshot_identifier can be fetched
// from any API call, so we need to default skip_final_snapshot to true so
// that final_snapshot_identifier is not required
d.Set("skip_final_snapshot", true)
return []*schema.ResourceData{d}, nil
}
func resourceAwsRedshiftClusterCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).redshiftconn
tags := tagsFromMapRedshift(d.Get("tags").(map[string]interface{}))
if v, ok := d.GetOk("snapshot_identifier"); ok {
restoreOpts := &redshift.RestoreFromClusterSnapshotInput{
ClusterIdentifier: aws.String(d.Get("cluster_identifier").(string)),
SnapshotIdentifier: aws.String(v.(string)),
Port: aws.Int64(int64(d.Get("port").(int))),
AllowVersionUpgrade: aws.Bool(d.Get("allow_version_upgrade").(bool)),
NodeType: aws.String(d.Get("node_type").(string)),
PubliclyAccessible: aws.Bool(d.Get("publicly_accessible").(bool)),
AutomatedSnapshotRetentionPeriod: aws.Int64(int64(d.Get("automated_snapshot_retention_period").(int))),
}
if v, ok := d.GetOk("owner_account"); ok {
restoreOpts.OwnerAccount = aws.String(v.(string))
}
if v, ok := d.GetOk("snapshot_cluster_identifier"); ok {
restoreOpts.SnapshotClusterIdentifier = aws.String(v.(string))
}
if v, ok := d.GetOk("availability_zone"); ok {
restoreOpts.AvailabilityZone = aws.String(v.(string))
}
if v, ok := d.GetOk("cluster_subnet_group_name"); ok {
restoreOpts.ClusterSubnetGroupName = aws.String(v.(string))
}
if v, ok := d.GetOk("cluster_parameter_group_name"); ok {
restoreOpts.ClusterParameterGroupName = aws.String(v.(string))
}
if v := d.Get("cluster_security_groups").(*schema.Set); v.Len() > 0 {
restoreOpts.ClusterSecurityGroups = expandStringList(v.List())
}
if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 {
restoreOpts.VpcSecurityGroupIds = expandStringList(v.List())
}
if v, ok := d.GetOk("preferred_maintenance_window"); ok {
restoreOpts.PreferredMaintenanceWindow = aws.String(v.(string))
}
if v, ok := d.GetOk("kms_key_id"); ok {
restoreOpts.KmsKeyId = aws.String(v.(string))
}
if v, ok := d.GetOk("elastic_ip"); ok {
restoreOpts.ElasticIp = aws.String(v.(string))
}
if v, ok := d.GetOk("enhanced_vpc_routing"); ok {
restoreOpts.EnhancedVpcRouting = aws.Bool(v.(bool))
}
if v, ok := d.GetOk("iam_roles"); ok {
restoreOpts.IamRoles = expandStringList(v.(*schema.Set).List())
}
log.Printf("[DEBUG] Redshift Cluster restore cluster options: %s", restoreOpts)
resp, err := conn.RestoreFromClusterSnapshot(restoreOpts)
if err != nil {
log.Printf("[ERROR] Error Restoring Redshift Cluster from Snapshot: %s", err)
return err
}
d.SetId(*resp.Cluster.ClusterIdentifier)
} else {
if _, ok := d.GetOk("master_password"); !ok {
return fmt.Errorf(`provider.aws: aws_redshift_cluster: %s: "master_password": required field is not set`, d.Get("cluster_identifier").(string))
}
if _, ok := d.GetOk("master_username"); !ok {
return fmt.Errorf(`provider.aws: aws_redshift_cluster: %s: "master_username": required field is not set`, d.Get("cluster_identifier").(string))
}
createOpts := &redshift.CreateClusterInput{
ClusterIdentifier: aws.String(d.Get("cluster_identifier").(string)),
Port: aws.Int64(int64(d.Get("port").(int))),
MasterUserPassword: aws.String(d.Get("master_password").(string)),
MasterUsername: aws.String(d.Get("master_username").(string)),
ClusterVersion: aws.String(d.Get("cluster_version").(string)),
NodeType: aws.String(d.Get("node_type").(string)),
DBName: aws.String(d.Get("database_name").(string)),
AllowVersionUpgrade: aws.Bool(d.Get("allow_version_upgrade").(bool)),
PubliclyAccessible: aws.Bool(d.Get("publicly_accessible").(bool)),
AutomatedSnapshotRetentionPeriod: aws.Int64(int64(d.Get("automated_snapshot_retention_period").(int))),
Tags: tags,
}
if v := d.Get("number_of_nodes").(int); v > 1 {
createOpts.ClusterType = aws.String("multi-node")
createOpts.NumberOfNodes = aws.Int64(int64(d.Get("number_of_nodes").(int)))
} else {
createOpts.ClusterType = aws.String("single-node")
}
if v := d.Get("cluster_security_groups").(*schema.Set); v.Len() > 0 {
createOpts.ClusterSecurityGroups = expandStringList(v.List())
}
if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 {
createOpts.VpcSecurityGroupIds = expandStringList(v.List())
}
if v, ok := d.GetOk("cluster_subnet_group_name"); ok {
createOpts.ClusterSubnetGroupName = aws.String(v.(string))
}
if v, ok := d.GetOk("availability_zone"); ok {
createOpts.AvailabilityZone = aws.String(v.(string))
}
if v, ok := d.GetOk("preferred_maintenance_window"); ok {
createOpts.PreferredMaintenanceWindow = aws.String(v.(string))
}
if v, ok := d.GetOk("cluster_parameter_group_name"); ok {
createOpts.ClusterParameterGroupName = aws.String(v.(string))
}
if v, ok := d.GetOk("encrypted"); ok {
createOpts.Encrypted = aws.Bool(v.(bool))
}
if v, ok := d.GetOk("enhanced_vpc_routing"); ok {
createOpts.EnhancedVpcRouting = aws.Bool(v.(bool))
}
if v, ok := d.GetOk("kms_key_id"); ok {
createOpts.KmsKeyId = aws.String(v.(string))
}
if v, ok := d.GetOk("elastic_ip"); ok {
createOpts.ElasticIp = aws.String(v.(string))
}
if v, ok := d.GetOk("iam_roles"); ok {
createOpts.IamRoles = expandStringList(v.(*schema.Set).List())
}
log.Printf("[DEBUG] Redshift Cluster create options: %s", createOpts)
resp, err := conn.CreateCluster(createOpts)
if err != nil {
log.Printf("[ERROR] Error creating Redshift Cluster: %s", err)
return err
}
log.Printf("[DEBUG]: Cluster create response: %s", resp)
d.SetId(*resp.Cluster.ClusterIdentifier)
}
stateConf := &resource.StateChangeConf{
Pending: []string{"creating", "backing-up", "modifying", "restoring"},
Target: []string{"available"},
Refresh: resourceAwsRedshiftClusterStateRefreshFunc(d, meta),
Timeout: 75 * time.Minute,
MinTimeout: 10 * time.Second,
}
_, err := stateConf.WaitForState()
if err != nil {
return fmt.Errorf("[WARN] Error waiting for Redshift Cluster state to be \"available\": %s", err)
}
if _, ok := d.GetOk("enable_logging"); ok {
loggingErr := enableRedshiftClusterLogging(d, conn)
if loggingErr != nil {
log.Printf("[ERROR] Error Enabling Logging on Redshift Cluster: %s", err)
return loggingErr
}
}
return resourceAwsRedshiftClusterRead(d, meta)
}
func resourceAwsRedshiftClusterRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).redshiftconn
log.Printf("[INFO] Reading Redshift Cluster Information: %s", d.Id())
resp, err := conn.DescribeClusters(&redshift.DescribeClustersInput{
ClusterIdentifier: aws.String(d.Id()),
})
if err != nil {
if awsErr, ok := err.(awserr.Error); ok {
if "ClusterNotFound" == awsErr.Code() {
d.SetId("")
log.Printf("[DEBUG] Redshift Cluster (%s) not found", d.Id())
return nil
}
}
log.Printf("[DEBUG] Error describing Redshift Cluster (%s)", d.Id())
return err
}
var rsc *redshift.Cluster
for _, c := range resp.Clusters {
if *c.ClusterIdentifier == d.Id() {
rsc = c
}
}
if rsc == nil {
log.Printf("[WARN] Redshift Cluster (%s) not found", d.Id())
d.SetId("")
return nil
}
log.Printf("[INFO] Reading Redshift Cluster Logging Status: %s", d.Id())
loggingStatus, loggingErr := conn.DescribeLoggingStatus(&redshift.DescribeLoggingStatusInput{
ClusterIdentifier: aws.String(d.Id()),
})
if loggingErr != nil {
return loggingErr
}
d.Set("master_username", rsc.MasterUsername)
d.Set("node_type", rsc.NodeType)
d.Set("allow_version_upgrade", rsc.AllowVersionUpgrade)
d.Set("database_name", rsc.DBName)
d.Set("cluster_identifier", rsc.ClusterIdentifier)
d.Set("cluster_version", rsc.ClusterVersion)
d.Set("cluster_subnet_group_name", rsc.ClusterSubnetGroupName)
d.Set("availability_zone", rsc.AvailabilityZone)
d.Set("encrypted", rsc.Encrypted)
d.Set("enhanced_vpc_routing", rsc.EnhancedVpcRouting)
d.Set("kms_key_id", rsc.KmsKeyId)
d.Set("automated_snapshot_retention_period", rsc.AutomatedSnapshotRetentionPeriod)
d.Set("preferred_maintenance_window", rsc.PreferredMaintenanceWindow)
if rsc.Endpoint != nil && rsc.Endpoint.Address != nil {
endpoint := *rsc.Endpoint.Address
if rsc.Endpoint.Port != nil {
endpoint = fmt.Sprintf("%s:%d", endpoint, *rsc.Endpoint.Port)
}
d.Set("port", rsc.Endpoint.Port)
d.Set("endpoint", endpoint)
}
d.Set("cluster_parameter_group_name", rsc.ClusterParameterGroups[0].ParameterGroupName)
if len(rsc.ClusterNodes) > 1 {
d.Set("cluster_type", "multi-node")
} else {
d.Set("cluster_type", "single-node")
}
d.Set("number_of_nodes", rsc.NumberOfNodes)
d.Set("publicly_accessible", rsc.PubliclyAccessible)
var vpcg []string
for _, g := range rsc.VpcSecurityGroups {
vpcg = append(vpcg, *g.VpcSecurityGroupId)
}
if err := d.Set("vpc_security_group_ids", vpcg); err != nil {
return fmt.Errorf("[DEBUG] Error saving VPC Security Group IDs to state for Redshift Cluster (%s): %s", d.Id(), err)
}
var csg []string
for _, g := range rsc.ClusterSecurityGroups {
csg = append(csg, *g.ClusterSecurityGroupName)
}
if err := d.Set("cluster_security_groups", csg); err != nil {
return fmt.Errorf("[DEBUG] Error saving Cluster Security Group Names to state for Redshift Cluster (%s): %s", d.Id(), err)
}
var iamRoles []string
for _, i := range rsc.IamRoles {
iamRoles = append(iamRoles, *i.IamRoleArn)
}
if err := d.Set("iam_roles", iamRoles); err != nil {
return fmt.Errorf("[DEBUG] Error saving IAM Roles to state for Redshift Cluster (%s): %s", d.Id(), err)
}
d.Set("cluster_public_key", rsc.ClusterPublicKey)
d.Set("cluster_revision_number", rsc.ClusterRevisionNumber)
d.Set("tags", tagsToMapRedshift(rsc.Tags))
d.Set("bucket_name", loggingStatus.BucketName)
d.Set("enable_logging", loggingStatus.LoggingEnabled)
d.Set("s3_key_prefix", loggingStatus.S3KeyPrefix)
return nil
}
func resourceAwsRedshiftClusterUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).redshiftconn
d.Partial(true)
arn, tagErr := buildRedshiftARN(d.Id(), meta.(*AWSClient).partition, meta.(*AWSClient).accountid, meta.(*AWSClient).region)
if tagErr != nil {
return fmt.Errorf("Error building ARN for Redshift Cluster, not updating Tags for cluster %s", d.Id())
} else {
if tagErr := setTagsRedshift(conn, d, arn); tagErr != nil {
return tagErr
} else {
d.SetPartial("tags")
}
}
requestUpdate := false
log.Printf("[INFO] Building Redshift Modify Cluster Options")
req := &redshift.ModifyClusterInput{
ClusterIdentifier: aws.String(d.Id()),
}
if d.HasChange("cluster_type") {
req.ClusterType = aws.String(d.Get("cluster_type").(string))
requestUpdate = true
}
if d.HasChange("node_type") {
req.NodeType = aws.String(d.Get("node_type").(string))
requestUpdate = true
}
if d.HasChange("number_of_nodes") {
if v := d.Get("number_of_nodes").(int); v > 1 {
req.ClusterType = aws.String("multi-node")
req.NumberOfNodes = aws.Int64(int64(d.Get("number_of_nodes").(int)))
} else {
req.ClusterType = aws.String("single-node")
}
req.NodeType = aws.String(d.Get("node_type").(string))
requestUpdate = true
}
if d.HasChange("cluster_security_groups") {
req.ClusterSecurityGroups = expandStringList(d.Get("cluster_security_groups").(*schema.Set).List())
requestUpdate = true
}
if d.HasChange("vpc_security_group_ids") {
req.VpcSecurityGroupIds = expandStringList(d.Get("vpc_security_group_ids").(*schema.Set).List())
requestUpdate = true
}
if d.HasChange("master_password") {
req.MasterUserPassword = aws.String(d.Get("master_password").(string))
requestUpdate = true
}
if d.HasChange("cluster_parameter_group_name") {
req.ClusterParameterGroupName = aws.String(d.Get("cluster_parameter_group_name").(string))
requestUpdate = true
}
if d.HasChange("automated_snapshot_retention_period") {
req.AutomatedSnapshotRetentionPeriod = aws.Int64(int64(d.Get("automated_snapshot_retention_period").(int)))
requestUpdate = true
}
if d.HasChange("preferred_maintenance_window") {
req.PreferredMaintenanceWindow = aws.String(d.Get("preferred_maintenance_window").(string))
requestUpdate = true
}
if d.HasChange("cluster_version") {
req.ClusterVersion = aws.String(d.Get("cluster_version").(string))
requestUpdate = true
}
if d.HasChange("allow_version_upgrade") {
req.AllowVersionUpgrade = aws.Bool(d.Get("allow_version_upgrade").(bool))
requestUpdate = true
}
if d.HasChange("publicly_accessible") {
req.PubliclyAccessible = aws.Bool(d.Get("publicly_accessible").(bool))
requestUpdate = true
}
if d.HasChange("enhanced_vpc_routing") {
req.EnhancedVpcRouting = aws.Bool(d.Get("enhanced_vpc_routing").(bool))
requestUpdate = true
}
if requestUpdate {
log.Printf("[INFO] Modifying Redshift Cluster: %s", d.Id())
log.Printf("[DEBUG] Redshift Cluster Modify options: %s", req)
_, err := conn.ModifyCluster(req)
if err != nil {
return fmt.Errorf("[WARN] Error modifying Redshift Cluster (%s): %s", d.Id(), err)
}
}
if d.HasChange("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)
removeIams := os.Difference(ns).List()
addIams := ns.Difference(os).List()
log.Printf("[INFO] Building Redshift Modify Cluster IAM Role Options")
req := &redshift.ModifyClusterIamRolesInput{
ClusterIdentifier: aws.String(d.Id()),
AddIamRoles: expandStringList(addIams),
RemoveIamRoles: expandStringList(removeIams),
}
log.Printf("[INFO] Modifying Redshift Cluster IAM Roles: %s", d.Id())
log.Printf("[DEBUG] Redshift Cluster Modify IAM Role options: %s", req)
_, err := conn.ModifyClusterIamRoles(req)
if err != nil {
return fmt.Errorf("[WARN] Error modifying Redshift Cluster IAM Roles (%s): %s", d.Id(), err)
}
d.SetPartial("iam_roles")
}
if requestUpdate || d.HasChange("iam_roles") {
stateConf := &resource.StateChangeConf{
Pending: []string{"creating", "deleting", "rebooting", "resizing", "renaming", "modifying"},
Target: []string{"available"},
Refresh: resourceAwsRedshiftClusterStateRefreshFunc(d, meta),
Timeout: 40 * time.Minute,
MinTimeout: 10 * time.Second,
}
// Wait, catching any errors
_, err := stateConf.WaitForState()
if err != nil {
return fmt.Errorf("[WARN] Error Modifying Redshift Cluster (%s): %s", d.Id(), err)
}
}
if d.HasChange("enable_logging") || d.HasChange("bucket_name") || d.HasChange("s3_key_prefix") {
var loggingErr error
if _, ok := d.GetOk("enable_logging"); ok {
log.Printf("[INFO] Enabling Logging for Redshift Cluster %q", d.Id())
loggingErr = enableRedshiftClusterLogging(d, conn)
if loggingErr != nil {
return loggingErr
}
} else {
log.Printf("[INFO] Disabling Logging for Redshift Cluster %q", d.Id())
_, loggingErr = conn.DisableLogging(&redshift.DisableLoggingInput{
ClusterIdentifier: aws.String(d.Id()),
})
if loggingErr != nil {
return loggingErr
}
}
d.SetPartial("enable_logging")
}
d.Partial(false)
return resourceAwsRedshiftClusterRead(d, meta)
}
func enableRedshiftClusterLogging(d *schema.ResourceData, conn *redshift.Redshift) error {
if _, ok := d.GetOk("bucket_name"); !ok {
return fmt.Errorf("bucket_name must be set when enabling logging for Redshift Clusters")
}
params := &redshift.EnableLoggingInput{
ClusterIdentifier: aws.String(d.Id()),
BucketName: aws.String(d.Get("bucket_name").(string)),
}
if v, ok := d.GetOk("s3_key_prefix"); ok {
params.S3KeyPrefix = aws.String(v.(string))
}
_, loggingErr := conn.EnableLogging(params)
if loggingErr != nil {
log.Printf("[ERROR] Error Enabling Logging on Redshift Cluster: %s", loggingErr)
return loggingErr
}
return nil
}
func resourceAwsRedshiftClusterDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).redshiftconn
log.Printf("[DEBUG] Destroying Redshift Cluster (%s)", d.Id())
deleteOpts := redshift.DeleteClusterInput{
ClusterIdentifier: aws.String(d.Id()),
}
skipFinalSnapshot := d.Get("skip_final_snapshot").(bool)
deleteOpts.SkipFinalClusterSnapshot = aws.Bool(skipFinalSnapshot)
if skipFinalSnapshot == false {
if name, present := d.GetOk("final_snapshot_identifier"); present {
deleteOpts.FinalClusterSnapshotIdentifier = aws.String(name.(string))
} else {
return fmt.Errorf("Redshift Cluster Instance FinalSnapshotIdentifier is required when a final snapshot is required")
}
}
log.Printf("[DEBUG] Redshift Cluster delete options: %s", deleteOpts)
_, err := conn.DeleteCluster(&deleteOpts)
if err != nil {
return fmt.Errorf("[ERROR] Error deleting Redshift Cluster (%s): %s", d.Id(), err)
}
stateConf := &resource.StateChangeConf{
Pending: []string{"available", "creating", "deleting", "rebooting", "resizing", "renaming", "final-snapshot"},
Target: []string{"destroyed"},
Refresh: resourceAwsRedshiftClusterStateRefreshFunc(d, meta),
Timeout: 40 * time.Minute,
MinTimeout: 5 * time.Second,
}
// Wait, catching any errors
_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf("[ERROR] Error deleting Redshift Cluster (%s): %s", d.Id(), err)
}
log.Printf("[INFO] Redshift Cluster %s successfully deleted", d.Id())
return nil
}
func resourceAwsRedshiftClusterStateRefreshFunc(d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
conn := meta.(*AWSClient).redshiftconn
log.Printf("[INFO] Reading Redshift Cluster Information: %s", d.Id())
resp, err := conn.DescribeClusters(&redshift.DescribeClustersInput{
ClusterIdentifier: aws.String(d.Id()),
})
if err != nil {
if awsErr, ok := err.(awserr.Error); ok {
if "ClusterNotFound" == awsErr.Code() {
return 42, "destroyed", nil
}
}
log.Printf("[WARN] Error on retrieving Redshift Cluster (%s) when waiting: %s", d.Id(), err)
return nil, "", err
}
var rsc *redshift.Cluster
for _, c := range resp.Clusters {
if *c.ClusterIdentifier == d.Id() {
rsc = c
}
}
if rsc == nil {
return 42, "destroyed", nil
}
if rsc.ClusterStatus != nil {
log.Printf("[DEBUG] Redshift Cluster status (%s): %s", d.Id(), *rsc.ClusterStatus)
}
return rsc, *rsc.ClusterStatus, nil
}
}
func validateRedshiftClusterIdentifier(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"only lowercase alphanumeric characters and hyphens allowed in %q", k))
}
if !regexp.MustCompile(`^[a-z]`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"first character of %q must be a letter", k))
}
if regexp.MustCompile(`--`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"%q cannot contain two consecutive hyphens", k))
}
if regexp.MustCompile(`-$`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"%q cannot end with a hyphen", k))
}
return
}
func validateRedshiftClusterDbName(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if !regexp.MustCompile(`^[0-9a-z_$]+$`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"only lowercase alphanumeric characters, underscores, and dollar signs are allowed in %q", k))
}
if !regexp.MustCompile(`^[a-zA-Z_]`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"first character of %q must be a letter or underscore", k))
}
if len(value) > 64 {
errors = append(errors, fmt.Errorf(
"%q cannot be longer than 64 characters: %q", k, value))
}
if value == "" {
errors = append(errors, fmt.Errorf(
"%q cannot be an empty string", k))
}
return
}
func validateRedshiftClusterFinalSnapshotIdentifier(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"only alphanumeric characters and hyphens allowed in %q", k))
}
if regexp.MustCompile(`--`).MatchString(value) {
errors = append(errors, fmt.Errorf("%q cannot contain two consecutive hyphens", k))
}
if regexp.MustCompile(`-$`).MatchString(value) {
errors = append(errors, fmt.Errorf("%q cannot end in a hyphen", k))
}
if len(value) > 255 {
errors = append(errors, fmt.Errorf("%q cannot be more than 255 characters", k))
}
return
}
func validateRedshiftClusterMasterUsername(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if !regexp.MustCompile(`^\w+$`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"only alphanumeric characters in %q", k))
}
if !regexp.MustCompile(`^[A-Za-z]`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"first character of %q must be a letter", k))
}
if len(value) > 128 {
errors = append(errors, fmt.Errorf("%q cannot be more than 128 characters", k))
}
return
}
func validateRedshiftClusterMasterPassword(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if !regexp.MustCompile(`^.*[a-z].*`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"%q must contain at least one lowercase letter", k))
}
if !regexp.MustCompile(`^.*[A-Z].*`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"%q must contain at least one uppercase letter", k))
}
if !regexp.MustCompile(`^.*[0-9].*`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"%q must contain at least one number", k))
}
if !regexp.MustCompile(`^[^\@\/'" ]*$`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"%q cannot contain [/@\"' ]", k))
}
if len(value) < 8 {
errors = append(errors, fmt.Errorf("%q must be at least 8 characters", k))
}
return
}
func buildRedshiftARN(identifier, partition, accountid, region string) (string, error) {
if partition == "" {
return "", fmt.Errorf("Unable to construct cluster ARN because of missing AWS partition")
}
if accountid == "" {
return "", fmt.Errorf("Unable to construct cluster ARN because of missing AWS Account ID")
}
arn := fmt.Sprintf("arn:%s:redshift:%s:%s:cluster:%s", partition, region, accountid, identifier)
return arn, nil
}