github.com/jsoriano/terraform@v0.6.7-0.20151026070445-8b70867fdd95/builtin/providers/aws/resource_aws_rds_cluster.go (about)

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