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