github.com/grange74/terraform@v0.7.0-rc3.0.20160722171430-8c8803864753/builtin/providers/aws/resource_aws_redshift_cluster.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"regexp"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/aws/aws-sdk-go/aws"
    11  	"github.com/aws/aws-sdk-go/aws/awserr"
    12  	"github.com/aws/aws-sdk-go/service/redshift"
    13  	"github.com/hashicorp/terraform/helper/resource"
    14  	"github.com/hashicorp/terraform/helper/schema"
    15  )
    16  
    17  func resourceAwsRedshiftCluster() *schema.Resource {
    18  	return &schema.Resource{
    19  		Create: resourceAwsRedshiftClusterCreate,
    20  		Read:   resourceAwsRedshiftClusterRead,
    21  		Update: resourceAwsRedshiftClusterUpdate,
    22  		Delete: resourceAwsRedshiftClusterDelete,
    23  		Importer: &schema.ResourceImporter{
    24  			State: resourceAwsRedshiftClusterImport,
    25  		},
    26  
    27  		Schema: map[string]*schema.Schema{
    28  			"database_name": &schema.Schema{
    29  				Type:         schema.TypeString,
    30  				Optional:     true,
    31  				Computed:     true,
    32  				ValidateFunc: validateRedshiftClusterDbName,
    33  			},
    34  
    35  			"cluster_identifier": &schema.Schema{
    36  				Type:         schema.TypeString,
    37  				Required:     true,
    38  				ForceNew:     true,
    39  				ValidateFunc: validateRedshiftClusterIdentifier,
    40  			},
    41  			"cluster_type": &schema.Schema{
    42  				Type:     schema.TypeString,
    43  				Optional: true,
    44  				Computed: true,
    45  			},
    46  
    47  			"node_type": &schema.Schema{
    48  				Type:     schema.TypeString,
    49  				Required: true,
    50  			},
    51  
    52  			"master_username": &schema.Schema{
    53  				Type:         schema.TypeString,
    54  				Required:     true,
    55  				ValidateFunc: validateRedshiftClusterMasterUsername,
    56  			},
    57  
    58  			"master_password": &schema.Schema{
    59  				Type:     schema.TypeString,
    60  				Required: true,
    61  			},
    62  
    63  			"cluster_security_groups": &schema.Schema{
    64  				Type:     schema.TypeSet,
    65  				Optional: true,
    66  				Computed: true,
    67  				Elem:     &schema.Schema{Type: schema.TypeString},
    68  				Set:      schema.HashString,
    69  			},
    70  
    71  			"vpc_security_group_ids": &schema.Schema{
    72  				Type:     schema.TypeSet,
    73  				Optional: true,
    74  				Computed: true,
    75  				Elem:     &schema.Schema{Type: schema.TypeString},
    76  				Set:      schema.HashString,
    77  			},
    78  
    79  			"cluster_subnet_group_name": &schema.Schema{
    80  				Type:     schema.TypeString,
    81  				Optional: true,
    82  				ForceNew: true,
    83  				Computed: true,
    84  			},
    85  
    86  			"availability_zone": &schema.Schema{
    87  				Type:     schema.TypeString,
    88  				Optional: true,
    89  				Computed: true,
    90  			},
    91  
    92  			"preferred_maintenance_window": &schema.Schema{
    93  				Type:     schema.TypeString,
    94  				Optional: true,
    95  				Computed: true,
    96  				StateFunc: func(val interface{}) string {
    97  					if val == nil {
    98  						return ""
    99  					}
   100  					return strings.ToLower(val.(string))
   101  				},
   102  			},
   103  
   104  			"cluster_parameter_group_name": &schema.Schema{
   105  				Type:     schema.TypeString,
   106  				Optional: true,
   107  				Computed: true,
   108  			},
   109  
   110  			"automated_snapshot_retention_period": &schema.Schema{
   111  				Type:     schema.TypeInt,
   112  				Optional: true,
   113  				Default:  1,
   114  				ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
   115  					value := v.(int)
   116  					if value > 35 {
   117  						es = append(es, fmt.Errorf(
   118  							"backup retention period cannot be more than 35 days"))
   119  					}
   120  					return
   121  				},
   122  			},
   123  
   124  			"port": &schema.Schema{
   125  				Type:     schema.TypeInt,
   126  				Optional: true,
   127  				Default:  5439,
   128  			},
   129  
   130  			"cluster_version": &schema.Schema{
   131  				Type:     schema.TypeString,
   132  				Optional: true,
   133  				Default:  "1.0",
   134  			},
   135  
   136  			"allow_version_upgrade": &schema.Schema{
   137  				Type:     schema.TypeBool,
   138  				Optional: true,
   139  				Default:  true,
   140  			},
   141  
   142  			"number_of_nodes": &schema.Schema{
   143  				Type:     schema.TypeInt,
   144  				Optional: true,
   145  				Default:  1,
   146  			},
   147  
   148  			"publicly_accessible": &schema.Schema{
   149  				Type:     schema.TypeBool,
   150  				Optional: true,
   151  				Default:  true,
   152  			},
   153  
   154  			"encrypted": &schema.Schema{
   155  				Type:     schema.TypeBool,
   156  				Optional: true,
   157  				Computed: true,
   158  			},
   159  
   160  			"kms_key_id": &schema.Schema{
   161  				Type:     schema.TypeString,
   162  				Optional: true,
   163  				Computed: true,
   164  				ForceNew: true,
   165  			},
   166  
   167  			"elastic_ip": &schema.Schema{
   168  				Type:     schema.TypeString,
   169  				Optional: true,
   170  			},
   171  
   172  			"final_snapshot_identifier": &schema.Schema{
   173  				Type:         schema.TypeString,
   174  				Optional:     true,
   175  				ValidateFunc: validateRedshiftClusterFinalSnapshotIdentifier,
   176  			},
   177  
   178  			"skip_final_snapshot": &schema.Schema{
   179  				Type:     schema.TypeBool,
   180  				Optional: true,
   181  				Default:  true,
   182  			},
   183  
   184  			"endpoint": &schema.Schema{
   185  				Type:     schema.TypeString,
   186  				Optional: true,
   187  				Computed: true,
   188  			},
   189  
   190  			"cluster_public_key": &schema.Schema{
   191  				Type:     schema.TypeString,
   192  				Optional: true,
   193  				Computed: true,
   194  			},
   195  
   196  			"cluster_revision_number": &schema.Schema{
   197  				Type:     schema.TypeString,
   198  				Optional: true,
   199  				Computed: true,
   200  			},
   201  
   202  			"iam_roles": &schema.Schema{
   203  				Type:     schema.TypeSet,
   204  				Optional: true,
   205  				Computed: true,
   206  				Elem:     &schema.Schema{Type: schema.TypeString},
   207  				Set:      schema.HashString,
   208  			},
   209  
   210  			"tags": tagsSchema(),
   211  		},
   212  	}
   213  }
   214  
   215  func resourceAwsRedshiftClusterImport(
   216  	d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
   217  	// Neither skip_final_snapshot nor final_snapshot_identifier can be fetched
   218  	// from any API call, so we need to default skip_final_snapshot to true so
   219  	// that final_snapshot_identifier is not required
   220  	d.Set("skip_final_snapshot", true)
   221  	return []*schema.ResourceData{d}, nil
   222  }
   223  
   224  func resourceAwsRedshiftClusterCreate(d *schema.ResourceData, meta interface{}) error {
   225  	conn := meta.(*AWSClient).redshiftconn
   226  
   227  	log.Printf("[INFO] Building Redshift Cluster Options")
   228  	tags := tagsFromMapRedshift(d.Get("tags").(map[string]interface{}))
   229  	createOpts := &redshift.CreateClusterInput{
   230  		ClusterIdentifier:                aws.String(d.Get("cluster_identifier").(string)),
   231  		Port:                             aws.Int64(int64(d.Get("port").(int))),
   232  		MasterUserPassword:               aws.String(d.Get("master_password").(string)),
   233  		MasterUsername:                   aws.String(d.Get("master_username").(string)),
   234  		ClusterVersion:                   aws.String(d.Get("cluster_version").(string)),
   235  		NodeType:                         aws.String(d.Get("node_type").(string)),
   236  		DBName:                           aws.String(d.Get("database_name").(string)),
   237  		AllowVersionUpgrade:              aws.Bool(d.Get("allow_version_upgrade").(bool)),
   238  		PubliclyAccessible:               aws.Bool(d.Get("publicly_accessible").(bool)),
   239  		AutomatedSnapshotRetentionPeriod: aws.Int64(int64(d.Get("automated_snapshot_retention_period").(int))),
   240  		Tags: tags,
   241  	}
   242  
   243  	if v := d.Get("number_of_nodes").(int); v > 1 {
   244  		createOpts.ClusterType = aws.String("multi-node")
   245  		createOpts.NumberOfNodes = aws.Int64(int64(d.Get("number_of_nodes").(int)))
   246  	} else {
   247  		createOpts.ClusterType = aws.String("single-node")
   248  	}
   249  
   250  	if v := d.Get("cluster_security_groups").(*schema.Set); v.Len() > 0 {
   251  		createOpts.ClusterSecurityGroups = expandStringList(v.List())
   252  	}
   253  
   254  	if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 {
   255  		createOpts.VpcSecurityGroupIds = expandStringList(v.List())
   256  	}
   257  
   258  	if v, ok := d.GetOk("cluster_subnet_group_name"); ok {
   259  		createOpts.ClusterSubnetGroupName = aws.String(v.(string))
   260  	}
   261  
   262  	if v, ok := d.GetOk("availability_zone"); ok {
   263  		createOpts.AvailabilityZone = aws.String(v.(string))
   264  	}
   265  
   266  	if v, ok := d.GetOk("preferred_maintenance_window"); ok {
   267  		createOpts.PreferredMaintenanceWindow = aws.String(v.(string))
   268  	}
   269  
   270  	if v, ok := d.GetOk("cluster_parameter_group_name"); ok {
   271  		createOpts.ClusterParameterGroupName = aws.String(v.(string))
   272  	}
   273  
   274  	if v, ok := d.GetOk("encrypted"); ok {
   275  		createOpts.Encrypted = aws.Bool(v.(bool))
   276  	}
   277  
   278  	if v, ok := d.GetOk("kms_key_id"); ok {
   279  		createOpts.KmsKeyId = aws.String(v.(string))
   280  	}
   281  
   282  	if v, ok := d.GetOk("elastic_ip"); ok {
   283  		createOpts.ElasticIp = aws.String(v.(string))
   284  	}
   285  
   286  	if v, ok := d.GetOk("iam_roles"); ok {
   287  		createOpts.IamRoles = expandStringList(v.(*schema.Set).List())
   288  	}
   289  
   290  	log.Printf("[DEBUG] Redshift Cluster create options: %s", createOpts)
   291  	resp, err := conn.CreateCluster(createOpts)
   292  	if err != nil {
   293  		log.Printf("[ERROR] Error creating Redshift Cluster: %s", err)
   294  		return err
   295  	}
   296  
   297  	log.Printf("[DEBUG]: Cluster create response: %s", resp)
   298  	d.SetId(*resp.Cluster.ClusterIdentifier)
   299  
   300  	stateConf := &resource.StateChangeConf{
   301  		Pending:    []string{"creating", "backing-up", "modifying"},
   302  		Target:     []string{"available"},
   303  		Refresh:    resourceAwsRedshiftClusterStateRefreshFunc(d, meta),
   304  		Timeout:    40 * time.Minute,
   305  		MinTimeout: 10 * time.Second,
   306  	}
   307  
   308  	_, err = stateConf.WaitForState()
   309  	if err != nil {
   310  		return fmt.Errorf("[WARN] Error waiting for Redshift Cluster state to be \"available\": %s", err)
   311  	}
   312  
   313  	return resourceAwsRedshiftClusterRead(d, meta)
   314  }
   315  
   316  func resourceAwsRedshiftClusterRead(d *schema.ResourceData, meta interface{}) error {
   317  	conn := meta.(*AWSClient).redshiftconn
   318  
   319  	log.Printf("[INFO] Reading Redshift Cluster Information: %s", d.Id())
   320  	resp, err := conn.DescribeClusters(&redshift.DescribeClustersInput{
   321  		ClusterIdentifier: aws.String(d.Id()),
   322  	})
   323  
   324  	if err != nil {
   325  		if awsErr, ok := err.(awserr.Error); ok {
   326  			if "ClusterNotFound" == awsErr.Code() {
   327  				d.SetId("")
   328  				log.Printf("[DEBUG] Redshift Cluster (%s) not found", d.Id())
   329  				return nil
   330  			}
   331  		}
   332  		log.Printf("[DEBUG] Error describing Redshift Cluster (%s)", d.Id())
   333  		return err
   334  	}
   335  
   336  	var rsc *redshift.Cluster
   337  	for _, c := range resp.Clusters {
   338  		if *c.ClusterIdentifier == d.Id() {
   339  			rsc = c
   340  		}
   341  	}
   342  
   343  	if rsc == nil {
   344  		log.Printf("[WARN] Redshift Cluster (%s) not found", d.Id())
   345  		d.SetId("")
   346  		return nil
   347  	}
   348  
   349  	d.Set("master_username", rsc.MasterUsername)
   350  	d.Set("node_type", rsc.NodeType)
   351  	d.Set("allow_version_upgrade", rsc.AllowVersionUpgrade)
   352  	d.Set("database_name", rsc.DBName)
   353  	d.Set("cluster_identifier", rsc.ClusterIdentifier)
   354  	d.Set("cluster_version", rsc.ClusterVersion)
   355  
   356  	d.Set("cluster_subnet_group_name", rsc.ClusterSubnetGroupName)
   357  	d.Set("availability_zone", rsc.AvailabilityZone)
   358  	d.Set("encrypted", rsc.Encrypted)
   359  	d.Set("kms_key_id", rsc.KmsKeyId)
   360  	d.Set("automated_snapshot_retention_period", rsc.AutomatedSnapshotRetentionPeriod)
   361  	d.Set("preferred_maintenance_window", rsc.PreferredMaintenanceWindow)
   362  	if rsc.Endpoint != nil && rsc.Endpoint.Address != nil {
   363  		endpoint := *rsc.Endpoint.Address
   364  		if rsc.Endpoint.Port != nil {
   365  			endpoint = fmt.Sprintf("%s:%d", endpoint, *rsc.Endpoint.Port)
   366  		}
   367  		d.Set("port", rsc.Endpoint.Port)
   368  		d.Set("endpoint", endpoint)
   369  	}
   370  	d.Set("cluster_parameter_group_name", rsc.ClusterParameterGroups[0].ParameterGroupName)
   371  	if len(rsc.ClusterNodes) > 1 {
   372  		d.Set("cluster_type", "multi-node")
   373  	} else {
   374  		d.Set("cluster_type", "single-node")
   375  	}
   376  	d.Set("number_of_nodes", rsc.NumberOfNodes)
   377  	d.Set("publicly_accessible", rsc.PubliclyAccessible)
   378  
   379  	var vpcg []string
   380  	for _, g := range rsc.VpcSecurityGroups {
   381  		vpcg = append(vpcg, *g.VpcSecurityGroupId)
   382  	}
   383  	if err := d.Set("vpc_security_group_ids", vpcg); err != nil {
   384  		return fmt.Errorf("[DEBUG] Error saving VPC Security Group IDs to state for Redshift Cluster (%s): %s", d.Id(), err)
   385  	}
   386  
   387  	var csg []string
   388  	for _, g := range rsc.ClusterSecurityGroups {
   389  		csg = append(csg, *g.ClusterSecurityGroupName)
   390  	}
   391  	if err := d.Set("cluster_security_groups", csg); err != nil {
   392  		return fmt.Errorf("[DEBUG] Error saving Cluster Security Group Names to state for Redshift Cluster (%s): %s", d.Id(), err)
   393  	}
   394  
   395  	var iamRoles []string
   396  	for _, i := range rsc.IamRoles {
   397  		iamRoles = append(iamRoles, *i.IamRoleArn)
   398  	}
   399  	if err := d.Set("iam_roles", iamRoles); err != nil {
   400  		return fmt.Errorf("[DEBUG] Error saving IAM Roles to state for Redshift Cluster (%s): %s", d.Id(), err)
   401  	}
   402  
   403  	d.Set("cluster_public_key", rsc.ClusterPublicKey)
   404  	d.Set("cluster_revision_number", rsc.ClusterRevisionNumber)
   405  	d.Set("tags", tagsToMapRedshift(rsc.Tags))
   406  
   407  	return nil
   408  }
   409  
   410  func resourceAwsRedshiftClusterUpdate(d *schema.ResourceData, meta interface{}) error {
   411  	conn := meta.(*AWSClient).redshiftconn
   412  	d.Partial(true)
   413  
   414  	arn, tagErr := buildRedshiftARN(d.Id(), meta.(*AWSClient).accountid, meta.(*AWSClient).region)
   415  	if tagErr != nil {
   416  		return fmt.Errorf("Error building ARN for Redshift Cluster, not updating Tags for cluster %s", d.Id())
   417  	} else {
   418  		if tagErr := setTagsRedshift(conn, d, arn); tagErr != nil {
   419  			return tagErr
   420  		} else {
   421  			d.SetPartial("tags")
   422  		}
   423  	}
   424  
   425  	requestUpdate := false
   426  	log.Printf("[INFO] Building Redshift Modify Cluster Options")
   427  	req := &redshift.ModifyClusterInput{
   428  		ClusterIdentifier: aws.String(d.Id()),
   429  	}
   430  
   431  	if d.HasChange("cluster_type") {
   432  		req.ClusterType = aws.String(d.Get("cluster_type").(string))
   433  		requestUpdate = true
   434  	}
   435  
   436  	if d.HasChange("node_type") {
   437  		req.NodeType = aws.String(d.Get("node_type").(string))
   438  		requestUpdate = true
   439  	}
   440  
   441  	if d.HasChange("number_of_nodes") {
   442  		if v := d.Get("number_of_nodes").(int); v > 1 {
   443  			req.ClusterType = aws.String("multi-node")
   444  			req.NumberOfNodes = aws.Int64(int64(d.Get("number_of_nodes").(int)))
   445  		} else {
   446  			req.ClusterType = aws.String("single-node")
   447  		}
   448  
   449  		req.NodeType = aws.String(d.Get("node_type").(string))
   450  		requestUpdate = true
   451  	}
   452  
   453  	if d.HasChange("cluster_security_groups") {
   454  		req.ClusterSecurityGroups = expandStringList(d.Get("cluster_security_groups").(*schema.Set).List())
   455  		requestUpdate = true
   456  	}
   457  
   458  	if d.HasChange("vpc_security_group_ips") {
   459  		req.VpcSecurityGroupIds = expandStringList(d.Get("vpc_security_group_ips").(*schema.Set).List())
   460  		requestUpdate = true
   461  	}
   462  
   463  	if d.HasChange("master_password") {
   464  		req.MasterUserPassword = aws.String(d.Get("master_password").(string))
   465  		requestUpdate = true
   466  	}
   467  
   468  	if d.HasChange("cluster_parameter_group_name") {
   469  		req.ClusterParameterGroupName = aws.String(d.Get("cluster_parameter_group_name").(string))
   470  		requestUpdate = true
   471  	}
   472  
   473  	if d.HasChange("automated_snapshot_retention_period") {
   474  		req.AutomatedSnapshotRetentionPeriod = aws.Int64(int64(d.Get("automated_snapshot_retention_period").(int)))
   475  		requestUpdate = true
   476  	}
   477  
   478  	if d.HasChange("preferred_maintenance_window") {
   479  		req.PreferredMaintenanceWindow = aws.String(d.Get("preferred_maintenance_window").(string))
   480  		requestUpdate = true
   481  	}
   482  
   483  	if d.HasChange("cluster_version") {
   484  		req.ClusterVersion = aws.String(d.Get("cluster_version").(string))
   485  		requestUpdate = true
   486  	}
   487  
   488  	if d.HasChange("allow_version_upgrade") {
   489  		req.AllowVersionUpgrade = aws.Bool(d.Get("allow_version_upgrade").(bool))
   490  		requestUpdate = true
   491  	}
   492  
   493  	if d.HasChange("publicly_accessible") {
   494  		req.PubliclyAccessible = aws.Bool(d.Get("publicly_accessible").(bool))
   495  		requestUpdate = true
   496  	}
   497  
   498  	if requestUpdate {
   499  		log.Printf("[INFO] Modifying Redshift Cluster: %s", d.Id())
   500  		log.Printf("[DEBUG] Redshift Cluster Modify options: %s", req)
   501  		_, err := conn.ModifyCluster(req)
   502  		if err != nil {
   503  			return fmt.Errorf("[WARN] Error modifying Redshift Cluster (%s): %s", d.Id(), err)
   504  		}
   505  	}
   506  
   507  	if d.HasChange("iam_roles") {
   508  		o, n := d.GetChange("iam_roles")
   509  		if o == nil {
   510  			o = new(schema.Set)
   511  		}
   512  		if n == nil {
   513  			n = new(schema.Set)
   514  		}
   515  
   516  		os := o.(*schema.Set)
   517  		ns := n.(*schema.Set)
   518  
   519  		removeIams := os.Difference(ns).List()
   520  		addIams := ns.Difference(os).List()
   521  
   522  		log.Printf("[INFO] Building Redshift Modify Cluster IAM Role Options")
   523  		req := &redshift.ModifyClusterIamRolesInput{
   524  			ClusterIdentifier: aws.String(d.Id()),
   525  			AddIamRoles:       expandStringList(addIams),
   526  			RemoveIamRoles:    expandStringList(removeIams),
   527  		}
   528  
   529  		log.Printf("[INFO] Modifying Redshift Cluster IAM Roles: %s", d.Id())
   530  		log.Printf("[DEBUG] Redshift Cluster Modify IAM Role options: %s", req)
   531  		_, err := conn.ModifyClusterIamRoles(req)
   532  		if err != nil {
   533  			return fmt.Errorf("[WARN] Error modifying Redshift Cluster IAM Roles (%s): %s", d.Id(), err)
   534  		}
   535  
   536  		d.SetPartial("iam_roles")
   537  	}
   538  
   539  	if requestUpdate || d.HasChange("iam_roles") {
   540  
   541  		stateConf := &resource.StateChangeConf{
   542  			Pending:    []string{"creating", "deleting", "rebooting", "resizing", "renaming", "modifying"},
   543  			Target:     []string{"available"},
   544  			Refresh:    resourceAwsRedshiftClusterStateRefreshFunc(d, meta),
   545  			Timeout:    40 * time.Minute,
   546  			MinTimeout: 10 * time.Second,
   547  		}
   548  
   549  		// Wait, catching any errors
   550  		_, err := stateConf.WaitForState()
   551  		if err != nil {
   552  			return fmt.Errorf("[WARN] Error Modifying Redshift Cluster (%s): %s", d.Id(), err)
   553  		}
   554  	}
   555  
   556  	d.Partial(false)
   557  
   558  	return resourceAwsRedshiftClusterRead(d, meta)
   559  }
   560  
   561  func resourceAwsRedshiftClusterDelete(d *schema.ResourceData, meta interface{}) error {
   562  	conn := meta.(*AWSClient).redshiftconn
   563  	log.Printf("[DEBUG] Destroying Redshift Cluster (%s)", d.Id())
   564  
   565  	deleteOpts := redshift.DeleteClusterInput{
   566  		ClusterIdentifier: aws.String(d.Id()),
   567  	}
   568  
   569  	skipFinalSnapshot := d.Get("skip_final_snapshot").(bool)
   570  	deleteOpts.SkipFinalClusterSnapshot = aws.Bool(skipFinalSnapshot)
   571  
   572  	if skipFinalSnapshot == false {
   573  		if name, present := d.GetOk("final_snapshot_identifier"); present {
   574  			deleteOpts.FinalClusterSnapshotIdentifier = aws.String(name.(string))
   575  		} else {
   576  			return fmt.Errorf("Redshift Cluster Instance FinalSnapshotIdentifier is required when a final snapshot is required")
   577  		}
   578  	}
   579  
   580  	log.Printf("[DEBUG] Redshift Cluster delete options: %s", deleteOpts)
   581  	_, err := conn.DeleteCluster(&deleteOpts)
   582  	if err != nil {
   583  		return fmt.Errorf("[ERROR] Error deleting Redshift Cluster (%s): %s", d.Id(), err)
   584  	}
   585  
   586  	stateConf := &resource.StateChangeConf{
   587  		Pending:    []string{"available", "creating", "deleting", "rebooting", "resizing", "renaming"},
   588  		Target:     []string{"destroyed"},
   589  		Refresh:    resourceAwsRedshiftClusterStateRefreshFunc(d, meta),
   590  		Timeout:    40 * time.Minute,
   591  		MinTimeout: 5 * time.Second,
   592  	}
   593  
   594  	// Wait, catching any errors
   595  	_, err = stateConf.WaitForState()
   596  	if err != nil {
   597  		return fmt.Errorf("[ERROR] Error deleting Redshift Cluster (%s): %s", d.Id(), err)
   598  	}
   599  
   600  	log.Printf("[INFO] Redshift Cluster %s successfully deleted", d.Id())
   601  
   602  	return nil
   603  }
   604  
   605  func resourceAwsRedshiftClusterStateRefreshFunc(d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc {
   606  	return func() (interface{}, string, error) {
   607  		conn := meta.(*AWSClient).redshiftconn
   608  
   609  		log.Printf("[INFO] Reading Redshift Cluster Information: %s", d.Id())
   610  		resp, err := conn.DescribeClusters(&redshift.DescribeClustersInput{
   611  			ClusterIdentifier: aws.String(d.Id()),
   612  		})
   613  
   614  		if err != nil {
   615  			if awsErr, ok := err.(awserr.Error); ok {
   616  				if "ClusterNotFound" == awsErr.Code() {
   617  					return 42, "destroyed", nil
   618  				}
   619  			}
   620  			log.Printf("[WARN] Error on retrieving Redshift Cluster (%s) when waiting: %s", d.Id(), err)
   621  			return nil, "", err
   622  		}
   623  
   624  		var rsc *redshift.Cluster
   625  
   626  		for _, c := range resp.Clusters {
   627  			if *c.ClusterIdentifier == d.Id() {
   628  				rsc = c
   629  			}
   630  		}
   631  
   632  		if rsc == nil {
   633  			return 42, "destroyed", nil
   634  		}
   635  
   636  		if rsc.ClusterStatus != nil {
   637  			log.Printf("[DEBUG] Redshift Cluster status (%s): %s", d.Id(), *rsc.ClusterStatus)
   638  		}
   639  
   640  		return rsc, *rsc.ClusterStatus, nil
   641  	}
   642  }
   643  
   644  func validateRedshiftClusterIdentifier(v interface{}, k string) (ws []string, errors []error) {
   645  	value := v.(string)
   646  	if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) {
   647  		errors = append(errors, fmt.Errorf(
   648  			"only lowercase alphanumeric characters and hyphens allowed in %q", k))
   649  	}
   650  	if !regexp.MustCompile(`^[a-z]`).MatchString(value) {
   651  		errors = append(errors, fmt.Errorf(
   652  			"first character of %q must be a letter", k))
   653  	}
   654  	if regexp.MustCompile(`--`).MatchString(value) {
   655  		errors = append(errors, fmt.Errorf(
   656  			"%q cannot contain two consecutive hyphens", k))
   657  	}
   658  	if regexp.MustCompile(`-$`).MatchString(value) {
   659  		errors = append(errors, fmt.Errorf(
   660  			"%q cannot end with a hyphen", k))
   661  	}
   662  	return
   663  }
   664  
   665  func validateRedshiftClusterDbName(v interface{}, k string) (ws []string, errors []error) {
   666  	value := v.(string)
   667  	if !regexp.MustCompile(`^[a-z]+$`).MatchString(value) {
   668  		errors = append(errors, fmt.Errorf(
   669  			"only lowercase letters characters allowed in %q", k))
   670  	}
   671  	if len(value) > 64 {
   672  		errors = append(errors, fmt.Errorf(
   673  			"%q cannot be longer than 64 characters: %q", k, value))
   674  	}
   675  	if value == "" {
   676  		errors = append(errors, fmt.Errorf(
   677  			"%q cannot be an empty string", k))
   678  	}
   679  
   680  	return
   681  }
   682  
   683  func validateRedshiftClusterFinalSnapshotIdentifier(v interface{}, k string) (ws []string, errors []error) {
   684  	value := v.(string)
   685  	if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) {
   686  		errors = append(errors, fmt.Errorf(
   687  			"only alphanumeric characters and hyphens allowed in %q", k))
   688  	}
   689  	if regexp.MustCompile(`--`).MatchString(value) {
   690  		errors = append(errors, fmt.Errorf("%q cannot contain two consecutive hyphens", k))
   691  	}
   692  	if regexp.MustCompile(`-$`).MatchString(value) {
   693  		errors = append(errors, fmt.Errorf("%q cannot end in a hyphen", k))
   694  	}
   695  	if len(value) > 255 {
   696  		errors = append(errors, fmt.Errorf("%q cannot be more than 255 characters", k))
   697  	}
   698  	return
   699  }
   700  
   701  func validateRedshiftClusterMasterUsername(v interface{}, k string) (ws []string, errors []error) {
   702  	value := v.(string)
   703  	if !regexp.MustCompile(`^\w+$`).MatchString(value) {
   704  		errors = append(errors, fmt.Errorf(
   705  			"only alphanumeric characters in %q", k))
   706  	}
   707  	if !regexp.MustCompile(`^[A-Za-z]`).MatchString(value) {
   708  		errors = append(errors, fmt.Errorf(
   709  			"first character of %q must be a letter", k))
   710  	}
   711  	if len(value) > 128 {
   712  		errors = append(errors, fmt.Errorf("%q cannot be more than 128 characters", k))
   713  	}
   714  	return
   715  }
   716  
   717  func buildRedshiftARN(identifier, accountid, region string) (string, error) {
   718  	if accountid == "" {
   719  		return "", fmt.Errorf("Unable to construct cluster ARN because of missing AWS Account ID")
   720  	}
   721  	arn := fmt.Sprintf("arn:aws:redshift:%s:%s:cluster:%s", region, accountid, identifier)
   722  	return arn, nil
   723  
   724  }