github.com/bendemaree/terraform@v0.5.4-0.20150613200311-f50d97d6eee6/builtin/providers/aws/resource_aws_db_instance.go (about) 1 package aws 2 3 import ( 4 "fmt" 5 "log" 6 "strings" 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/iam" 12 "github.com/aws/aws-sdk-go/service/rds" 13 14 "github.com/hashicorp/terraform/helper/resource" 15 "github.com/hashicorp/terraform/helper/schema" 16 ) 17 18 func resourceAwsDbInstance() *schema.Resource { 19 return &schema.Resource{ 20 Create: resourceAwsDbInstanceCreate, 21 Read: resourceAwsDbInstanceRead, 22 Update: resourceAwsDbInstanceUpdate, 23 Delete: resourceAwsDbInstanceDelete, 24 25 Schema: map[string]*schema.Schema{ 26 "name": &schema.Schema{ 27 Type: schema.TypeString, 28 Optional: true, 29 Computed: true, 30 ForceNew: true, 31 }, 32 33 "username": &schema.Schema{ 34 Type: schema.TypeString, 35 Required: true, 36 ForceNew: true, 37 }, 38 39 "password": &schema.Schema{ 40 Type: schema.TypeString, 41 Required: true, 42 }, 43 44 "engine": &schema.Schema{ 45 Type: schema.TypeString, 46 Required: true, 47 ForceNew: true, 48 }, 49 50 "engine_version": &schema.Schema{ 51 Type: schema.TypeString, 52 Required: true, 53 }, 54 55 "storage_encrypted": &schema.Schema{ 56 Type: schema.TypeBool, 57 Optional: true, 58 ForceNew: true, 59 }, 60 61 "allocated_storage": &schema.Schema{ 62 Type: schema.TypeInt, 63 Required: true, 64 }, 65 66 "storage_type": &schema.Schema{ 67 Type: schema.TypeString, 68 Optional: true, 69 Computed: true, 70 }, 71 72 "identifier": &schema.Schema{ 73 Type: schema.TypeString, 74 Required: true, 75 ForceNew: true, 76 }, 77 78 "instance_class": &schema.Schema{ 79 Type: schema.TypeString, 80 Required: true, 81 }, 82 83 "availability_zone": &schema.Schema{ 84 Type: schema.TypeString, 85 Optional: true, 86 Computed: true, 87 ForceNew: true, 88 }, 89 90 "backup_retention_period": &schema.Schema{ 91 Type: schema.TypeInt, 92 Optional: true, 93 Computed: true, 94 }, 95 96 "backup_window": &schema.Schema{ 97 Type: schema.TypeString, 98 Optional: true, 99 Computed: true, 100 }, 101 102 "iops": &schema.Schema{ 103 Type: schema.TypeInt, 104 Optional: true, 105 }, 106 107 "license_model": &schema.Schema{ 108 Type: schema.TypeString, 109 Optional: true, 110 Computed: true, 111 }, 112 113 "maintenance_window": &schema.Schema{ 114 Type: schema.TypeString, 115 Optional: true, 116 Computed: true, 117 }, 118 119 "multi_az": &schema.Schema{ 120 Type: schema.TypeBool, 121 Optional: true, 122 Computed: true, 123 }, 124 125 "port": &schema.Schema{ 126 Type: schema.TypeInt, 127 Optional: true, 128 Computed: true, 129 ForceNew: true, 130 }, 131 132 "publicly_accessible": &schema.Schema{ 133 Type: schema.TypeBool, 134 Optional: true, 135 ForceNew: true, 136 }, 137 138 "vpc_security_group_ids": &schema.Schema{ 139 Type: schema.TypeSet, 140 Optional: true, 141 Computed: true, 142 Elem: &schema.Schema{Type: schema.TypeString}, 143 Set: schema.HashString, 144 }, 145 146 "security_group_names": &schema.Schema{ 147 Type: schema.TypeSet, 148 Optional: true, 149 Elem: &schema.Schema{Type: schema.TypeString}, 150 Set: schema.HashString, 151 }, 152 153 "final_snapshot_identifier": &schema.Schema{ 154 Type: schema.TypeString, 155 Optional: true, 156 }, 157 158 "db_subnet_group_name": &schema.Schema{ 159 Type: schema.TypeString, 160 Optional: true, 161 ForceNew: true, 162 Computed: true, 163 }, 164 165 "parameter_group_name": &schema.Schema{ 166 Type: schema.TypeString, 167 Optional: true, 168 Computed: true, 169 }, 170 171 "address": &schema.Schema{ 172 Type: schema.TypeString, 173 Computed: true, 174 }, 175 176 "endpoint": &schema.Schema{ 177 Type: schema.TypeString, 178 Computed: true, 179 }, 180 181 "status": &schema.Schema{ 182 Type: schema.TypeString, 183 Computed: true, 184 }, 185 186 // apply_immediately is used to determine when the update modifications 187 // take place. 188 // See http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.DBInstance.Modifying.html 189 "apply_immediately": &schema.Schema{ 190 Type: schema.TypeBool, 191 Optional: true, 192 Computed: true, 193 }, 194 195 "replicate_source_db": &schema.Schema{ 196 Type: schema.TypeString, 197 Optional: true, 198 }, 199 200 "replicas": &schema.Schema{ 201 Type: schema.TypeList, 202 Computed: true, 203 Elem: &schema.Schema{Type: schema.TypeString}, 204 }, 205 206 "tags": tagsSchema(), 207 }, 208 } 209 } 210 211 func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error { 212 conn := meta.(*AWSClient).rdsconn 213 tags := tagsFromMapRDS(d.Get("tags").(map[string]interface{})) 214 215 if v, ok := d.GetOk("replicate_source_db"); ok { 216 opts := rds.CreateDBInstanceReadReplicaInput{ 217 SourceDBInstanceIdentifier: aws.String(v.(string)), 218 DBInstanceClass: aws.String(d.Get("instance_class").(string)), 219 DBInstanceIdentifier: aws.String(d.Get("identifier").(string)), 220 Tags: tags, 221 } 222 if attr, ok := d.GetOk("iops"); ok { 223 opts.IOPS = aws.Long(int64(attr.(int))) 224 } 225 226 if attr, ok := d.GetOk("port"); ok { 227 opts.Port = aws.Long(int64(attr.(int))) 228 } 229 230 if attr, ok := d.GetOk("availability_zone"); ok { 231 opts.AvailabilityZone = aws.String(attr.(string)) 232 } 233 234 if attr, ok := d.GetOk("publicly_accessible"); ok { 235 opts.PubliclyAccessible = aws.Boolean(attr.(bool)) 236 } 237 _, err := conn.CreateDBInstanceReadReplica(&opts) 238 if err != nil { 239 return fmt.Errorf("Error creating DB Instance: %s", err) 240 } 241 } else { 242 opts := rds.CreateDBInstanceInput{ 243 AllocatedStorage: aws.Long(int64(d.Get("allocated_storage").(int))), 244 DBName: aws.String(d.Get("name").(string)), 245 DBInstanceClass: aws.String(d.Get("instance_class").(string)), 246 DBInstanceIdentifier: aws.String(d.Get("identifier").(string)), 247 MasterUsername: aws.String(d.Get("username").(string)), 248 MasterUserPassword: aws.String(d.Get("password").(string)), 249 Engine: aws.String(d.Get("engine").(string)), 250 EngineVersion: aws.String(d.Get("engine_version").(string)), 251 StorageEncrypted: aws.Boolean(d.Get("storage_encrypted").(bool)), 252 Tags: tags, 253 } 254 255 attr := d.Get("backup_retention_period") 256 opts.BackupRetentionPeriod = aws.Long(int64(attr.(int))) 257 if attr, ok := d.GetOk("multi_az"); ok { 258 opts.MultiAZ = aws.Boolean(attr.(bool)) 259 } 260 261 if attr, ok := d.GetOk("maintenance_window"); ok { 262 opts.PreferredMaintenanceWindow = aws.String(attr.(string)) 263 } 264 265 if attr, ok := d.GetOk("backup_window"); ok { 266 opts.PreferredBackupWindow = aws.String(attr.(string)) 267 } 268 269 if attr, ok := d.GetOk("license_model"); ok { 270 opts.LicenseModel = aws.String(attr.(string)) 271 } 272 if attr, ok := d.GetOk("parameter_group_name"); ok { 273 opts.DBParameterGroupName = aws.String(attr.(string)) 274 } 275 276 if attr := d.Get("vpc_security_group_ids").(*schema.Set); attr.Len() > 0 { 277 var s []*string 278 for _, v := range attr.List() { 279 s = append(s, aws.String(v.(string))) 280 } 281 opts.VPCSecurityGroupIDs = s 282 } 283 284 if attr := d.Get("security_group_names").(*schema.Set); attr.Len() > 0 { 285 var s []*string 286 for _, v := range attr.List() { 287 s = append(s, aws.String(v.(string))) 288 } 289 opts.DBSecurityGroups = s 290 } 291 if attr, ok := d.GetOk("storage_type"); ok { 292 opts.StorageType = aws.String(attr.(string)) 293 } 294 295 if attr, ok := d.GetOk("db_subnet_group_name"); ok { 296 opts.DBSubnetGroupName = aws.String(attr.(string)) 297 } 298 299 if attr, ok := d.GetOk("iops"); ok { 300 opts.IOPS = aws.Long(int64(attr.(int))) 301 } 302 303 if attr, ok := d.GetOk("port"); ok { 304 opts.Port = aws.Long(int64(attr.(int))) 305 } 306 307 if attr, ok := d.GetOk("availability_zone"); ok { 308 opts.AvailabilityZone = aws.String(attr.(string)) 309 } 310 311 if attr, ok := d.GetOk("publicly_accessible"); ok { 312 opts.PubliclyAccessible = aws.Boolean(attr.(bool)) 313 } 314 315 log.Printf("[DEBUG] DB Instance create configuration: %#v", opts) 316 var err error 317 _, err = conn.CreateDBInstance(&opts) 318 if err != nil { 319 return fmt.Errorf("Error creating DB Instance: %s", err) 320 } 321 } 322 323 d.SetId(d.Get("identifier").(string)) 324 325 log.Printf("[INFO] DB Instance ID: %s", d.Id()) 326 327 log.Println( 328 "[INFO] Waiting for DB Instance to be available") 329 330 stateConf := &resource.StateChangeConf{ 331 Pending: []string{"creating", "backing-up", "modifying"}, 332 Target: "available", 333 Refresh: resourceAwsDbInstanceStateRefreshFunc(d, meta), 334 Timeout: 40 * time.Minute, 335 MinTimeout: 10 * time.Second, 336 Delay: 30 * time.Second, // Wait 30 secs before starting 337 } 338 339 // Wait, catching any errors 340 _, err := stateConf.WaitForState() 341 if err != nil { 342 return err 343 } 344 345 return resourceAwsDbInstanceRead(d, meta) 346 } 347 348 func resourceAwsDbInstanceRead(d *schema.ResourceData, meta interface{}) error { 349 v, err := resourceAwsDbInstanceRetrieve(d, meta) 350 351 if err != nil { 352 return err 353 } 354 if v == nil { 355 d.SetId("") 356 return nil 357 } 358 359 d.Set("name", v.DBName) 360 d.Set("username", v.MasterUsername) 361 d.Set("engine", v.Engine) 362 d.Set("engine_version", v.EngineVersion) 363 d.Set("allocated_storage", v.AllocatedStorage) 364 d.Set("storage_type", v.StorageType) 365 d.Set("instance_class", v.DBInstanceClass) 366 d.Set("availability_zone", v.AvailabilityZone) 367 d.Set("backup_retention_period", v.BackupRetentionPeriod) 368 d.Set("backup_window", v.PreferredBackupWindow) 369 d.Set("license_model", v.LicenseModel) 370 d.Set("maintenance_window", v.PreferredMaintenanceWindow) 371 d.Set("multi_az", v.MultiAZ) 372 if v.DBSubnetGroup != nil { 373 d.Set("db_subnet_group_name", v.DBSubnetGroup.DBSubnetGroupName) 374 } 375 376 if len(v.DBParameterGroups) > 0 { 377 d.Set("parameter_group_name", v.DBParameterGroups[0].DBParameterGroupName) 378 } 379 380 if v.Endpoint != nil { 381 d.Set("port", v.Endpoint.Port) 382 d.Set("address", v.Endpoint.Address) 383 384 if v.Endpoint.Address != nil && v.Endpoint.Port != nil { 385 d.Set("endpoint", 386 fmt.Sprintf("%s:%d", *v.Endpoint.Address, *v.Endpoint.Port)) 387 } 388 } 389 390 d.Set("status", v.DBInstanceStatus) 391 d.Set("storage_encrypted", v.StorageEncrypted) 392 393 // list tags for resource 394 // set tags 395 conn := meta.(*AWSClient).rdsconn 396 arn, err := buildRDSARN(d, meta) 397 if err != nil { 398 name := "<empty>" 399 if v.DBName != nil && *v.DBName != "" { 400 name = *v.DBName 401 } 402 403 log.Printf("[DEBUG] Error building ARN for DB Instance, not setting Tags for DB %s", name) 404 } else { 405 resp, err := conn.ListTagsForResource(&rds.ListTagsForResourceInput{ 406 ResourceName: aws.String(arn), 407 }) 408 409 if err != nil { 410 log.Printf("[DEBUG] Error retreiving tags for ARN: %s", arn) 411 } 412 413 var dt []*rds.Tag 414 if len(resp.TagList) > 0 { 415 dt = resp.TagList 416 } 417 d.Set("tags", tagsToMapRDS(dt)) 418 } 419 420 // Create an empty schema.Set to hold all vpc security group ids 421 ids := &schema.Set{ 422 F: schema.HashString, 423 } 424 for _, v := range v.VPCSecurityGroups { 425 ids.Add(*v.VPCSecurityGroupID) 426 } 427 d.Set("vpc_security_group_ids", ids) 428 429 // Create an empty schema.Set to hold all security group names 430 sgn := &schema.Set{ 431 F: schema.HashString, 432 } 433 for _, v := range v.DBSecurityGroups { 434 sgn.Add(*v.DBSecurityGroupName) 435 } 436 d.Set("security_group_names", sgn) 437 438 // replica things 439 440 var replicas []string 441 for _, v := range v.ReadReplicaDBInstanceIdentifiers { 442 replicas = append(replicas, *v) 443 } 444 if err := d.Set("replicas", replicas); err != nil { 445 return fmt.Errorf("[DEBUG] Error setting replicas attribute: %#v, error: %#v", replicas, err) 446 } 447 448 d.Set("replicate_source_db", v.ReadReplicaSourceDBInstanceIdentifier) 449 450 return nil 451 } 452 453 func resourceAwsDbInstanceDelete(d *schema.ResourceData, meta interface{}) error { 454 conn := meta.(*AWSClient).rdsconn 455 456 log.Printf("[DEBUG] DB Instance destroy: %v", d.Id()) 457 458 opts := rds.DeleteDBInstanceInput{DBInstanceIdentifier: aws.String(d.Id())} 459 460 finalSnapshot := d.Get("final_snapshot_identifier").(string) 461 if finalSnapshot == "" { 462 opts.SkipFinalSnapshot = aws.Boolean(true) 463 } else { 464 opts.FinalDBSnapshotIdentifier = aws.String(finalSnapshot) 465 } 466 467 log.Printf("[DEBUG] DB Instance destroy configuration: %v", opts) 468 if _, err := conn.DeleteDBInstance(&opts); err != nil { 469 return err 470 } 471 472 log.Println( 473 "[INFO] Waiting for DB Instance to be destroyed") 474 stateConf := &resource.StateChangeConf{ 475 Pending: []string{"creating", "backing-up", 476 "modifying", "deleting", "available"}, 477 Target: "", 478 Refresh: resourceAwsDbInstanceStateRefreshFunc(d, meta), 479 Timeout: 40 * time.Minute, 480 MinTimeout: 10 * time.Second, 481 Delay: 30 * time.Second, // Wait 30 secs before starting 482 } 483 if _, err := stateConf.WaitForState(); err != nil { 484 return err 485 } 486 487 return nil 488 } 489 490 func resourceAwsDbInstanceUpdate(d *schema.ResourceData, meta interface{}) error { 491 conn := meta.(*AWSClient).rdsconn 492 493 d.Partial(true) 494 495 req := &rds.ModifyDBInstanceInput{ 496 ApplyImmediately: aws.Boolean(d.Get("apply_immediately").(bool)), 497 DBInstanceIdentifier: aws.String(d.Id()), 498 } 499 d.SetPartial("apply_immediately") 500 501 requestUpdate := false 502 if d.HasChange("allocated_storage") { 503 d.SetPartial("allocated_storage") 504 req.AllocatedStorage = aws.Long(int64(d.Get("allocated_storage").(int))) 505 requestUpdate = true 506 } 507 if d.HasChange("backup_retention_period") { 508 d.SetPartial("backup_retention_period") 509 req.BackupRetentionPeriod = aws.Long(int64(d.Get("backup_retention_period").(int))) 510 requestUpdate = true 511 } 512 if d.HasChange("instance_class") { 513 d.SetPartial("instance_class") 514 req.DBInstanceClass = aws.String(d.Get("instance_class").(string)) 515 requestUpdate = true 516 } 517 if d.HasChange("parameter_group_name") { 518 d.SetPartial("parameter_group_name") 519 req.DBParameterGroupName = aws.String(d.Get("parameter_group_name").(string)) 520 requestUpdate = true 521 } 522 if d.HasChange("engine_version") { 523 d.SetPartial("engine_version") 524 req.EngineVersion = aws.String(d.Get("engine_version").(string)) 525 requestUpdate = true 526 } 527 if d.HasChange("iops") { 528 d.SetPartial("iops") 529 req.IOPS = aws.Long(int64(d.Get("iops").(int))) 530 requestUpdate = true 531 } 532 if d.HasChange("backup_window") { 533 d.SetPartial("backup_window") 534 req.PreferredBackupWindow = aws.String(d.Get("backup_window").(string)) 535 requestUpdate = true 536 } 537 if d.HasChange("maintenance_window") { 538 d.SetPartial("maintenance_window") 539 req.PreferredMaintenanceWindow = aws.String(d.Get("maintenance_window").(string)) 540 requestUpdate = true 541 } 542 if d.HasChange("password") { 543 d.SetPartial("password") 544 req.MasterUserPassword = aws.String(d.Get("password").(string)) 545 requestUpdate = true 546 } 547 if d.HasChange("multi_az") { 548 d.SetPartial("multi_az") 549 req.MultiAZ = aws.Boolean(d.Get("multi_az").(bool)) 550 requestUpdate = true 551 } 552 if d.HasChange("storage_type") { 553 d.SetPartial("storage_type") 554 req.StorageType = aws.String(d.Get("storage_type").(string)) 555 requestUpdate = true 556 } 557 558 if d.HasChange("vpc_security_group_ids") { 559 if attr := d.Get("vpc_security_group_ids").(*schema.Set); attr.Len() > 0 { 560 var s []*string 561 for _, v := range attr.List() { 562 s = append(s, aws.String(v.(string))) 563 } 564 req.VPCSecurityGroupIDs = s 565 } 566 requestUpdate = true 567 } 568 569 if d.HasChange("vpc_security_group_ids") { 570 if attr := d.Get("security_group_names").(*schema.Set); attr.Len() > 0 { 571 var s []*string 572 for _, v := range attr.List() { 573 s = append(s, aws.String(v.(string))) 574 } 575 req.DBSecurityGroups = s 576 } 577 requestUpdate = true 578 } 579 580 log.Printf("[DEBUG] Send DB Instance Modification request: %#v", requestUpdate) 581 if requestUpdate { 582 log.Printf("[DEBUG] DB Instance Modification request: %#v", req) 583 _, err := conn.ModifyDBInstance(req) 584 if err != nil { 585 return fmt.Errorf("Error modifying DB Instance %s: %s", d.Id(), err) 586 } 587 } 588 589 // seperate request to promote a database 590 if d.HasChange("replicate_source_db") { 591 if d.Get("replicate_source_db").(string) == "" { 592 // promote 593 opts := rds.PromoteReadReplicaInput{ 594 DBInstanceIdentifier: aws.String(d.Id()), 595 } 596 attr := d.Get("backup_retention_period") 597 opts.BackupRetentionPeriod = aws.Long(int64(attr.(int))) 598 if attr, ok := d.GetOk("backup_window"); ok { 599 opts.PreferredBackupWindow = aws.String(attr.(string)) 600 } 601 _, err := conn.PromoteReadReplica(&opts) 602 if err != nil { 603 return fmt.Errorf("Error promoting database: %#v", err) 604 } 605 d.Set("replicate_source_db", "") 606 } else { 607 return fmt.Errorf("cannot elect new source database for replication") 608 } 609 } 610 611 if arn, err := buildRDSARN(d, meta); err == nil { 612 if err := setTagsRDS(conn, d, arn); err != nil { 613 return err 614 } else { 615 d.SetPartial("tags") 616 } 617 } 618 d.Partial(false) 619 return resourceAwsDbInstanceRead(d, meta) 620 } 621 622 func resourceAwsDbInstanceRetrieve( 623 d *schema.ResourceData, meta interface{}) (*rds.DBInstance, error) { 624 conn := meta.(*AWSClient).rdsconn 625 626 opts := rds.DescribeDBInstancesInput{ 627 DBInstanceIdentifier: aws.String(d.Id()), 628 } 629 630 log.Printf("[DEBUG] DB Instance describe configuration: %#v", opts) 631 632 resp, err := conn.DescribeDBInstances(&opts) 633 634 if err != nil { 635 dbinstanceerr, ok := err.(awserr.Error) 636 if ok && dbinstanceerr.Code() == "DBInstanceNotFound" { 637 return nil, nil 638 } 639 return nil, fmt.Errorf("Error retrieving DB Instances: %s", err) 640 } 641 642 if len(resp.DBInstances) != 1 || 643 *resp.DBInstances[0].DBInstanceIdentifier != d.Id() { 644 if err != nil { 645 return nil, nil 646 } 647 } 648 649 return resp.DBInstances[0], nil 650 } 651 652 func resourceAwsDbInstanceStateRefreshFunc( 653 d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc { 654 return func() (interface{}, string, error) { 655 v, err := resourceAwsDbInstanceRetrieve(d, meta) 656 657 if err != nil { 658 log.Printf("Error on retrieving DB Instance when waiting: %s", err) 659 return nil, "", err 660 } 661 662 if v == nil { 663 return nil, "", nil 664 } 665 666 if v.DBInstanceStatus != nil { 667 log.Printf("[DEBUG] DB Instance status for instance %s: %s", d.Id(), *v.DBInstanceStatus) 668 } 669 670 return v, *v.DBInstanceStatus, nil 671 } 672 } 673 674 func buildRDSARN(d *schema.ResourceData, meta interface{}) (string, error) { 675 iamconn := meta.(*AWSClient).iamconn 676 region := meta.(*AWSClient).region 677 // An zero value GetUserInput{} defers to the currently logged in user 678 resp, err := iamconn.GetUser(&iam.GetUserInput{}) 679 if err != nil { 680 return "", err 681 } 682 userARN := *resp.User.ARN 683 accountID := strings.Split(userARN, ":")[4] 684 arn := fmt.Sprintf("arn:aws:rds:%s:%s:db:%s", region, accountID, d.Id()) 685 return arn, nil 686 }