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 }