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