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