github.com/gabrielperezs/terraform@v0.7.0-rc2.0.20160715084931-f7da2612946f/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 // TODO: remove parameter_group_name 65 // See https://github.com/hashicorp/terraform/issues/7046 66 // Likely need migration to remove from state 67 "parameter_group_name": &schema.Schema{ 68 Type: schema.TypeString, 69 Optional: true, 70 Computed: true, 71 Deprecated: "Use db_cluster_parameter_group_name instead. This attribute will be removed in a future version", 72 }, 73 74 "db_cluster_parameter_group_name": &schema.Schema{ 75 Type: schema.TypeString, 76 Optional: true, 77 Computed: true, 78 }, 79 80 "endpoint": &schema.Schema{ 81 Type: schema.TypeString, 82 Computed: true, 83 }, 84 85 "engine": &schema.Schema{ 86 Type: schema.TypeString, 87 Computed: true, 88 }, 89 90 "storage_encrypted": &schema.Schema{ 91 Type: schema.TypeBool, 92 Optional: true, 93 Default: false, 94 ForceNew: true, 95 }, 96 97 "final_snapshot_identifier": &schema.Schema{ 98 Type: schema.TypeString, 99 Optional: true, 100 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 101 value := v.(string) 102 if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) { 103 es = append(es, fmt.Errorf( 104 "only alphanumeric characters and hyphens allowed in %q", k)) 105 } 106 if regexp.MustCompile(`--`).MatchString(value) { 107 es = append(es, fmt.Errorf("%q cannot contain two consecutive hyphens", k)) 108 } 109 if regexp.MustCompile(`-$`).MatchString(value) { 110 es = append(es, fmt.Errorf("%q cannot end in a hyphen", k)) 111 } 112 return 113 }, 114 }, 115 116 "skip_final_snapshot": &schema.Schema{ 117 Type: schema.TypeBool, 118 Optional: true, 119 Default: true, 120 }, 121 122 "master_username": &schema.Schema{ 123 Type: schema.TypeString, 124 Computed: true, 125 Optional: true, 126 ForceNew: true, 127 }, 128 129 "master_password": &schema.Schema{ 130 Type: schema.TypeString, 131 Optional: true, 132 }, 133 134 "snapshot_identifier": &schema.Schema{ 135 Type: schema.TypeString, 136 Computed: false, 137 Optional: true, 138 Elem: &schema.Schema{Type: schema.TypeString}, 139 }, 140 141 "port": &schema.Schema{ 142 Type: schema.TypeInt, 143 Optional: true, 144 Computed: true, 145 }, 146 147 // apply_immediately is used to determine when the update modifications 148 // take place. 149 // See http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.DBInstance.Modifying.html 150 "apply_immediately": &schema.Schema{ 151 Type: schema.TypeBool, 152 Optional: true, 153 Computed: true, 154 }, 155 156 "vpc_security_group_ids": &schema.Schema{ 157 Type: schema.TypeSet, 158 Optional: true, 159 Computed: true, 160 Elem: &schema.Schema{Type: schema.TypeString}, 161 Set: schema.HashString, 162 }, 163 164 "preferred_backup_window": &schema.Schema{ 165 Type: schema.TypeString, 166 Optional: true, 167 Computed: true, 168 }, 169 170 "preferred_maintenance_window": &schema.Schema{ 171 Type: schema.TypeString, 172 Optional: true, 173 Computed: true, 174 StateFunc: func(val interface{}) string { 175 if val == nil { 176 return "" 177 } 178 return strings.ToLower(val.(string)) 179 }, 180 }, 181 182 "backup_retention_period": &schema.Schema{ 183 Type: schema.TypeInt, 184 Optional: true, 185 Default: 1, 186 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 187 value := v.(int) 188 if value > 35 { 189 es = append(es, fmt.Errorf( 190 "backup retention period cannot be more than 35 days")) 191 } 192 return 193 }, 194 }, 195 }, 196 } 197 } 198 199 func resourceAwsRDSClusterCreate(d *schema.ResourceData, meta interface{}) error { 200 conn := meta.(*AWSClient).rdsconn 201 202 if _, ok := d.GetOk("snapshot_identifier"); ok { 203 opts := rds.RestoreDBClusterFromSnapshotInput{ 204 DBClusterIdentifier: aws.String(d.Get("cluster_identifier").(string)), 205 SnapshotIdentifier: aws.String(d.Get("snapshot_identifier").(string)), 206 Engine: aws.String("aurora"), 207 } 208 209 if attr := d.Get("availability_zones").(*schema.Set); attr.Len() > 0 { 210 opts.AvailabilityZones = expandStringList(attr.List()) 211 } 212 213 if attr, ok := d.GetOk("db_subnet_group_name"); ok { 214 opts.DBSubnetGroupName = aws.String(attr.(string)) 215 } 216 217 if attr, ok := d.GetOk("database_name"); ok { 218 opts.DatabaseName = aws.String(attr.(string)) 219 } 220 221 if attr, ok := d.GetOk("option_group_name"); ok { 222 opts.OptionGroupName = aws.String(attr.(string)) 223 } 224 225 if attr, ok := d.GetOk("port"); ok { 226 opts.Port = aws.Int64(int64(attr.(int))) 227 } 228 229 var sgUpdate bool 230 if attr := d.Get("vpc_security_group_ids").(*schema.Set); attr.Len() > 0 { 231 sgUpdate = true 232 opts.VpcSecurityGroupIds = expandStringList(attr.List()) 233 } 234 235 log.Printf("[DEBUG] RDS Cluster restore from snapshot configuration: %s", opts) 236 _, err := conn.RestoreDBClusterFromSnapshot(&opts) 237 if err != nil { 238 return fmt.Errorf("Error creating RDS Cluster: %s", err) 239 } 240 241 if sgUpdate { 242 log.Printf("[INFO] RDS Cluster is restoring from snapshot with default security, but custom security should be set, will now update after snapshot is restored!") 243 244 d.SetId(d.Get("cluster_identifier").(string)) 245 246 log.Printf("[INFO] RDS Cluster Instance ID: %s", d.Id()) 247 248 log.Println("[INFO] Waiting for RDS Cluster to be available") 249 250 stateConf := &resource.StateChangeConf{ 251 Pending: []string{"creating", "backing-up", "modifying"}, 252 Target: []string{"available"}, 253 Refresh: resourceAwsRDSClusterStateRefreshFunc(d, meta), 254 Timeout: 5 * time.Minute, 255 MinTimeout: 3 * time.Second, 256 Delay: 30 * time.Second, // Wait 30 secs before starting 257 } 258 259 // Wait, catching any errors 260 _, err := stateConf.WaitForState() 261 if err != nil { 262 return err 263 } 264 265 err = resourceAwsRDSClusterInstanceUpdate(d, meta) 266 if err != nil { 267 return err 268 } 269 } 270 } else { 271 if _, ok := d.GetOk("master_password"); !ok { 272 return fmt.Errorf(`provider.aws: aws_rds_cluster: %s: "master_password": required field is not set`, d.Get("name").(string)) 273 } 274 275 if _, ok := d.GetOk("master_username"); !ok { 276 return fmt.Errorf(`provider.aws: aws_rds_cluster: %s: "master_username": required field is not set`, d.Get("name").(string)) 277 } 278 279 createOpts := &rds.CreateDBClusterInput{ 280 DBClusterIdentifier: aws.String(d.Get("cluster_identifier").(string)), 281 Engine: aws.String("aurora"), 282 MasterUserPassword: aws.String(d.Get("master_password").(string)), 283 MasterUsername: aws.String(d.Get("master_username").(string)), 284 StorageEncrypted: aws.Bool(d.Get("storage_encrypted").(bool)), 285 } 286 287 if v := d.Get("database_name"); v.(string) != "" { 288 createOpts.DatabaseName = aws.String(v.(string)) 289 } 290 291 if attr, ok := d.GetOk("port"); ok { 292 createOpts.Port = aws.Int64(int64(attr.(int))) 293 } 294 295 if attr, ok := d.GetOk("db_subnet_group_name"); ok { 296 createOpts.DBSubnetGroupName = aws.String(attr.(string)) 297 } 298 299 if attr, ok := d.GetOk("parameter_group_name"); ok { 300 createOpts.DBClusterParameterGroupName = aws.String(attr.(string)) 301 } 302 303 if attr, ok := d.GetOk("db_cluster_parameter_group_name"); ok { 304 createOpts.DBClusterParameterGroupName = aws.String(attr.(string)) 305 } 306 307 if attr := d.Get("vpc_security_group_ids").(*schema.Set); attr.Len() > 0 { 308 createOpts.VpcSecurityGroupIds = expandStringList(attr.List()) 309 } 310 311 if attr := d.Get("availability_zones").(*schema.Set); attr.Len() > 0 { 312 createOpts.AvailabilityZones = expandStringList(attr.List()) 313 } 314 315 if v, ok := d.GetOk("backup_retention_period"); ok { 316 createOpts.BackupRetentionPeriod = aws.Int64(int64(v.(int))) 317 } 318 319 if v, ok := d.GetOk("preferred_backup_window"); ok { 320 createOpts.PreferredBackupWindow = aws.String(v.(string)) 321 } 322 323 if v, ok := d.GetOk("preferred_maintenance_window"); ok { 324 createOpts.PreferredMaintenanceWindow = aws.String(v.(string)) 325 } 326 327 log.Printf("[DEBUG] RDS Cluster create options: %s", createOpts) 328 resp, err := conn.CreateDBCluster(createOpts) 329 if err != nil { 330 log.Printf("[ERROR] Error creating RDS Cluster: %s", err) 331 return err 332 } 333 334 log.Printf("[DEBUG]: RDS Cluster create response: %s", resp) 335 } 336 337 d.SetId(d.Get("cluster_identifier").(string)) 338 339 log.Printf("[INFO] RDS Cluster ID: %s", d.Id()) 340 341 log.Println( 342 "[INFO] Waiting for RDS Cluster to be available") 343 344 stateConf := &resource.StateChangeConf{ 345 Pending: []string{"creating", "backing-up", "modifying"}, 346 Target: []string{"available"}, 347 Refresh: resourceAwsRDSClusterStateRefreshFunc(d, meta), 348 Timeout: 5 * time.Minute, 349 MinTimeout: 3 * time.Second, 350 } 351 352 // Wait, catching any errors 353 _, err := stateConf.WaitForState() 354 if err != nil { 355 return fmt.Errorf("[WARN] Error waiting for RDS Cluster state to be \"available\": %s", err) 356 } 357 358 return resourceAwsRDSClusterRead(d, meta) 359 } 360 361 func resourceAwsRDSClusterRead(d *schema.ResourceData, meta interface{}) error { 362 conn := meta.(*AWSClient).rdsconn 363 364 resp, err := conn.DescribeDBClusters(&rds.DescribeDBClustersInput{ 365 DBClusterIdentifier: aws.String(d.Id()), 366 }) 367 368 if err != nil { 369 if awsErr, ok := err.(awserr.Error); ok { 370 if "DBClusterNotFoundFault" == awsErr.Code() { 371 d.SetId("") 372 log.Printf("[DEBUG] RDS Cluster (%s) not found", d.Id()) 373 return nil 374 } 375 } 376 log.Printf("[DEBUG] Error describing RDS Cluster (%s)", d.Id()) 377 return err 378 } 379 380 var dbc *rds.DBCluster 381 for _, c := range resp.DBClusters { 382 if *c.DBClusterIdentifier == d.Id() { 383 dbc = c 384 } 385 } 386 387 if dbc == nil { 388 log.Printf("[WARN] RDS Cluster (%s) not found", d.Id()) 389 d.SetId("") 390 return nil 391 } 392 393 if err := d.Set("availability_zones", aws.StringValueSlice(dbc.AvailabilityZones)); err != nil { 394 return fmt.Errorf("[DEBUG] Error saving AvailabilityZones to state for RDS Cluster (%s): %s", d.Id(), err) 395 } 396 397 // Only set the DatabaseName if it is not nil. There is a known API bug where 398 // RDS accepts a DatabaseName but does not return it, causing a perpetual 399 // diff. 400 // See https://github.com/hashicorp/terraform/issues/4671 for backstory 401 if dbc.DatabaseName != nil { 402 d.Set("database_name", dbc.DatabaseName) 403 } 404 405 d.Set("db_subnet_group_name", dbc.DBSubnetGroup) 406 d.Set("parameter_group_name", dbc.DBClusterParameterGroup) 407 d.Set("db_cluster_parameter_group_name", dbc.DBClusterParameterGroup) 408 d.Set("endpoint", dbc.Endpoint) 409 d.Set("engine", dbc.Engine) 410 d.Set("master_username", dbc.MasterUsername) 411 d.Set("port", dbc.Port) 412 d.Set("storage_encrypted", dbc.StorageEncrypted) 413 d.Set("backup_retention_period", dbc.BackupRetentionPeriod) 414 d.Set("preferred_backup_window", dbc.PreferredBackupWindow) 415 d.Set("preferred_maintenance_window", dbc.PreferredMaintenanceWindow) 416 417 var vpcg []string 418 for _, g := range dbc.VpcSecurityGroups { 419 vpcg = append(vpcg, *g.VpcSecurityGroupId) 420 } 421 if err := d.Set("vpc_security_group_ids", vpcg); err != nil { 422 return fmt.Errorf("[DEBUG] Error saving VPC Security Group IDs to state for RDS Cluster (%s): %s", d.Id(), err) 423 } 424 425 var cm []string 426 for _, m := range dbc.DBClusterMembers { 427 cm = append(cm, *m.DBInstanceIdentifier) 428 } 429 if err := d.Set("cluster_members", cm); err != nil { 430 return fmt.Errorf("[DEBUG] Error saving RDS Cluster Members to state for RDS Cluster (%s): %s", d.Id(), err) 431 } 432 433 return nil 434 } 435 436 func resourceAwsRDSClusterUpdate(d *schema.ResourceData, meta interface{}) error { 437 conn := meta.(*AWSClient).rdsconn 438 439 req := &rds.ModifyDBClusterInput{ 440 ApplyImmediately: aws.Bool(d.Get("apply_immediately").(bool)), 441 DBClusterIdentifier: aws.String(d.Id()), 442 } 443 444 if d.HasChange("master_password") { 445 req.MasterUserPassword = aws.String(d.Get("master_password").(string)) 446 } 447 448 if d.HasChange("vpc_security_group_ids") { 449 if attr := d.Get("vpc_security_group_ids").(*schema.Set); attr.Len() > 0 { 450 req.VpcSecurityGroupIds = expandStringList(attr.List()) 451 } else { 452 req.VpcSecurityGroupIds = []*string{} 453 } 454 } 455 456 if d.HasChange("preferred_backup_window") { 457 req.PreferredBackupWindow = aws.String(d.Get("preferred_backup_window").(string)) 458 } 459 460 if d.HasChange("preferred_maintenance_window") { 461 req.PreferredMaintenanceWindow = aws.String(d.Get("preferred_maintenance_window").(string)) 462 } 463 464 if d.HasChange("backup_retention_period") { 465 req.BackupRetentionPeriod = aws.Int64(int64(d.Get("backup_retention_period").(int))) 466 } 467 468 if d.HasChange("parameter_group_name") { 469 d.SetPartial("parameter_group_name") 470 req.DBClusterParameterGroupName = aws.String(d.Get("parameter_group_name").(string)) 471 } 472 473 if d.HasChange("db_cluster_parameter_group_name") { 474 d.SetPartial("db_cluster_parameter_group_name") 475 req.DBClusterParameterGroupName = aws.String(d.Get("db_cluster_parameter_group_name").(string)) 476 } 477 478 _, err := conn.ModifyDBCluster(req) 479 if err != nil { 480 return fmt.Errorf("[WARN] Error modifying RDS Cluster (%s): %s", d.Id(), err) 481 } 482 483 return resourceAwsRDSClusterRead(d, meta) 484 } 485 486 func resourceAwsRDSClusterDelete(d *schema.ResourceData, meta interface{}) error { 487 conn := meta.(*AWSClient).rdsconn 488 log.Printf("[DEBUG] Destroying RDS Cluster (%s)", d.Id()) 489 490 deleteOpts := rds.DeleteDBClusterInput{ 491 DBClusterIdentifier: aws.String(d.Id()), 492 } 493 494 skipFinalSnapshot := d.Get("skip_final_snapshot").(bool) 495 deleteOpts.SkipFinalSnapshot = aws.Bool(skipFinalSnapshot) 496 497 if skipFinalSnapshot == false { 498 if name, present := d.GetOk("final_snapshot_identifier"); present { 499 deleteOpts.FinalDBSnapshotIdentifier = aws.String(name.(string)) 500 } else { 501 return fmt.Errorf("RDS Cluster FinalSnapshotIdentifier is required when a final snapshot is required") 502 } 503 } 504 505 log.Printf("[DEBUG] RDS Cluster delete options: %s", deleteOpts) 506 _, err := conn.DeleteDBCluster(&deleteOpts) 507 508 stateConf := &resource.StateChangeConf{ 509 Pending: []string{"available", "deleting", "backing-up", "modifying"}, 510 Target: []string{"destroyed"}, 511 Refresh: resourceAwsRDSClusterStateRefreshFunc(d, meta), 512 Timeout: 5 * time.Minute, 513 MinTimeout: 3 * time.Second, 514 } 515 516 // Wait, catching any errors 517 _, err = stateConf.WaitForState() 518 if err != nil { 519 return fmt.Errorf("[WARN] Error deleting RDS Cluster (%s): %s", d.Id(), err) 520 } 521 522 return nil 523 } 524 525 func resourceAwsRDSClusterStateRefreshFunc( 526 d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc { 527 return func() (interface{}, string, error) { 528 conn := meta.(*AWSClient).rdsconn 529 530 resp, err := conn.DescribeDBClusters(&rds.DescribeDBClustersInput{ 531 DBClusterIdentifier: aws.String(d.Id()), 532 }) 533 534 if err != nil { 535 if awsErr, ok := err.(awserr.Error); ok { 536 if "DBClusterNotFoundFault" == awsErr.Code() { 537 return 42, "destroyed", nil 538 } 539 } 540 log.Printf("[WARN] Error on retrieving DB Cluster (%s) when waiting: %s", d.Id(), err) 541 return nil, "", err 542 } 543 544 var dbc *rds.DBCluster 545 546 for _, c := range resp.DBClusters { 547 if *c.DBClusterIdentifier == d.Id() { 548 dbc = c 549 } 550 } 551 552 if dbc == nil { 553 return 42, "destroyed", nil 554 } 555 556 if dbc.Status != nil { 557 log.Printf("[DEBUG] DB Cluster status (%s): %s", d.Id(), *dbc.Status) 558 } 559 560 return dbc, *dbc.Status, nil 561 } 562 }