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