github.com/recobe182/terraform@v0.8.5-0.20170117231232-49ab22a935b7/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": {
    29  				Type:         schema.TypeString,
    30  				Optional:     true,
    31  				Computed:     true,
    32  				ValidateFunc: validateRedshiftClusterDbName,
    33  			},
    34  
    35  			"cluster_identifier": {
    36  				Type:         schema.TypeString,
    37  				Required:     true,
    38  				ForceNew:     true,
    39  				ValidateFunc: validateRedshiftClusterIdentifier,
    40  			},
    41  			"cluster_type": {
    42  				Type:     schema.TypeString,
    43  				Optional: true,
    44  				Computed: true,
    45  			},
    46  
    47  			"node_type": {
    48  				Type:     schema.TypeString,
    49  				Required: true,
    50  			},
    51  
    52  			"master_username": {
    53  				Type:         schema.TypeString,
    54  				Optional:     true,
    55  				ValidateFunc: validateRedshiftClusterMasterUsername,
    56  			},
    57  
    58  			"master_password": {
    59  				Type:         schema.TypeString,
    60  				Optional:     true,
    61  				Sensitive:    true,
    62  				ValidateFunc: validateRedshiftClusterMasterPassword,
    63  			},
    64  
    65  			"cluster_security_groups": {
    66  				Type:     schema.TypeSet,
    67  				Optional: true,
    68  				Computed: true,
    69  				Elem:     &schema.Schema{Type: schema.TypeString},
    70  				Set:      schema.HashString,
    71  			},
    72  
    73  			"vpc_security_group_ids": {
    74  				Type:     schema.TypeSet,
    75  				Optional: true,
    76  				Computed: true,
    77  				Elem:     &schema.Schema{Type: schema.TypeString},
    78  				Set:      schema.HashString,
    79  			},
    80  
    81  			"cluster_subnet_group_name": {
    82  				Type:     schema.TypeString,
    83  				Optional: true,
    84  				ForceNew: true,
    85  				Computed: true,
    86  			},
    87  
    88  			"availability_zone": {
    89  				Type:     schema.TypeString,
    90  				Optional: true,
    91  				Computed: true,
    92  			},
    93  
    94  			"preferred_maintenance_window": {
    95  				Type:     schema.TypeString,
    96  				Optional: true,
    97  				Computed: true,
    98  				StateFunc: func(val interface{}) string {
    99  					if val == nil {
   100  						return ""
   101  					}
   102  					return strings.ToLower(val.(string))
   103  				},
   104  				ValidateFunc: validateOnceAWeekWindowFormat,
   105  			},
   106  
   107  			"cluster_parameter_group_name": {
   108  				Type:     schema.TypeString,
   109  				Optional: true,
   110  				Computed: true,
   111  			},
   112  
   113  			"automated_snapshot_retention_period": {
   114  				Type:     schema.TypeInt,
   115  				Optional: true,
   116  				Default:  1,
   117  				ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
   118  					value := v.(int)
   119  					if value > 35 {
   120  						es = append(es, fmt.Errorf(
   121  							"backup retention period cannot be more than 35 days"))
   122  					}
   123  					return
   124  				},
   125  			},
   126  
   127  			"port": {
   128  				Type:     schema.TypeInt,
   129  				Optional: true,
   130  				Default:  5439,
   131  			},
   132  
   133  			"cluster_version": {
   134  				Type:     schema.TypeString,
   135  				Optional: true,
   136  				Default:  "1.0",
   137  			},
   138  
   139  			"allow_version_upgrade": {
   140  				Type:     schema.TypeBool,
   141  				Optional: true,
   142  				Default:  true,
   143  			},
   144  
   145  			"number_of_nodes": {
   146  				Type:     schema.TypeInt,
   147  				Optional: true,
   148  				Default:  1,
   149  			},
   150  
   151  			"publicly_accessible": {
   152  				Type:     schema.TypeBool,
   153  				Optional: true,
   154  				Default:  true,
   155  			},
   156  
   157  			"encrypted": {
   158  				Type:     schema.TypeBool,
   159  				Optional: true,
   160  				Computed: true,
   161  			},
   162  
   163  			"enhanced_vpc_routing": {
   164  				Type:     schema.TypeBool,
   165  				Optional: true,
   166  				Computed: true,
   167  			},
   168  
   169  			"kms_key_id": {
   170  				Type:         schema.TypeString,
   171  				Optional:     true,
   172  				Computed:     true,
   173  				ForceNew:     true,
   174  				ValidateFunc: validateArn,
   175  			},
   176  
   177  			"elastic_ip": {
   178  				Type:     schema.TypeString,
   179  				Optional: true,
   180  			},
   181  
   182  			"final_snapshot_identifier": {
   183  				Type:         schema.TypeString,
   184  				Optional:     true,
   185  				ValidateFunc: validateRedshiftClusterFinalSnapshotIdentifier,
   186  			},
   187  
   188  			"skip_final_snapshot": {
   189  				Type:     schema.TypeBool,
   190  				Optional: true,
   191  				Default:  true,
   192  			},
   193  
   194  			"endpoint": {
   195  				Type:     schema.TypeString,
   196  				Optional: true,
   197  				Computed: true,
   198  			},
   199  
   200  			"cluster_public_key": {
   201  				Type:     schema.TypeString,
   202  				Optional: true,
   203  				Computed: true,
   204  			},
   205  
   206  			"cluster_revision_number": {
   207  				Type:     schema.TypeString,
   208  				Optional: true,
   209  				Computed: true,
   210  			},
   211  
   212  			"iam_roles": {
   213  				Type:     schema.TypeSet,
   214  				Optional: true,
   215  				Computed: true,
   216  				Elem:     &schema.Schema{Type: schema.TypeString},
   217  				Set:      schema.HashString,
   218  			},
   219  
   220  			"enable_logging": {
   221  				Type:     schema.TypeBool,
   222  				Optional: true,
   223  				Default:  false,
   224  			},
   225  
   226  			"bucket_name": {
   227  				Type:     schema.TypeString,
   228  				Optional: true,
   229  				Computed: true,
   230  			},
   231  
   232  			"s3_key_prefix": {
   233  				Type:     schema.TypeString,
   234  				Optional: true,
   235  				Computed: true,
   236  			},
   237  
   238  			"snapshot_identifier": {
   239  				Type:     schema.TypeString,
   240  				Optional: true,
   241  			},
   242  
   243  			"snapshot_cluster_identifier": {
   244  				Type:     schema.TypeString,
   245  				Optional: true,
   246  			},
   247  
   248  			"tags": tagsSchema(),
   249  		},
   250  	}
   251  }
   252  
   253  func resourceAwsRedshiftClusterImport(
   254  	d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
   255  	// Neither skip_final_snapshot nor final_snapshot_identifier can be fetched
   256  	// from any API call, so we need to default skip_final_snapshot to true so
   257  	// that final_snapshot_identifier is not required
   258  	d.Set("skip_final_snapshot", true)
   259  	return []*schema.ResourceData{d}, nil
   260  }
   261  
   262  func resourceAwsRedshiftClusterCreate(d *schema.ResourceData, meta interface{}) error {
   263  	conn := meta.(*AWSClient).redshiftconn
   264  	tags := tagsFromMapRedshift(d.Get("tags").(map[string]interface{}))
   265  
   266  	if v, ok := d.GetOk("snapshot_identifier"); ok {
   267  		restoreOpts := &redshift.RestoreFromClusterSnapshotInput{
   268  			ClusterIdentifier:                aws.String(d.Get("cluster_identifier").(string)),
   269  			SnapshotIdentifier:               aws.String(v.(string)),
   270  			Port:                             aws.Int64(int64(d.Get("port").(int))),
   271  			AllowVersionUpgrade:              aws.Bool(d.Get("allow_version_upgrade").(bool)),
   272  			NodeType:                         aws.String(d.Get("node_type").(string)),
   273  			PubliclyAccessible:               aws.Bool(d.Get("publicly_accessible").(bool)),
   274  			AutomatedSnapshotRetentionPeriod: aws.Int64(int64(d.Get("automated_snapshot_retention_period").(int))),
   275  		}
   276  
   277  		if v, ok := d.GetOk("snapshot_cluster_identifier"); ok {
   278  			restoreOpts.SnapshotClusterIdentifier = aws.String(v.(string))
   279  		}
   280  
   281  		if v, ok := d.GetOk("availability_zone"); ok {
   282  			restoreOpts.AvailabilityZone = aws.String(v.(string))
   283  		}
   284  
   285  		if v, ok := d.GetOk("cluster_subnet_group_name"); ok {
   286  			restoreOpts.ClusterSubnetGroupName = aws.String(v.(string))
   287  		}
   288  
   289  		if v, ok := d.GetOk("cluster_parameter_group_name"); ok {
   290  			restoreOpts.ClusterParameterGroupName = aws.String(v.(string))
   291  		}
   292  
   293  		if v := d.Get("cluster_security_groups").(*schema.Set); v.Len() > 0 {
   294  			restoreOpts.ClusterSecurityGroups = expandStringList(v.List())
   295  		}
   296  
   297  		if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 {
   298  			restoreOpts.VpcSecurityGroupIds = expandStringList(v.List())
   299  		}
   300  
   301  		if v, ok := d.GetOk("preferred_maintenance_window"); ok {
   302  			restoreOpts.PreferredMaintenanceWindow = aws.String(v.(string))
   303  		}
   304  
   305  		if v, ok := d.GetOk("kms_key_id"); ok {
   306  			restoreOpts.KmsKeyId = aws.String(v.(string))
   307  		}
   308  
   309  		if v, ok := d.GetOk("elastic_ip"); ok {
   310  			restoreOpts.ElasticIp = aws.String(v.(string))
   311  		}
   312  
   313  		if v, ok := d.GetOk("enhanced_vpc_routing"); ok {
   314  			restoreOpts.EnhancedVpcRouting = aws.Bool(v.(bool))
   315  		}
   316  
   317  		if v, ok := d.GetOk("iam_roles"); ok {
   318  			restoreOpts.IamRoles = expandStringList(v.(*schema.Set).List())
   319  		}
   320  
   321  		log.Printf("[DEBUG] Redshift Cluster restore cluster options: %s", restoreOpts)
   322  
   323  		resp, err := conn.RestoreFromClusterSnapshot(restoreOpts)
   324  		if err != nil {
   325  			log.Printf("[ERROR] Error Restoring Redshift Cluster from Snapshot: %s", err)
   326  			return err
   327  		}
   328  
   329  		d.SetId(*resp.Cluster.ClusterIdentifier)
   330  
   331  	} else {
   332  		if _, ok := d.GetOk("master_password"); !ok {
   333  			return fmt.Errorf(`provider.aws: aws_redshift_cluster: %s: "master_password": required field is not set`, d.Get("cluster_identifier").(string))
   334  		}
   335  
   336  		if _, ok := d.GetOk("master_username"); !ok {
   337  			return fmt.Errorf(`provider.aws: aws_redshift_cluster: %s: "master_username": required field is not set`, d.Get("cluster_identifier").(string))
   338  		}
   339  
   340  		createOpts := &redshift.CreateClusterInput{
   341  			ClusterIdentifier:                aws.String(d.Get("cluster_identifier").(string)),
   342  			Port:                             aws.Int64(int64(d.Get("port").(int))),
   343  			MasterUserPassword:               aws.String(d.Get("master_password").(string)),
   344  			MasterUsername:                   aws.String(d.Get("master_username").(string)),
   345  			ClusterVersion:                   aws.String(d.Get("cluster_version").(string)),
   346  			NodeType:                         aws.String(d.Get("node_type").(string)),
   347  			DBName:                           aws.String(d.Get("database_name").(string)),
   348  			AllowVersionUpgrade:              aws.Bool(d.Get("allow_version_upgrade").(bool)),
   349  			PubliclyAccessible:               aws.Bool(d.Get("publicly_accessible").(bool)),
   350  			AutomatedSnapshotRetentionPeriod: aws.Int64(int64(d.Get("automated_snapshot_retention_period").(int))),
   351  			Tags: tags,
   352  		}
   353  
   354  		if v := d.Get("number_of_nodes").(int); v > 1 {
   355  			createOpts.ClusterType = aws.String("multi-node")
   356  			createOpts.NumberOfNodes = aws.Int64(int64(d.Get("number_of_nodes").(int)))
   357  		} else {
   358  			createOpts.ClusterType = aws.String("single-node")
   359  		}
   360  
   361  		if v := d.Get("cluster_security_groups").(*schema.Set); v.Len() > 0 {
   362  			createOpts.ClusterSecurityGroups = expandStringList(v.List())
   363  		}
   364  
   365  		if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 {
   366  			createOpts.VpcSecurityGroupIds = expandStringList(v.List())
   367  		}
   368  
   369  		if v, ok := d.GetOk("cluster_subnet_group_name"); ok {
   370  			createOpts.ClusterSubnetGroupName = aws.String(v.(string))
   371  		}
   372  
   373  		if v, ok := d.GetOk("availability_zone"); ok {
   374  			createOpts.AvailabilityZone = aws.String(v.(string))
   375  		}
   376  
   377  		if v, ok := d.GetOk("preferred_maintenance_window"); ok {
   378  			createOpts.PreferredMaintenanceWindow = aws.String(v.(string))
   379  		}
   380  
   381  		if v, ok := d.GetOk("cluster_parameter_group_name"); ok {
   382  			createOpts.ClusterParameterGroupName = aws.String(v.(string))
   383  		}
   384  
   385  		if v, ok := d.GetOk("encrypted"); ok {
   386  			createOpts.Encrypted = aws.Bool(v.(bool))
   387  		}
   388  
   389  		if v, ok := d.GetOk("enhanced_vpc_routing"); ok {
   390  			createOpts.EnhancedVpcRouting = aws.Bool(v.(bool))
   391  		}
   392  
   393  		if v, ok := d.GetOk("kms_key_id"); ok {
   394  			createOpts.KmsKeyId = aws.String(v.(string))
   395  		}
   396  
   397  		if v, ok := d.GetOk("elastic_ip"); ok {
   398  			createOpts.ElasticIp = aws.String(v.(string))
   399  		}
   400  
   401  		if v, ok := d.GetOk("iam_roles"); ok {
   402  			createOpts.IamRoles = expandStringList(v.(*schema.Set).List())
   403  		}
   404  
   405  		log.Printf("[DEBUG] Redshift Cluster create options: %s", createOpts)
   406  		resp, err := conn.CreateCluster(createOpts)
   407  		if err != nil {
   408  			log.Printf("[ERROR] Error creating Redshift Cluster: %s", err)
   409  			return err
   410  		}
   411  
   412  		log.Printf("[DEBUG]: Cluster create response: %s", resp)
   413  		d.SetId(*resp.Cluster.ClusterIdentifier)
   414  	}
   415  
   416  	stateConf := &resource.StateChangeConf{
   417  		Pending:    []string{"creating", "backing-up", "modifying", "restoring"},
   418  		Target:     []string{"available"},
   419  		Refresh:    resourceAwsRedshiftClusterStateRefreshFunc(d, meta),
   420  		Timeout:    75 * time.Minute,
   421  		MinTimeout: 10 * time.Second,
   422  	}
   423  
   424  	_, err := stateConf.WaitForState()
   425  	if err != nil {
   426  		return fmt.Errorf("[WARN] Error waiting for Redshift Cluster state to be \"available\": %s", err)
   427  	}
   428  
   429  	if _, ok := d.GetOk("enable_logging"); ok {
   430  
   431  		loggingErr := enableRedshiftClusterLogging(d, conn)
   432  		if loggingErr != nil {
   433  			log.Printf("[ERROR] Error Enabling Logging on Redshift Cluster: %s", err)
   434  			return loggingErr
   435  		}
   436  
   437  	}
   438  
   439  	return resourceAwsRedshiftClusterRead(d, meta)
   440  }
   441  
   442  func resourceAwsRedshiftClusterRead(d *schema.ResourceData, meta interface{}) error {
   443  	conn := meta.(*AWSClient).redshiftconn
   444  
   445  	log.Printf("[INFO] Reading Redshift Cluster Information: %s", d.Id())
   446  	resp, err := conn.DescribeClusters(&redshift.DescribeClustersInput{
   447  		ClusterIdentifier: aws.String(d.Id()),
   448  	})
   449  
   450  	if err != nil {
   451  		if awsErr, ok := err.(awserr.Error); ok {
   452  			if "ClusterNotFound" == awsErr.Code() {
   453  				d.SetId("")
   454  				log.Printf("[DEBUG] Redshift Cluster (%s) not found", d.Id())
   455  				return nil
   456  			}
   457  		}
   458  		log.Printf("[DEBUG] Error describing Redshift Cluster (%s)", d.Id())
   459  		return err
   460  	}
   461  
   462  	var rsc *redshift.Cluster
   463  	for _, c := range resp.Clusters {
   464  		if *c.ClusterIdentifier == d.Id() {
   465  			rsc = c
   466  		}
   467  	}
   468  
   469  	if rsc == nil {
   470  		log.Printf("[WARN] Redshift Cluster (%s) not found", d.Id())
   471  		d.SetId("")
   472  		return nil
   473  	}
   474  
   475  	log.Printf("[INFO] Reading Redshift Cluster Logging Status: %s", d.Id())
   476  	loggingStatus, loggingErr := conn.DescribeLoggingStatus(&redshift.DescribeLoggingStatusInput{
   477  		ClusterIdentifier: aws.String(d.Id()),
   478  	})
   479  
   480  	if loggingErr != nil {
   481  		return loggingErr
   482  	}
   483  
   484  	d.Set("master_username", rsc.MasterUsername)
   485  	d.Set("node_type", rsc.NodeType)
   486  	d.Set("allow_version_upgrade", rsc.AllowVersionUpgrade)
   487  	d.Set("database_name", rsc.DBName)
   488  	d.Set("cluster_identifier", rsc.ClusterIdentifier)
   489  	d.Set("cluster_version", rsc.ClusterVersion)
   490  
   491  	d.Set("cluster_subnet_group_name", rsc.ClusterSubnetGroupName)
   492  	d.Set("availability_zone", rsc.AvailabilityZone)
   493  	d.Set("encrypted", rsc.Encrypted)
   494  	d.Set("enhanced_vpc_routing", rsc.EnhancedVpcRouting)
   495  	d.Set("kms_key_id", rsc.KmsKeyId)
   496  	d.Set("automated_snapshot_retention_period", rsc.AutomatedSnapshotRetentionPeriod)
   497  	d.Set("preferred_maintenance_window", rsc.PreferredMaintenanceWindow)
   498  	if rsc.Endpoint != nil && rsc.Endpoint.Address != nil {
   499  		endpoint := *rsc.Endpoint.Address
   500  		if rsc.Endpoint.Port != nil {
   501  			endpoint = fmt.Sprintf("%s:%d", endpoint, *rsc.Endpoint.Port)
   502  		}
   503  		d.Set("port", rsc.Endpoint.Port)
   504  		d.Set("endpoint", endpoint)
   505  	}
   506  	d.Set("cluster_parameter_group_name", rsc.ClusterParameterGroups[0].ParameterGroupName)
   507  	if len(rsc.ClusterNodes) > 1 {
   508  		d.Set("cluster_type", "multi-node")
   509  	} else {
   510  		d.Set("cluster_type", "single-node")
   511  	}
   512  	d.Set("number_of_nodes", rsc.NumberOfNodes)
   513  	d.Set("publicly_accessible", rsc.PubliclyAccessible)
   514  
   515  	var vpcg []string
   516  	for _, g := range rsc.VpcSecurityGroups {
   517  		vpcg = append(vpcg, *g.VpcSecurityGroupId)
   518  	}
   519  	if err := d.Set("vpc_security_group_ids", vpcg); err != nil {
   520  		return fmt.Errorf("[DEBUG] Error saving VPC Security Group IDs to state for Redshift Cluster (%s): %s", d.Id(), err)
   521  	}
   522  
   523  	var csg []string
   524  	for _, g := range rsc.ClusterSecurityGroups {
   525  		csg = append(csg, *g.ClusterSecurityGroupName)
   526  	}
   527  	if err := d.Set("cluster_security_groups", csg); err != nil {
   528  		return fmt.Errorf("[DEBUG] Error saving Cluster Security Group Names to state for Redshift Cluster (%s): %s", d.Id(), err)
   529  	}
   530  
   531  	var iamRoles []string
   532  	for _, i := range rsc.IamRoles {
   533  		iamRoles = append(iamRoles, *i.IamRoleArn)
   534  	}
   535  	if err := d.Set("iam_roles", iamRoles); err != nil {
   536  		return fmt.Errorf("[DEBUG] Error saving IAM Roles to state for Redshift Cluster (%s): %s", d.Id(), err)
   537  	}
   538  
   539  	d.Set("cluster_public_key", rsc.ClusterPublicKey)
   540  	d.Set("cluster_revision_number", rsc.ClusterRevisionNumber)
   541  	d.Set("tags", tagsToMapRedshift(rsc.Tags))
   542  
   543  	d.Set("bucket_name", loggingStatus.BucketName)
   544  	d.Set("enable_logging", loggingStatus.LoggingEnabled)
   545  	d.Set("s3_key_prefix", loggingStatus.S3KeyPrefix)
   546  
   547  	return nil
   548  }
   549  
   550  func resourceAwsRedshiftClusterUpdate(d *schema.ResourceData, meta interface{}) error {
   551  	conn := meta.(*AWSClient).redshiftconn
   552  	d.Partial(true)
   553  
   554  	arn, tagErr := buildRedshiftARN(d.Id(), meta.(*AWSClient).partition, meta.(*AWSClient).accountid, meta.(*AWSClient).region)
   555  	if tagErr != nil {
   556  		return fmt.Errorf("Error building ARN for Redshift Cluster, not updating Tags for cluster %s", d.Id())
   557  	} else {
   558  		if tagErr := setTagsRedshift(conn, d, arn); tagErr != nil {
   559  			return tagErr
   560  		} else {
   561  			d.SetPartial("tags")
   562  		}
   563  	}
   564  
   565  	requestUpdate := false
   566  	log.Printf("[INFO] Building Redshift Modify Cluster Options")
   567  	req := &redshift.ModifyClusterInput{
   568  		ClusterIdentifier: aws.String(d.Id()),
   569  	}
   570  
   571  	if d.HasChange("cluster_type") {
   572  		req.ClusterType = aws.String(d.Get("cluster_type").(string))
   573  		requestUpdate = true
   574  	}
   575  
   576  	if d.HasChange("node_type") {
   577  		req.NodeType = aws.String(d.Get("node_type").(string))
   578  		requestUpdate = true
   579  	}
   580  
   581  	if d.HasChange("number_of_nodes") {
   582  		if v := d.Get("number_of_nodes").(int); v > 1 {
   583  			req.ClusterType = aws.String("multi-node")
   584  			req.NumberOfNodes = aws.Int64(int64(d.Get("number_of_nodes").(int)))
   585  		} else {
   586  			req.ClusterType = aws.String("single-node")
   587  		}
   588  
   589  		req.NodeType = aws.String(d.Get("node_type").(string))
   590  		requestUpdate = true
   591  	}
   592  
   593  	if d.HasChange("cluster_security_groups") {
   594  		req.ClusterSecurityGroups = expandStringList(d.Get("cluster_security_groups").(*schema.Set).List())
   595  		requestUpdate = true
   596  	}
   597  
   598  	if d.HasChange("vpc_security_group_ids") {
   599  		req.VpcSecurityGroupIds = expandStringList(d.Get("vpc_security_group_ids").(*schema.Set).List())
   600  		requestUpdate = true
   601  	}
   602  
   603  	if d.HasChange("master_password") {
   604  		req.MasterUserPassword = aws.String(d.Get("master_password").(string))
   605  		requestUpdate = true
   606  	}
   607  
   608  	if d.HasChange("cluster_parameter_group_name") {
   609  		req.ClusterParameterGroupName = aws.String(d.Get("cluster_parameter_group_name").(string))
   610  		requestUpdate = true
   611  	}
   612  
   613  	if d.HasChange("automated_snapshot_retention_period") {
   614  		req.AutomatedSnapshotRetentionPeriod = aws.Int64(int64(d.Get("automated_snapshot_retention_period").(int)))
   615  		requestUpdate = true
   616  	}
   617  
   618  	if d.HasChange("preferred_maintenance_window") {
   619  		req.PreferredMaintenanceWindow = aws.String(d.Get("preferred_maintenance_window").(string))
   620  		requestUpdate = true
   621  	}
   622  
   623  	if d.HasChange("cluster_version") {
   624  		req.ClusterVersion = aws.String(d.Get("cluster_version").(string))
   625  		requestUpdate = true
   626  	}
   627  
   628  	if d.HasChange("allow_version_upgrade") {
   629  		req.AllowVersionUpgrade = aws.Bool(d.Get("allow_version_upgrade").(bool))
   630  		requestUpdate = true
   631  	}
   632  
   633  	if d.HasChange("publicly_accessible") {
   634  		req.PubliclyAccessible = aws.Bool(d.Get("publicly_accessible").(bool))
   635  		requestUpdate = true
   636  	}
   637  
   638  	if d.HasChange("enhanced_vpc_routing") {
   639  		req.EnhancedVpcRouting = aws.Bool(d.Get("enhanced_vpc_routing").(bool))
   640  		requestUpdate = true
   641  	}
   642  
   643  	if requestUpdate {
   644  		log.Printf("[INFO] Modifying Redshift Cluster: %s", d.Id())
   645  		log.Printf("[DEBUG] Redshift Cluster Modify options: %s", req)
   646  		_, err := conn.ModifyCluster(req)
   647  		if err != nil {
   648  			return fmt.Errorf("[WARN] Error modifying Redshift Cluster (%s): %s", d.Id(), err)
   649  		}
   650  	}
   651  
   652  	if d.HasChange("iam_roles") {
   653  		o, n := d.GetChange("iam_roles")
   654  		if o == nil {
   655  			o = new(schema.Set)
   656  		}
   657  		if n == nil {
   658  			n = new(schema.Set)
   659  		}
   660  
   661  		os := o.(*schema.Set)
   662  		ns := n.(*schema.Set)
   663  
   664  		removeIams := os.Difference(ns).List()
   665  		addIams := ns.Difference(os).List()
   666  
   667  		log.Printf("[INFO] Building Redshift Modify Cluster IAM Role Options")
   668  		req := &redshift.ModifyClusterIamRolesInput{
   669  			ClusterIdentifier: aws.String(d.Id()),
   670  			AddIamRoles:       expandStringList(addIams),
   671  			RemoveIamRoles:    expandStringList(removeIams),
   672  		}
   673  
   674  		log.Printf("[INFO] Modifying Redshift Cluster IAM Roles: %s", d.Id())
   675  		log.Printf("[DEBUG] Redshift Cluster Modify IAM Role options: %s", req)
   676  		_, err := conn.ModifyClusterIamRoles(req)
   677  		if err != nil {
   678  			return fmt.Errorf("[WARN] Error modifying Redshift Cluster IAM Roles (%s): %s", d.Id(), err)
   679  		}
   680  
   681  		d.SetPartial("iam_roles")
   682  	}
   683  
   684  	if requestUpdate || d.HasChange("iam_roles") {
   685  
   686  		stateConf := &resource.StateChangeConf{
   687  			Pending:    []string{"creating", "deleting", "rebooting", "resizing", "renaming", "modifying"},
   688  			Target:     []string{"available"},
   689  			Refresh:    resourceAwsRedshiftClusterStateRefreshFunc(d, meta),
   690  			Timeout:    40 * time.Minute,
   691  			MinTimeout: 10 * time.Second,
   692  		}
   693  
   694  		// Wait, catching any errors
   695  		_, err := stateConf.WaitForState()
   696  		if err != nil {
   697  			return fmt.Errorf("[WARN] Error Modifying Redshift Cluster (%s): %s", d.Id(), err)
   698  		}
   699  	}
   700  
   701  	if d.HasChange("enable_logging") || d.HasChange("bucket_name") || d.HasChange("s3_key_prefix") {
   702  		var loggingErr error
   703  		if _, ok := d.GetOk("enable_logging"); ok {
   704  
   705  			log.Printf("[INFO] Enabling Logging for Redshift Cluster %q", d.Id())
   706  			loggingErr = enableRedshiftClusterLogging(d, conn)
   707  			if loggingErr != nil {
   708  				return loggingErr
   709  			}
   710  		} else {
   711  
   712  			log.Printf("[INFO] Disabling Logging for Redshift Cluster %q", d.Id())
   713  			_, loggingErr = conn.DisableLogging(&redshift.DisableLoggingInput{
   714  				ClusterIdentifier: aws.String(d.Id()),
   715  			})
   716  			if loggingErr != nil {
   717  				return loggingErr
   718  			}
   719  		}
   720  
   721  		d.SetPartial("enable_logging")
   722  	}
   723  
   724  	d.Partial(false)
   725  
   726  	return resourceAwsRedshiftClusterRead(d, meta)
   727  }
   728  
   729  func enableRedshiftClusterLogging(d *schema.ResourceData, conn *redshift.Redshift) error {
   730  	if _, ok := d.GetOk("bucket_name"); !ok {
   731  		return fmt.Errorf("bucket_name must be set when enabling logging for Redshift Clusters")
   732  	}
   733  
   734  	params := &redshift.EnableLoggingInput{
   735  		ClusterIdentifier: aws.String(d.Id()),
   736  		BucketName:        aws.String(d.Get("bucket_name").(string)),
   737  	}
   738  
   739  	if v, ok := d.GetOk("s3_key_prefix"); ok {
   740  		params.S3KeyPrefix = aws.String(v.(string))
   741  	}
   742  
   743  	_, loggingErr := conn.EnableLogging(params)
   744  	if loggingErr != nil {
   745  		log.Printf("[ERROR] Error Enabling Logging on Redshift Cluster: %s", loggingErr)
   746  		return loggingErr
   747  	}
   748  	return nil
   749  }
   750  
   751  func resourceAwsRedshiftClusterDelete(d *schema.ResourceData, meta interface{}) error {
   752  	conn := meta.(*AWSClient).redshiftconn
   753  	log.Printf("[DEBUG] Destroying Redshift Cluster (%s)", d.Id())
   754  
   755  	deleteOpts := redshift.DeleteClusterInput{
   756  		ClusterIdentifier: aws.String(d.Id()),
   757  	}
   758  
   759  	skipFinalSnapshot := d.Get("skip_final_snapshot").(bool)
   760  	deleteOpts.SkipFinalClusterSnapshot = aws.Bool(skipFinalSnapshot)
   761  
   762  	if skipFinalSnapshot == false {
   763  		if name, present := d.GetOk("final_snapshot_identifier"); present {
   764  			deleteOpts.FinalClusterSnapshotIdentifier = aws.String(name.(string))
   765  		} else {
   766  			return fmt.Errorf("Redshift Cluster Instance FinalSnapshotIdentifier is required when a final snapshot is required")
   767  		}
   768  	}
   769  
   770  	log.Printf("[DEBUG] Redshift Cluster delete options: %s", deleteOpts)
   771  	_, err := conn.DeleteCluster(&deleteOpts)
   772  	if err != nil {
   773  		return fmt.Errorf("[ERROR] Error deleting Redshift Cluster (%s): %s", d.Id(), err)
   774  	}
   775  
   776  	stateConf := &resource.StateChangeConf{
   777  		Pending:    []string{"available", "creating", "deleting", "rebooting", "resizing", "renaming", "final-snapshot"},
   778  		Target:     []string{"destroyed"},
   779  		Refresh:    resourceAwsRedshiftClusterStateRefreshFunc(d, meta),
   780  		Timeout:    40 * time.Minute,
   781  		MinTimeout: 5 * time.Second,
   782  	}
   783  
   784  	// Wait, catching any errors
   785  	_, err = stateConf.WaitForState()
   786  	if err != nil {
   787  		return fmt.Errorf("[ERROR] Error deleting Redshift Cluster (%s): %s", d.Id(), err)
   788  	}
   789  
   790  	log.Printf("[INFO] Redshift Cluster %s successfully deleted", d.Id())
   791  
   792  	return nil
   793  }
   794  
   795  func resourceAwsRedshiftClusterStateRefreshFunc(d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc {
   796  	return func() (interface{}, string, error) {
   797  		conn := meta.(*AWSClient).redshiftconn
   798  
   799  		log.Printf("[INFO] Reading Redshift Cluster Information: %s", d.Id())
   800  		resp, err := conn.DescribeClusters(&redshift.DescribeClustersInput{
   801  			ClusterIdentifier: aws.String(d.Id()),
   802  		})
   803  
   804  		if err != nil {
   805  			if awsErr, ok := err.(awserr.Error); ok {
   806  				if "ClusterNotFound" == awsErr.Code() {
   807  					return 42, "destroyed", nil
   808  				}
   809  			}
   810  			log.Printf("[WARN] Error on retrieving Redshift Cluster (%s) when waiting: %s", d.Id(), err)
   811  			return nil, "", err
   812  		}
   813  
   814  		var rsc *redshift.Cluster
   815  
   816  		for _, c := range resp.Clusters {
   817  			if *c.ClusterIdentifier == d.Id() {
   818  				rsc = c
   819  			}
   820  		}
   821  
   822  		if rsc == nil {
   823  			return 42, "destroyed", nil
   824  		}
   825  
   826  		if rsc.ClusterStatus != nil {
   827  			log.Printf("[DEBUG] Redshift Cluster status (%s): %s", d.Id(), *rsc.ClusterStatus)
   828  		}
   829  
   830  		return rsc, *rsc.ClusterStatus, nil
   831  	}
   832  }
   833  
   834  func validateRedshiftClusterIdentifier(v interface{}, k string) (ws []string, errors []error) {
   835  	value := v.(string)
   836  	if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) {
   837  		errors = append(errors, fmt.Errorf(
   838  			"only lowercase alphanumeric characters and hyphens allowed in %q", k))
   839  	}
   840  	if !regexp.MustCompile(`^[a-z]`).MatchString(value) {
   841  		errors = append(errors, fmt.Errorf(
   842  			"first character of %q must be a letter", k))
   843  	}
   844  	if regexp.MustCompile(`--`).MatchString(value) {
   845  		errors = append(errors, fmt.Errorf(
   846  			"%q cannot contain two consecutive hyphens", k))
   847  	}
   848  	if regexp.MustCompile(`-$`).MatchString(value) {
   849  		errors = append(errors, fmt.Errorf(
   850  			"%q cannot end with a hyphen", k))
   851  	}
   852  	return
   853  }
   854  
   855  func validateRedshiftClusterDbName(v interface{}, k string) (ws []string, errors []error) {
   856  	value := v.(string)
   857  	if !regexp.MustCompile(`^[0-9A-Za-z_$]+$`).MatchString(value) {
   858  		errors = append(errors, fmt.Errorf(
   859  			"only alphanumeric characters, underscores, and dollar signs are allowed in %q", k))
   860  	}
   861  	if !regexp.MustCompile(`^[a-zA-Z_]`).MatchString(value) {
   862  		errors = append(errors, fmt.Errorf(
   863  			"first character of %q must be a letter or underscore", k))
   864  	}
   865  	if len(value) > 64 {
   866  		errors = append(errors, fmt.Errorf(
   867  			"%q cannot be longer than 64 characters: %q", k, value))
   868  	}
   869  	if value == "" {
   870  		errors = append(errors, fmt.Errorf(
   871  			"%q cannot be an empty string", k))
   872  	}
   873  
   874  	return
   875  }
   876  
   877  func validateRedshiftClusterFinalSnapshotIdentifier(v interface{}, k string) (ws []string, errors []error) {
   878  	value := v.(string)
   879  	if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) {
   880  		errors = append(errors, fmt.Errorf(
   881  			"only alphanumeric characters and hyphens allowed in %q", k))
   882  	}
   883  	if regexp.MustCompile(`--`).MatchString(value) {
   884  		errors = append(errors, fmt.Errorf("%q cannot contain two consecutive hyphens", k))
   885  	}
   886  	if regexp.MustCompile(`-$`).MatchString(value) {
   887  		errors = append(errors, fmt.Errorf("%q cannot end in a hyphen", k))
   888  	}
   889  	if len(value) > 255 {
   890  		errors = append(errors, fmt.Errorf("%q cannot be more than 255 characters", k))
   891  	}
   892  	return
   893  }
   894  
   895  func validateRedshiftClusterMasterUsername(v interface{}, k string) (ws []string, errors []error) {
   896  	value := v.(string)
   897  	if !regexp.MustCompile(`^\w+$`).MatchString(value) {
   898  		errors = append(errors, fmt.Errorf(
   899  			"only alphanumeric characters in %q", k))
   900  	}
   901  	if !regexp.MustCompile(`^[A-Za-z]`).MatchString(value) {
   902  		errors = append(errors, fmt.Errorf(
   903  			"first character of %q must be a letter", k))
   904  	}
   905  	if len(value) > 128 {
   906  		errors = append(errors, fmt.Errorf("%q cannot be more than 128 characters", k))
   907  	}
   908  	return
   909  }
   910  
   911  func validateRedshiftClusterMasterPassword(v interface{}, k string) (ws []string, errors []error) {
   912  	value := v.(string)
   913  	if !regexp.MustCompile(`^.*[a-z].*`).MatchString(value) {
   914  		errors = append(errors, fmt.Errorf(
   915  			"%q must contain at least one lowercase letter", k))
   916  	}
   917  	if !regexp.MustCompile(`^.*[A-Z].*`).MatchString(value) {
   918  		errors = append(errors, fmt.Errorf(
   919  			"%q must contain at least one uppercase letter", k))
   920  	}
   921  	if !regexp.MustCompile(`^.*[0-9].*`).MatchString(value) {
   922  		errors = append(errors, fmt.Errorf(
   923  			"%q must contain at least one number", k))
   924  	}
   925  	if len(value) < 8 {
   926  		errors = append(errors, fmt.Errorf("%q must be at least 8 characters", k))
   927  	}
   928  	return
   929  }
   930  
   931  func buildRedshiftARN(identifier, partition, accountid, region string) (string, error) {
   932  	if partition == "" {
   933  		return "", fmt.Errorf("Unable to construct cluster ARN because of missing AWS partition")
   934  	}
   935  	if accountid == "" {
   936  		return "", fmt.Errorf("Unable to construct cluster ARN because of missing AWS Account ID")
   937  	}
   938  	arn := fmt.Sprintf("arn:%s:redshift:%s:%s:cluster:%s", partition, region, accountid, identifier)
   939  	return arn, nil
   940  
   941  }