github.com/turtlemonvh/terraform@v0.6.9-0.20151204001754-8e40b6b855e8/builtin/providers/aws/resource_aws_rds_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/rds"
    13  	"github.com/hashicorp/terraform/helper/resource"
    14  	"github.com/hashicorp/terraform/helper/schema"
    15  )
    16  
    17  func resourceAwsRDSCluster() *schema.Resource {
    18  	return &schema.Resource{
    19  		Create: resourceAwsRDSClusterCreate,
    20  		Read:   resourceAwsRDSClusterRead,
    21  		Update: resourceAwsRDSClusterUpdate,
    22  		Delete: resourceAwsRDSClusterDelete,
    23  
    24  		Schema: map[string]*schema.Schema{
    25  
    26  			"availability_zones": &schema.Schema{
    27  				Type:     schema.TypeSet,
    28  				Elem:     &schema.Schema{Type: schema.TypeString},
    29  				Optional: true,
    30  				ForceNew: true,
    31  				Computed: true,
    32  				Set:      schema.HashString,
    33  			},
    34  
    35  			"cluster_identifier": &schema.Schema{
    36  				Type:         schema.TypeString,
    37  				Required:     true,
    38  				ForceNew:     true,
    39  				ValidateFunc: validateRdsId,
    40  			},
    41  
    42  			"cluster_members": &schema.Schema{
    43  				Type:     schema.TypeSet,
    44  				Elem:     &schema.Schema{Type: schema.TypeString},
    45  				Optional: true,
    46  				Computed: true,
    47  				Set:      schema.HashString,
    48  			},
    49  
    50  			"database_name": &schema.Schema{
    51  				Type:     schema.TypeString,
    52  				Optional: true,
    53  				Computed: true,
    54  				ForceNew: true,
    55  			},
    56  
    57  			"db_subnet_group_name": &schema.Schema{
    58  				Type:     schema.TypeString,
    59  				Optional: true,
    60  				ForceNew: true,
    61  				Computed: true,
    62  			},
    63  
    64  			"endpoint": &schema.Schema{
    65  				Type:     schema.TypeString,
    66  				Computed: true,
    67  			},
    68  
    69  			"engine": &schema.Schema{
    70  				Type:     schema.TypeString,
    71  				Computed: true,
    72  			},
    73  
    74  			"final_snapshot_identifier": &schema.Schema{
    75  				Type:     schema.TypeString,
    76  				Optional: true,
    77  				ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
    78  					value := v.(string)
    79  					if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) {
    80  						es = append(es, fmt.Errorf(
    81  							"only alphanumeric characters and hyphens allowed in %q", k))
    82  					}
    83  					if regexp.MustCompile(`--`).MatchString(value) {
    84  						es = append(es, fmt.Errorf("%q cannot contain two consecutive hyphens", k))
    85  					}
    86  					if regexp.MustCompile(`-$`).MatchString(value) {
    87  						es = append(es, fmt.Errorf("%q cannot end in a hyphen", k))
    88  					}
    89  					return
    90  				},
    91  			},
    92  
    93  			"master_username": &schema.Schema{
    94  				Type:     schema.TypeString,
    95  				Required: true,
    96  				ForceNew: true,
    97  			},
    98  
    99  			"master_password": &schema.Schema{
   100  				Type:     schema.TypeString,
   101  				Required: true,
   102  			},
   103  
   104  			"port": &schema.Schema{
   105  				Type:     schema.TypeInt,
   106  				Optional: true,
   107  				Computed: true,
   108  			},
   109  
   110  			// apply_immediately is used to determine when the update modifications
   111  			// take place.
   112  			// See http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.DBInstance.Modifying.html
   113  			"apply_immediately": &schema.Schema{
   114  				Type:     schema.TypeBool,
   115  				Optional: true,
   116  				Computed: true,
   117  			},
   118  
   119  			"vpc_security_group_ids": &schema.Schema{
   120  				Type:     schema.TypeSet,
   121  				Optional: true,
   122  				Computed: true,
   123  				Elem:     &schema.Schema{Type: schema.TypeString},
   124  				Set:      schema.HashString,
   125  			},
   126  
   127  			"preferred_backup_window": &schema.Schema{
   128  				Type:     schema.TypeString,
   129  				Optional: true,
   130  				Computed: true,
   131  			},
   132  
   133  			"preferred_maintenance_window": &schema.Schema{
   134  				Type:     schema.TypeString,
   135  				Optional: true,
   136  				Computed: true,
   137  				StateFunc: func(val interface{}) string {
   138  					if val == nil {
   139  						return ""
   140  					}
   141  					return strings.ToLower(val.(string))
   142  				},
   143  			},
   144  
   145  			"backup_retention_period": &schema.Schema{
   146  				Type:     schema.TypeInt,
   147  				Optional: true,
   148  				Default:  1,
   149  				ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
   150  					value := v.(int)
   151  					if value > 35 {
   152  						es = append(es, fmt.Errorf(
   153  							"backup retention period cannot be more than 35 days"))
   154  					}
   155  					return
   156  				},
   157  			},
   158  		},
   159  	}
   160  }
   161  
   162  func resourceAwsRDSClusterCreate(d *schema.ResourceData, meta interface{}) error {
   163  	conn := meta.(*AWSClient).rdsconn
   164  
   165  	createOpts := &rds.CreateDBClusterInput{
   166  		DBClusterIdentifier: aws.String(d.Get("cluster_identifier").(string)),
   167  		Engine:              aws.String("aurora"),
   168  		MasterUserPassword:  aws.String(d.Get("master_password").(string)),
   169  		MasterUsername:      aws.String(d.Get("master_username").(string)),
   170  	}
   171  
   172  	if v := d.Get("database_name"); v.(string) != "" {
   173  		createOpts.DatabaseName = aws.String(v.(string))
   174  	}
   175  
   176  	if attr, ok := d.GetOk("port"); ok {
   177  		createOpts.Port = aws.Int64(int64(attr.(int)))
   178  	}
   179  
   180  	if attr, ok := d.GetOk("db_subnet_group_name"); ok {
   181  		createOpts.DBSubnetGroupName = aws.String(attr.(string))
   182  	}
   183  
   184  	if attr := d.Get("vpc_security_group_ids").(*schema.Set); attr.Len() > 0 {
   185  		createOpts.VpcSecurityGroupIds = expandStringList(attr.List())
   186  	}
   187  
   188  	if attr := d.Get("availability_zones").(*schema.Set); attr.Len() > 0 {
   189  		createOpts.AvailabilityZones = expandStringList(attr.List())
   190  	}
   191  
   192  	if v, ok := d.GetOk("backup_retention_period"); ok {
   193  		createOpts.BackupRetentionPeriod = aws.Int64(int64(v.(int)))
   194  	}
   195  
   196  	if v, ok := d.GetOk("preferred_backup_window"); ok {
   197  		createOpts.PreferredBackupWindow = aws.String(v.(string))
   198  	}
   199  
   200  	if v, ok := d.GetOk("preferred_maintenance_window"); ok {
   201  		createOpts.PreferredMaintenanceWindow = aws.String(v.(string))
   202  	}
   203  
   204  	log.Printf("[DEBUG] RDS Cluster create options: %s", createOpts)
   205  	resp, err := conn.CreateDBCluster(createOpts)
   206  	if err != nil {
   207  		log.Printf("[ERROR] Error creating RDS Cluster: %s", err)
   208  		return err
   209  	}
   210  
   211  	log.Printf("[DEBUG]: Cluster create response: %s", resp)
   212  	d.SetId(*resp.DBCluster.DBClusterIdentifier)
   213  	stateConf := &resource.StateChangeConf{
   214  		Pending:    []string{"creating", "backing-up", "modifying"},
   215  		Target:     "available",
   216  		Refresh:    resourceAwsRDSClusterStateRefreshFunc(d, meta),
   217  		Timeout:    5 * time.Minute,
   218  		MinTimeout: 3 * time.Second,
   219  	}
   220  
   221  	// Wait, catching any errors
   222  	_, err = stateConf.WaitForState()
   223  	if err != nil {
   224  		return fmt.Errorf("[WARN] Error waiting for RDS Cluster state to be \"available\": %s", err)
   225  	}
   226  
   227  	return resourceAwsRDSClusterRead(d, meta)
   228  }
   229  
   230  func resourceAwsRDSClusterRead(d *schema.ResourceData, meta interface{}) error {
   231  	conn := meta.(*AWSClient).rdsconn
   232  
   233  	resp, err := conn.DescribeDBClusters(&rds.DescribeDBClustersInput{
   234  		DBClusterIdentifier: aws.String(d.Id()),
   235  	})
   236  
   237  	if err != nil {
   238  		if awsErr, ok := err.(awserr.Error); ok {
   239  			if "DBClusterNotFoundFault" == awsErr.Code() {
   240  				d.SetId("")
   241  				log.Printf("[DEBUG] RDS Cluster (%s) not found", d.Id())
   242  				return nil
   243  			}
   244  		}
   245  		log.Printf("[DEBUG] Error describing RDS Cluster (%s)", d.Id())
   246  		return err
   247  	}
   248  
   249  	var dbc *rds.DBCluster
   250  	for _, c := range resp.DBClusters {
   251  		if *c.DBClusterIdentifier == d.Id() {
   252  			dbc = c
   253  		}
   254  	}
   255  
   256  	if dbc == nil {
   257  		log.Printf("[WARN] RDS Cluster (%s) not found", d.Id())
   258  		d.SetId("")
   259  		return nil
   260  	}
   261  
   262  	if err := d.Set("availability_zones", aws.StringValueSlice(dbc.AvailabilityZones)); err != nil {
   263  		return fmt.Errorf("[DEBUG] Error saving AvailabilityZones to state for RDS Cluster (%s): %s", d.Id(), err)
   264  	}
   265  	d.Set("database_name", dbc.DatabaseName)
   266  	d.Set("db_subnet_group_name", dbc.DBSubnetGroup)
   267  	d.Set("endpoint", dbc.Endpoint)
   268  	d.Set("engine", dbc.Engine)
   269  	d.Set("master_username", dbc.MasterUsername)
   270  	d.Set("port", dbc.Port)
   271  	d.Set("backup_retention_period", dbc.BackupRetentionPeriod)
   272  	d.Set("preferred_backup_window", dbc.PreferredBackupWindow)
   273  	d.Set("preferred_maintenance_window", dbc.PreferredMaintenanceWindow)
   274  
   275  	var vpcg []string
   276  	for _, g := range dbc.VpcSecurityGroups {
   277  		vpcg = append(vpcg, *g.VpcSecurityGroupId)
   278  	}
   279  	if err := d.Set("vpc_security_group_ids", vpcg); err != nil {
   280  		return fmt.Errorf("[DEBUG] Error saving VPC Security Group IDs to state for RDS Cluster (%s): %s", d.Id(), err)
   281  	}
   282  
   283  	var cm []string
   284  	for _, m := range dbc.DBClusterMembers {
   285  		cm = append(cm, *m.DBInstanceIdentifier)
   286  	}
   287  	if err := d.Set("cluster_members", cm); err != nil {
   288  		return fmt.Errorf("[DEBUG] Error saving RDS Cluster Members to state for RDS Cluster (%s): %s", d.Id(), err)
   289  	}
   290  
   291  	return nil
   292  }
   293  
   294  func resourceAwsRDSClusterUpdate(d *schema.ResourceData, meta interface{}) error {
   295  	conn := meta.(*AWSClient).rdsconn
   296  
   297  	req := &rds.ModifyDBClusterInput{
   298  		ApplyImmediately:    aws.Bool(d.Get("apply_immediately").(bool)),
   299  		DBClusterIdentifier: aws.String(d.Id()),
   300  	}
   301  
   302  	if d.HasChange("master_password") {
   303  		req.MasterUserPassword = aws.String(d.Get("master_password").(string))
   304  	}
   305  
   306  	if d.HasChange("vpc_security_group_ids") {
   307  		if attr := d.Get("vpc_security_group_ids").(*schema.Set); attr.Len() > 0 {
   308  			req.VpcSecurityGroupIds = expandStringList(attr.List())
   309  		} else {
   310  			req.VpcSecurityGroupIds = []*string{}
   311  		}
   312  	}
   313  
   314  	if d.HasChange("preferred_backup_window") {
   315  		req.PreferredBackupWindow = aws.String(d.Get("preferred_backup_window").(string))
   316  	}
   317  
   318  	if d.HasChange("preferred_maintenance_window") {
   319  		req.PreferredMaintenanceWindow = aws.String(d.Get("preferred_maintenance_window").(string))
   320  	}
   321  
   322  	if d.HasChange("backup_retention_period") {
   323  		req.BackupRetentionPeriod = aws.Int64(int64(d.Get("backup_retention_period").(int)))
   324  	}
   325  
   326  	_, err := conn.ModifyDBCluster(req)
   327  	if err != nil {
   328  		return fmt.Errorf("[WARN] Error modifying RDS Cluster (%s): %s", d.Id(), err)
   329  	}
   330  
   331  	return resourceAwsRDSClusterRead(d, meta)
   332  }
   333  
   334  func resourceAwsRDSClusterDelete(d *schema.ResourceData, meta interface{}) error {
   335  	conn := meta.(*AWSClient).rdsconn
   336  	log.Printf("[DEBUG] Destroying RDS Cluster (%s)", d.Id())
   337  
   338  	deleteOpts := rds.DeleteDBClusterInput{
   339  		DBClusterIdentifier: aws.String(d.Id()),
   340  	}
   341  
   342  	finalSnapshot := d.Get("final_snapshot_identifier").(string)
   343  	if finalSnapshot == "" {
   344  		deleteOpts.SkipFinalSnapshot = aws.Bool(true)
   345  	} else {
   346  		deleteOpts.FinalDBSnapshotIdentifier = aws.String(finalSnapshot)
   347  		deleteOpts.SkipFinalSnapshot = aws.Bool(false)
   348  	}
   349  
   350  	log.Printf("[DEBUG] RDS Cluster delete options: %s", deleteOpts)
   351  	_, err := conn.DeleteDBCluster(&deleteOpts)
   352  
   353  	stateConf := &resource.StateChangeConf{
   354  		Pending:    []string{"deleting", "backing-up", "modifying"},
   355  		Target:     "destroyed",
   356  		Refresh:    resourceAwsRDSClusterStateRefreshFunc(d, meta),
   357  		Timeout:    5 * time.Minute,
   358  		MinTimeout: 3 * time.Second,
   359  	}
   360  
   361  	// Wait, catching any errors
   362  	_, err = stateConf.WaitForState()
   363  	if err != nil {
   364  		return fmt.Errorf("[WARN] Error deleting RDS Cluster (%s): %s", d.Id(), err)
   365  	}
   366  
   367  	return nil
   368  }
   369  
   370  func resourceAwsRDSClusterStateRefreshFunc(
   371  	d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc {
   372  	return func() (interface{}, string, error) {
   373  		conn := meta.(*AWSClient).rdsconn
   374  
   375  		resp, err := conn.DescribeDBClusters(&rds.DescribeDBClustersInput{
   376  			DBClusterIdentifier: aws.String(d.Id()),
   377  		})
   378  
   379  		if err != nil {
   380  			if awsErr, ok := err.(awserr.Error); ok {
   381  				if "DBClusterNotFoundFault" == awsErr.Code() {
   382  					return 42, "destroyed", nil
   383  				}
   384  			}
   385  			log.Printf("[WARN] Error on retrieving DB Cluster (%s) when waiting: %s", d.Id(), err)
   386  			return nil, "", err
   387  		}
   388  
   389  		var dbc *rds.DBCluster
   390  
   391  		for _, c := range resp.DBClusters {
   392  			if *c.DBClusterIdentifier == d.Id() {
   393  				dbc = c
   394  			}
   395  		}
   396  
   397  		if dbc == nil {
   398  			return 42, "destroyed", nil
   399  		}
   400  
   401  		if dbc.Status != nil {
   402  			log.Printf("[DEBUG] DB Cluster status (%s): %s", d.Id(), *dbc.Status)
   403  		}
   404  
   405  		return dbc, *dbc.Status, nil
   406  	}
   407  }