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