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