github.com/leeprovoost/terraform@v0.6.10-0.20160119085442-96f3f76118e7/builtin/providers/aws/resource_aws_redshift_cluster.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"regexp"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/aws/aws-sdk-go/aws"
    11  	"github.com/aws/aws-sdk-go/aws/awserr"
    12  	"github.com/aws/aws-sdk-go/service/redshift"
    13  	"github.com/hashicorp/terraform/helper/resource"
    14  	"github.com/hashicorp/terraform/helper/schema"
    15  )
    16  
    17  func resourceAwsRedshiftCluster() *schema.Resource {
    18  	return &schema.Resource{
    19  		Create: resourceAwsRedshiftClusterCreate,
    20  		Read:   resourceAwsRedshiftClusterRead,
    21  		Update: resourceAwsRedshiftClusterUpdate,
    22  		Delete: resourceAwsRedshiftClusterDelete,
    23  
    24  		Schema: map[string]*schema.Schema{
    25  			"database_name": &schema.Schema{
    26  				Type:         schema.TypeString,
    27  				Optional:     true,
    28  				Computed:     true,
    29  				ValidateFunc: validateRedshiftClusterDbName,
    30  			},
    31  
    32  			"cluster_identifier": &schema.Schema{
    33  				Type:         schema.TypeString,
    34  				Required:     true,
    35  				ForceNew:     true,
    36  				ValidateFunc: validateRedshiftClusterIdentifier,
    37  			},
    38  			"cluster_type": &schema.Schema{
    39  				Type:     schema.TypeString,
    40  				Required: true,
    41  			},
    42  
    43  			"node_type": &schema.Schema{
    44  				Type:     schema.TypeString,
    45  				Required: true,
    46  			},
    47  
    48  			"master_username": &schema.Schema{
    49  				Type:         schema.TypeString,
    50  				Required:     true,
    51  				ValidateFunc: validateRedshiftClusterMasterUsername,
    52  			},
    53  
    54  			"master_password": &schema.Schema{
    55  				Type:     schema.TypeString,
    56  				Required: true,
    57  			},
    58  
    59  			"cluster_security_groups": &schema.Schema{
    60  				Type:     schema.TypeSet,
    61  				Optional: true,
    62  				Computed: true,
    63  				Elem:     &schema.Schema{Type: schema.TypeString},
    64  				Set:      schema.HashString,
    65  			},
    66  
    67  			"vpc_security_group_ids": &schema.Schema{
    68  				Type:     schema.TypeSet,
    69  				Optional: true,
    70  				Computed: true,
    71  				Elem:     &schema.Schema{Type: schema.TypeString},
    72  				Set:      schema.HashString,
    73  			},
    74  
    75  			"cluster_subnet_group_name": &schema.Schema{
    76  				Type:     schema.TypeString,
    77  				Optional: true,
    78  				ForceNew: true,
    79  				Computed: true,
    80  			},
    81  
    82  			"availability_zone": &schema.Schema{
    83  				Type:     schema.TypeString,
    84  				Optional: true,
    85  				Computed: true,
    86  			},
    87  
    88  			"preferred_maintenance_window": &schema.Schema{
    89  				Type:     schema.TypeString,
    90  				Optional: true,
    91  				Computed: true,
    92  				StateFunc: func(val interface{}) string {
    93  					if val == nil {
    94  						return ""
    95  					}
    96  					return strings.ToLower(val.(string))
    97  				},
    98  			},
    99  
   100  			"cluster_parameter_group_name": &schema.Schema{
   101  				Type:     schema.TypeString,
   102  				Optional: true,
   103  				Computed: true,
   104  			},
   105  
   106  			"automated_snapshot_retention_period": &schema.Schema{
   107  				Type:     schema.TypeInt,
   108  				Optional: true,
   109  				Default:  1,
   110  				ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
   111  					value := v.(int)
   112  					if value > 35 {
   113  						es = append(es, fmt.Errorf(
   114  							"backup retention period cannot be more than 35 days"))
   115  					}
   116  					return
   117  				},
   118  			},
   119  
   120  			"port": &schema.Schema{
   121  				Type:     schema.TypeInt,
   122  				Optional: true,
   123  				Default:  5439,
   124  			},
   125  
   126  			"cluster_version": &schema.Schema{
   127  				Type:     schema.TypeString,
   128  				Optional: true,
   129  				Default:  "1.0",
   130  			},
   131  
   132  			"allow_version_upgrade": &schema.Schema{
   133  				Type:     schema.TypeBool,
   134  				Optional: true,
   135  				Default:  true,
   136  			},
   137  
   138  			"number_of_nodes": &schema.Schema{
   139  				Type:     schema.TypeInt,
   140  				Optional: true,
   141  				Default:  1,
   142  			},
   143  
   144  			"publicly_accessible": &schema.Schema{
   145  				Type:     schema.TypeBool,
   146  				Optional: true,
   147  				ForceNew: true,
   148  			},
   149  
   150  			"encrypted": &schema.Schema{
   151  				Type:     schema.TypeBool,
   152  				Optional: true,
   153  				Computed: true,
   154  			},
   155  
   156  			"elastic_ip": &schema.Schema{
   157  				Type:     schema.TypeString,
   158  				Optional: true,
   159  			},
   160  
   161  			"final_snapshot_identifier": &schema.Schema{
   162  				Type:         schema.TypeString,
   163  				Optional:     true,
   164  				ValidateFunc: validateRedshiftClusterFinalSnapshotIdentifier,
   165  			},
   166  
   167  			"skip_final_snapshot": &schema.Schema{
   168  				Type:     schema.TypeBool,
   169  				Optional: true,
   170  				Default:  true,
   171  			},
   172  
   173  			"endpoint": &schema.Schema{
   174  				Type:     schema.TypeString,
   175  				Optional: true,
   176  				Computed: true,
   177  			},
   178  
   179  			"cluster_public_key": &schema.Schema{
   180  				Type:     schema.TypeString,
   181  				Optional: true,
   182  				Computed: true,
   183  			},
   184  
   185  			"cluster_revision_number": &schema.Schema{
   186  				Type:     schema.TypeString,
   187  				Optional: true,
   188  				Computed: true,
   189  			},
   190  		},
   191  	}
   192  }
   193  
   194  func resourceAwsRedshiftClusterCreate(d *schema.ResourceData, meta interface{}) error {
   195  	conn := meta.(*AWSClient).redshiftconn
   196  
   197  	log.Printf("[INFO] Building Redshift Cluster Options")
   198  	createOpts := &redshift.CreateClusterInput{
   199  		ClusterIdentifier:   aws.String(d.Get("cluster_identifier").(string)),
   200  		Port:                aws.Int64(int64(d.Get("port").(int))),
   201  		MasterUserPassword:  aws.String(d.Get("master_password").(string)),
   202  		MasterUsername:      aws.String(d.Get("master_username").(string)),
   203  		ClusterType:         aws.String(d.Get("cluster_type").(string)),
   204  		ClusterVersion:      aws.String(d.Get("cluster_version").(string)),
   205  		NodeType:            aws.String(d.Get("node_type").(string)),
   206  		DBName:              aws.String(d.Get("database_name").(string)),
   207  		AllowVersionUpgrade: aws.Bool(d.Get("allow_version_upgrade").(bool)),
   208  	}
   209  	if d.Get("cluster_type") == "multi-node" {
   210  		createOpts.NumberOfNodes = aws.Int64(int64(d.Get("number_of_nodes").(int)))
   211  	}
   212  	if v := d.Get("cluster_security_groups").(*schema.Set); v.Len() > 0 {
   213  		createOpts.ClusterSecurityGroups = expandStringList(v.List())
   214  	}
   215  
   216  	if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 {
   217  		createOpts.VpcSecurityGroupIds = expandStringList(v.List())
   218  	}
   219  
   220  	if v, ok := d.GetOk("cluster_subnet_group_name"); ok {
   221  		createOpts.ClusterSubnetGroupName = aws.String(v.(string))
   222  	}
   223  
   224  	if v, ok := d.GetOk("availability_zone"); ok {
   225  		createOpts.AvailabilityZone = aws.String(v.(string))
   226  	}
   227  
   228  	if v, ok := d.GetOk("preferred_maintenance_window"); ok {
   229  		createOpts.PreferredMaintenanceWindow = aws.String(v.(string))
   230  	}
   231  
   232  	if v, ok := d.GetOk("cluster_parameter_group_name"); ok {
   233  		createOpts.ClusterParameterGroupName = aws.String(v.(string))
   234  	}
   235  
   236  	if v, ok := d.GetOk("automated_snapshot_retention_period"); ok {
   237  		createOpts.AutomatedSnapshotRetentionPeriod = aws.Int64(int64(v.(int)))
   238  	}
   239  
   240  	if v, ok := d.GetOk("publicly_accessible"); ok {
   241  		createOpts.PubliclyAccessible = aws.Bool(v.(bool))
   242  	}
   243  
   244  	if v, ok := d.GetOk("encrypted"); ok {
   245  		createOpts.Encrypted = aws.Bool(v.(bool))
   246  	}
   247  
   248  	if v, ok := d.GetOk("elastic_ip"); ok {
   249  		createOpts.ElasticIp = aws.String(v.(string))
   250  	}
   251  
   252  	log.Printf("[DEBUG] Redshift Cluster create options: %s", createOpts)
   253  	resp, err := conn.CreateCluster(createOpts)
   254  	if err != nil {
   255  		log.Printf("[ERROR] Error creating Redshift Cluster: %s", err)
   256  		return err
   257  	}
   258  
   259  	log.Printf("[DEBUG]: Cluster create response: %s", resp)
   260  	d.SetId(*resp.Cluster.ClusterIdentifier)
   261  
   262  	stateConf := &resource.StateChangeConf{
   263  		Pending:    []string{"creating", "backing-up", "modifying"},
   264  		Target:     "available",
   265  		Refresh:    resourceAwsRedshiftClusterStateRefreshFunc(d, meta),
   266  		Timeout:    5 * time.Minute,
   267  		MinTimeout: 3 * time.Second,
   268  	}
   269  
   270  	_, err = stateConf.WaitForState()
   271  	if err != nil {
   272  		return fmt.Errorf("[WARN] Error waiting for Redshift Cluster state to be \"available\": %s", err)
   273  	}
   274  
   275  	return resourceAwsRedshiftClusterRead(d, meta)
   276  }
   277  
   278  func resourceAwsRedshiftClusterRead(d *schema.ResourceData, meta interface{}) error {
   279  	conn := meta.(*AWSClient).redshiftconn
   280  
   281  	log.Printf("[INFO] Reading Redshift Cluster Information: %s", d.Id())
   282  	resp, err := conn.DescribeClusters(&redshift.DescribeClustersInput{
   283  		ClusterIdentifier: aws.String(d.Id()),
   284  	})
   285  
   286  	if err != nil {
   287  		if awsErr, ok := err.(awserr.Error); ok {
   288  			if "ClusterNotFound" == awsErr.Code() {
   289  				d.SetId("")
   290  				log.Printf("[DEBUG] Redshift Cluster (%s) not found", d.Id())
   291  				return nil
   292  			}
   293  		}
   294  		log.Printf("[DEBUG] Error describing Redshift Cluster (%s)", d.Id())
   295  		return err
   296  	}
   297  
   298  	var rsc *redshift.Cluster
   299  	for _, c := range resp.Clusters {
   300  		if *c.ClusterIdentifier == d.Id() {
   301  			rsc = c
   302  		}
   303  	}
   304  
   305  	if rsc == nil {
   306  		log.Printf("[WARN] Redshift Cluster (%s) not found", d.Id())
   307  		d.SetId("")
   308  		return nil
   309  	}
   310  
   311  	d.Set("database_name", rsc.DBName)
   312  	d.Set("cluster_subnet_group_name", rsc.ClusterSubnetGroupName)
   313  	d.Set("availability_zone", rsc.AvailabilityZone)
   314  	d.Set("encrypted", rsc.Encrypted)
   315  	d.Set("automated_snapshot_retention_period", rsc.AutomatedSnapshotRetentionPeriod)
   316  	d.Set("preferred_maintenance_window", rsc.PreferredMaintenanceWindow)
   317  	d.Set("endpoint", aws.String(fmt.Sprintf("%s:%d", *rsc.Endpoint.Address, *rsc.Endpoint.Port)))
   318  	d.Set("cluster_parameter_group_name", rsc.ClusterParameterGroups[0].ParameterGroupName)
   319  
   320  	var vpcg []string
   321  	for _, g := range rsc.VpcSecurityGroups {
   322  		vpcg = append(vpcg, *g.VpcSecurityGroupId)
   323  	}
   324  	if err := d.Set("vpc_security_group_ids", vpcg); err != nil {
   325  		return fmt.Errorf("[DEBUG] Error saving VPC Security Group IDs to state for Redshift Cluster (%s): %s", d.Id(), err)
   326  	}
   327  
   328  	var csg []string
   329  	for _, g := range rsc.ClusterSecurityGroups {
   330  		csg = append(csg, *g.ClusterSecurityGroupName)
   331  	}
   332  	if err := d.Set("cluster_security_groups", csg); err != nil {
   333  		return fmt.Errorf("[DEBUG] Error saving Cluster Security Group Names to state for Redshift Cluster (%s): %s", d.Id(), err)
   334  	}
   335  
   336  	d.Set("cluster_public_key", rsc.ClusterPublicKey)
   337  	d.Set("cluster_revision_number", rsc.ClusterRevisionNumber)
   338  
   339  	return nil
   340  }
   341  
   342  func resourceAwsRedshiftClusterUpdate(d *schema.ResourceData, meta interface{}) error {
   343  	conn := meta.(*AWSClient).redshiftconn
   344  
   345  	log.Printf("[INFO] Building Redshift Modify Cluster Options")
   346  	req := &redshift.ModifyClusterInput{
   347  		ClusterIdentifier: aws.String(d.Id()),
   348  	}
   349  
   350  	if d.HasChange("cluster_type") {
   351  		req.ClusterType = aws.String(d.Get("cluster_type").(string))
   352  	}
   353  
   354  	if d.HasChange("node_type") {
   355  		req.NodeType = aws.String(d.Get("node_type").(string))
   356  	}
   357  
   358  	if d.HasChange("number_of_nodes") {
   359  		log.Printf("[INFO] When changing the NumberOfNodes in a Redshift Cluster, NodeType is required")
   360  		req.NumberOfNodes = aws.Int64(int64(d.Get("number_of_nodes").(int)))
   361  		req.NodeType = aws.String(d.Get("node_type").(string))
   362  	}
   363  
   364  	if d.HasChange("cluster_security_groups") {
   365  		req.ClusterSecurityGroups = expandStringList(d.Get("cluster_security_groups").(*schema.Set).List())
   366  	}
   367  
   368  	if d.HasChange("vpc_security_group_ips") {
   369  		req.VpcSecurityGroupIds = expandStringList(d.Get("vpc_security_group_ips").(*schema.Set).List())
   370  	}
   371  
   372  	if d.HasChange("master_password") {
   373  		req.MasterUserPassword = aws.String(d.Get("master_password").(string))
   374  	}
   375  
   376  	if d.HasChange("cluster_parameter_group_name") {
   377  		req.ClusterParameterGroupName = aws.String(d.Get("cluster_parameter_group_name").(string))
   378  	}
   379  
   380  	if d.HasChange("automated_snapshot_retention_period") {
   381  		req.AutomatedSnapshotRetentionPeriod = aws.Int64(int64(d.Get("automated_snapshot_retention_period").(int)))
   382  	}
   383  
   384  	if d.HasChange("preferred_maintenance_window") {
   385  		req.PreferredMaintenanceWindow = aws.String(d.Get("preferred_maintenance_window").(string))
   386  	}
   387  
   388  	if d.HasChange("cluster_version") {
   389  		req.ClusterVersion = aws.String(d.Get("cluster_version").(string))
   390  	}
   391  
   392  	if d.HasChange("allow_version_upgrade") {
   393  		req.AllowVersionUpgrade = aws.Bool(d.Get("allow_version_upgrade").(bool))
   394  	}
   395  
   396  	log.Printf("[INFO] Modifying Redshift Cluster: %s", d.Id())
   397  	log.Printf("[DEBUG] Redshift Cluster Modify options: %s", req)
   398  	_, err := conn.ModifyCluster(req)
   399  	if err != nil {
   400  		return fmt.Errorf("[WARN] Error modifying Redshift Cluster (%s): %s", d.Id(), err)
   401  	}
   402  
   403  	stateConf := &resource.StateChangeConf{
   404  		Pending:    []string{"creating", "deleting", "rebooting", "resizing", "renaming"},
   405  		Target:     "available",
   406  		Refresh:    resourceAwsRedshiftClusterStateRefreshFunc(d, meta),
   407  		Timeout:    10 * time.Minute,
   408  		MinTimeout: 5 * time.Second,
   409  	}
   410  
   411  	// Wait, catching any errors
   412  	_, err = stateConf.WaitForState()
   413  	if err != nil {
   414  		return fmt.Errorf("[WARN] Error Modifying Redshift Cluster (%s): %s", d.Id(), err)
   415  	}
   416  
   417  	return resourceAwsRedshiftClusterRead(d, meta)
   418  }
   419  
   420  func resourceAwsRedshiftClusterDelete(d *schema.ResourceData, meta interface{}) error {
   421  	conn := meta.(*AWSClient).redshiftconn
   422  	log.Printf("[DEBUG] Destroying Redshift Cluster (%s)", d.Id())
   423  
   424  	deleteOpts := redshift.DeleteClusterInput{
   425  		ClusterIdentifier: aws.String(d.Id()),
   426  	}
   427  
   428  	skipFinalSnapshot := d.Get("skip_final_snapshot").(bool)
   429  	deleteOpts.SkipFinalClusterSnapshot = aws.Bool(skipFinalSnapshot)
   430  
   431  	if !skipFinalSnapshot {
   432  		if name, present := d.GetOk("final_snapshot_identifier"); present {
   433  			deleteOpts.FinalClusterSnapshotIdentifier = aws.String(name.(string))
   434  		} else {
   435  			return fmt.Errorf("Redshift Cluster Instance FinalSnapshotIdentifier is required when a final snapshot is required")
   436  		}
   437  	}
   438  
   439  	log.Printf("[DEBUG] Redshift Cluster delete options: %s", deleteOpts)
   440  	_, err := conn.DeleteCluster(&deleteOpts)
   441  	if err != nil {
   442  		return fmt.Errorf("[ERROR] Error deleting Redshift Cluster (%s): %s", d.Id(), err)
   443  	}
   444  
   445  	stateConf := &resource.StateChangeConf{
   446  		Pending:    []string{"available", "creating", "deleting", "rebooting", "resizing", "renaming"},
   447  		Target:     "destroyed",
   448  		Refresh:    resourceAwsRedshiftClusterStateRefreshFunc(d, meta),
   449  		Timeout:    40 * time.Minute,
   450  		MinTimeout: 5 * time.Second,
   451  	}
   452  
   453  	// Wait, catching any errors
   454  	_, err = stateConf.WaitForState()
   455  	if err != nil {
   456  		return fmt.Errorf("[ERROR] Error deleting Redshift Cluster (%s): %s", d.Id(), err)
   457  	}
   458  
   459  	log.Printf("[INFO] Redshift Cluster %s successfully deleted", d.Id())
   460  
   461  	return nil
   462  }
   463  
   464  func resourceAwsRedshiftClusterStateRefreshFunc(d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc {
   465  	return func() (interface{}, string, error) {
   466  		conn := meta.(*AWSClient).redshiftconn
   467  
   468  		log.Printf("[INFO] Reading Redshift Cluster Information: %s", d.Id())
   469  		resp, err := conn.DescribeClusters(&redshift.DescribeClustersInput{
   470  			ClusterIdentifier: aws.String(d.Id()),
   471  		})
   472  
   473  		if err != nil {
   474  			if awsErr, ok := err.(awserr.Error); ok {
   475  				if "ClusterNotFound" == awsErr.Code() {
   476  					return 42, "destroyed", nil
   477  				}
   478  			}
   479  			log.Printf("[WARN] Error on retrieving Redshift Cluster (%s) when waiting: %s", d.Id(), err)
   480  			return nil, "", err
   481  		}
   482  
   483  		var rsc *redshift.Cluster
   484  
   485  		for _, c := range resp.Clusters {
   486  			if *c.ClusterIdentifier == d.Id() {
   487  				rsc = c
   488  			}
   489  		}
   490  
   491  		if rsc == nil {
   492  			return 42, "destroyed", nil
   493  		}
   494  
   495  		if rsc.ClusterStatus != nil {
   496  			log.Printf("[DEBUG] Redshift Cluster status (%s): %s", d.Id(), *rsc.ClusterStatus)
   497  		}
   498  
   499  		return rsc, *rsc.ClusterStatus, nil
   500  	}
   501  }
   502  
   503  func validateRedshiftClusterIdentifier(v interface{}, k string) (ws []string, errors []error) {
   504  	value := v.(string)
   505  	if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) {
   506  		errors = append(errors, fmt.Errorf(
   507  			"only lowercase alphanumeric characters and hyphens allowed in %q", k))
   508  	}
   509  	if !regexp.MustCompile(`^[a-z]`).MatchString(value) {
   510  		errors = append(errors, fmt.Errorf(
   511  			"first character of %q must be a letter", k))
   512  	}
   513  	if regexp.MustCompile(`--`).MatchString(value) {
   514  		errors = append(errors, fmt.Errorf(
   515  			"%q cannot contain two consecutive hyphens", k))
   516  	}
   517  	if regexp.MustCompile(`-$`).MatchString(value) {
   518  		errors = append(errors, fmt.Errorf(
   519  			"%q cannot end with a hyphen", k))
   520  	}
   521  	return
   522  }
   523  
   524  func validateRedshiftClusterDbName(v interface{}, k string) (ws []string, errors []error) {
   525  	value := v.(string)
   526  	if !regexp.MustCompile(`^[a-z]+$`).MatchString(value) {
   527  		errors = append(errors, fmt.Errorf(
   528  			"only lowercase letters characters allowed in %q", k))
   529  	}
   530  	if len(value) > 64 {
   531  		errors = append(errors, fmt.Errorf(
   532  			"%q cannot be longer than 64 characters: %q", k, value))
   533  	}
   534  	if value == "" {
   535  		errors = append(errors, fmt.Errorf(
   536  			"%q cannot be an empty string", k))
   537  	}
   538  
   539  	return
   540  }
   541  
   542  func validateRedshiftClusterFinalSnapshotIdentifier(v interface{}, k string) (ws []string, errors []error) {
   543  	value := v.(string)
   544  	if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) {
   545  		errors = append(errors, fmt.Errorf(
   546  			"only alphanumeric characters and hyphens allowed in %q", k))
   547  	}
   548  	if regexp.MustCompile(`--`).MatchString(value) {
   549  		errors = append(errors, fmt.Errorf("%q cannot contain two consecutive hyphens", k))
   550  	}
   551  	if regexp.MustCompile(`-$`).MatchString(value) {
   552  		errors = append(errors, fmt.Errorf("%q cannot end in a hyphen", k))
   553  	}
   554  	if len(value) > 255 {
   555  		errors = append(errors, fmt.Errorf("%q cannot be more than 255 characters", k))
   556  	}
   557  	return
   558  }
   559  
   560  func validateRedshiftClusterMasterUsername(v interface{}, k string) (ws []string, errors []error) {
   561  	value := v.(string)
   562  	if !regexp.MustCompile(`^[A-Za-z0-9]+$`).MatchString(value) {
   563  		errors = append(errors, fmt.Errorf(
   564  			"only alphanumeric characters in %q", k))
   565  	}
   566  	if !regexp.MustCompile(`^[A-Za-z]`).MatchString(value) {
   567  		errors = append(errors, fmt.Errorf(
   568  			"first character of %q must be a letter", k))
   569  	}
   570  	if len(value) > 128 {
   571  		errors = append(errors, fmt.Errorf("%q cannot be more than 128 characters", k))
   572  	}
   573  	return
   574  }