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