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