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