github.com/mohanarpit/terraform@v0.6.16-0.20160909104007-291f29853544/builtin/providers/aws/opsworks_layers.go (about) 1 package aws 2 3 import ( 4 "fmt" 5 "log" 6 "strconv" 7 8 "github.com/hashicorp/terraform/helper/hashcode" 9 "github.com/hashicorp/terraform/helper/schema" 10 11 "github.com/aws/aws-sdk-go/aws" 12 "github.com/aws/aws-sdk-go/aws/awserr" 13 "github.com/aws/aws-sdk-go/service/opsworks" 14 ) 15 16 // OpsWorks has a single concept of "layer" which represents several different 17 // layer types. The differences between these are in some extra properties that 18 // get packed into an "Attributes" map, but in the OpsWorks UI these are presented 19 // as first-class options, and so Terraform prefers to expose them this way and 20 // hide the implementation detail that they are all packed into a single type 21 // in the underlying API. 22 // 23 // This file contains utilities that are shared between all of the concrete 24 // layer resource types, which have names matching aws_opsworks_*_layer . 25 26 type opsworksLayerTypeAttribute struct { 27 AttrName string 28 Type schema.ValueType 29 Default interface{} 30 Required bool 31 WriteOnly bool 32 } 33 34 type opsworksLayerType struct { 35 TypeName string 36 DefaultLayerName string 37 Attributes map[string]*opsworksLayerTypeAttribute 38 CustomShortName bool 39 } 40 41 var ( 42 opsworksTrueString = "true" 43 opsworksFalseString = "false" 44 ) 45 46 func (lt *opsworksLayerType) SchemaResource() *schema.Resource { 47 resourceSchema := map[string]*schema.Schema{ 48 "id": &schema.Schema{ 49 Type: schema.TypeString, 50 Computed: true, 51 }, 52 53 "auto_assign_elastic_ips": &schema.Schema{ 54 Type: schema.TypeBool, 55 Optional: true, 56 Default: false, 57 }, 58 59 "auto_assign_public_ips": &schema.Schema{ 60 Type: schema.TypeBool, 61 Optional: true, 62 Default: false, 63 }, 64 65 "custom_instance_profile_arn": &schema.Schema{ 66 Type: schema.TypeString, 67 Optional: true, 68 }, 69 70 "elastic_load_balancer": &schema.Schema{ 71 Type: schema.TypeString, 72 Optional: true, 73 }, 74 75 "custom_setup_recipes": &schema.Schema{ 76 Type: schema.TypeList, 77 Optional: true, 78 Elem: &schema.Schema{Type: schema.TypeString}, 79 }, 80 81 "custom_configure_recipes": &schema.Schema{ 82 Type: schema.TypeList, 83 Optional: true, 84 Elem: &schema.Schema{Type: schema.TypeString}, 85 }, 86 87 "custom_deploy_recipes": &schema.Schema{ 88 Type: schema.TypeList, 89 Optional: true, 90 Elem: &schema.Schema{Type: schema.TypeString}, 91 }, 92 93 "custom_undeploy_recipes": &schema.Schema{ 94 Type: schema.TypeList, 95 Optional: true, 96 Elem: &schema.Schema{Type: schema.TypeString}, 97 }, 98 99 "custom_shutdown_recipes": &schema.Schema{ 100 Type: schema.TypeList, 101 Optional: true, 102 Elem: &schema.Schema{Type: schema.TypeString}, 103 }, 104 105 "custom_security_group_ids": &schema.Schema{ 106 Type: schema.TypeSet, 107 Optional: true, 108 Elem: &schema.Schema{Type: schema.TypeString}, 109 Set: schema.HashString, 110 }, 111 112 "custom_json": &schema.Schema{ 113 Type: schema.TypeString, 114 StateFunc: normalizeJson, 115 Optional: true, 116 }, 117 118 "auto_healing": &schema.Schema{ 119 Type: schema.TypeBool, 120 Optional: true, 121 Default: true, 122 }, 123 124 "install_updates_on_boot": &schema.Schema{ 125 Type: schema.TypeBool, 126 Optional: true, 127 Default: true, 128 }, 129 130 "instance_shutdown_timeout": &schema.Schema{ 131 Type: schema.TypeInt, 132 Optional: true, 133 Default: 120, 134 }, 135 136 "drain_elb_on_shutdown": &schema.Schema{ 137 Type: schema.TypeBool, 138 Optional: true, 139 Default: true, 140 }, 141 142 "system_packages": &schema.Schema{ 143 Type: schema.TypeSet, 144 Optional: true, 145 Elem: &schema.Schema{Type: schema.TypeString}, 146 Set: schema.HashString, 147 }, 148 149 "stack_id": &schema.Schema{ 150 Type: schema.TypeString, 151 ForceNew: true, 152 Required: true, 153 }, 154 155 "use_ebs_optimized_instances": &schema.Schema{ 156 Type: schema.TypeBool, 157 Optional: true, 158 Default: false, 159 }, 160 161 "ebs_volume": &schema.Schema{ 162 Type: schema.TypeSet, 163 Optional: true, 164 Elem: &schema.Resource{ 165 Schema: map[string]*schema.Schema{ 166 167 "iops": &schema.Schema{ 168 Type: schema.TypeInt, 169 Optional: true, 170 Default: 0, 171 }, 172 173 "mount_point": &schema.Schema{ 174 Type: schema.TypeString, 175 Required: true, 176 }, 177 178 "number_of_disks": &schema.Schema{ 179 Type: schema.TypeInt, 180 Required: true, 181 }, 182 183 "raid_level": &schema.Schema{ 184 Type: schema.TypeString, 185 Optional: true, 186 Default: "", 187 }, 188 189 "size": &schema.Schema{ 190 Type: schema.TypeInt, 191 Required: true, 192 }, 193 194 "type": &schema.Schema{ 195 Type: schema.TypeString, 196 Optional: true, 197 Default: "standard", 198 }, 199 }, 200 }, 201 Set: func(v interface{}) int { 202 m := v.(map[string]interface{}) 203 return hashcode.String(m["mount_point"].(string)) 204 }, 205 }, 206 } 207 208 if lt.CustomShortName { 209 resourceSchema["short_name"] = &schema.Schema{ 210 Type: schema.TypeString, 211 Required: true, 212 } 213 } 214 215 if lt.DefaultLayerName != "" { 216 resourceSchema["name"] = &schema.Schema{ 217 Type: schema.TypeString, 218 Optional: true, 219 Default: lt.DefaultLayerName, 220 } 221 } else { 222 resourceSchema["name"] = &schema.Schema{ 223 Type: schema.TypeString, 224 Required: true, 225 } 226 } 227 228 for key, def := range lt.Attributes { 229 resourceSchema[key] = &schema.Schema{ 230 Type: def.Type, 231 Default: def.Default, 232 Required: def.Required, 233 Optional: !def.Required, 234 } 235 } 236 237 return &schema.Resource{ 238 Read: func(d *schema.ResourceData, meta interface{}) error { 239 client := meta.(*AWSClient).opsworksconn 240 return lt.Read(d, client) 241 }, 242 Create: func(d *schema.ResourceData, meta interface{}) error { 243 client := meta.(*AWSClient).opsworksconn 244 return lt.Create(d, client) 245 }, 246 Update: func(d *schema.ResourceData, meta interface{}) error { 247 client := meta.(*AWSClient).opsworksconn 248 return lt.Update(d, client) 249 }, 250 Delete: func(d *schema.ResourceData, meta interface{}) error { 251 client := meta.(*AWSClient).opsworksconn 252 return lt.Delete(d, client) 253 }, 254 255 Schema: resourceSchema, 256 } 257 } 258 259 func (lt *opsworksLayerType) Read(d *schema.ResourceData, client *opsworks.OpsWorks) error { 260 261 req := &opsworks.DescribeLayersInput{ 262 LayerIds: []*string{ 263 aws.String(d.Id()), 264 }, 265 } 266 267 log.Printf("[DEBUG] Reading OpsWorks layer: %s", d.Id()) 268 269 resp, err := client.DescribeLayers(req) 270 if err != nil { 271 if awserr, ok := err.(awserr.Error); ok { 272 if awserr.Code() == "ResourceNotFoundException" { 273 d.SetId("") 274 return nil 275 } 276 } 277 return err 278 } 279 280 layer := resp.Layers[0] 281 d.Set("id", layer.LayerId) 282 d.Set("auto_assign_elastic_ips", layer.AutoAssignElasticIps) 283 d.Set("auto_assign_public_ips", layer.AutoAssignPublicIps) 284 d.Set("custom_instance_profile_arn", layer.CustomInstanceProfileArn) 285 d.Set("custom_security_group_ids", flattenStringList(layer.CustomSecurityGroupIds)) 286 d.Set("auto_healing", layer.EnableAutoHealing) 287 d.Set("install_updates_on_boot", layer.InstallUpdatesOnBoot) 288 d.Set("name", layer.Name) 289 d.Set("system_packages", flattenStringList(layer.Packages)) 290 d.Set("stack_id", layer.StackId) 291 d.Set("use_ebs_optimized_instances", layer.UseEbsOptimizedInstances) 292 293 if lt.CustomShortName { 294 d.Set("short_name", layer.Shortname) 295 } 296 297 if v := layer.CustomJson; v == nil { 298 if err := d.Set("custom_json", ""); err != nil { 299 return err 300 } 301 } else if err := d.Set("custom_json", normalizeJson(*v)); err != nil { 302 return err 303 } 304 305 lt.SetAttributeMap(d, layer.Attributes) 306 lt.SetLifecycleEventConfiguration(d, layer.LifecycleEventConfiguration) 307 lt.SetCustomRecipes(d, layer.CustomRecipes) 308 lt.SetVolumeConfigurations(d, layer.VolumeConfigurations) 309 310 /* get ELB */ 311 ebsRequest := &opsworks.DescribeElasticLoadBalancersInput{ 312 LayerIds: []*string{ 313 aws.String(d.Id()), 314 }, 315 } 316 loadBalancers, err := client.DescribeElasticLoadBalancers(ebsRequest) 317 if err != nil { 318 return err 319 } 320 321 if loadBalancers.ElasticLoadBalancers == nil || len(loadBalancers.ElasticLoadBalancers) == 0 { 322 d.Set("elastic_load_balancer", "") 323 } else { 324 loadBalancer := loadBalancers.ElasticLoadBalancers[0] 325 if loadBalancer != nil { 326 d.Set("elastic_load_balancer", loadBalancer.ElasticLoadBalancerName) 327 } 328 } 329 330 return nil 331 } 332 333 func (lt *opsworksLayerType) Create(d *schema.ResourceData, client *opsworks.OpsWorks) error { 334 335 req := &opsworks.CreateLayerInput{ 336 AutoAssignElasticIps: aws.Bool(d.Get("auto_assign_elastic_ips").(bool)), 337 AutoAssignPublicIps: aws.Bool(d.Get("auto_assign_public_ips").(bool)), 338 CustomInstanceProfileArn: aws.String(d.Get("custom_instance_profile_arn").(string)), 339 CustomRecipes: lt.CustomRecipes(d), 340 CustomSecurityGroupIds: expandStringSet(d.Get("custom_security_group_ids").(*schema.Set)), 341 EnableAutoHealing: aws.Bool(d.Get("auto_healing").(bool)), 342 InstallUpdatesOnBoot: aws.Bool(d.Get("install_updates_on_boot").(bool)), 343 LifecycleEventConfiguration: lt.LifecycleEventConfiguration(d), 344 Name: aws.String(d.Get("name").(string)), 345 Packages: expandStringSet(d.Get("system_packages").(*schema.Set)), 346 Type: aws.String(lt.TypeName), 347 StackId: aws.String(d.Get("stack_id").(string)), 348 UseEbsOptimizedInstances: aws.Bool(d.Get("use_ebs_optimized_instances").(bool)), 349 Attributes: lt.AttributeMap(d), 350 VolumeConfigurations: lt.VolumeConfigurations(d), 351 } 352 353 if lt.CustomShortName { 354 req.Shortname = aws.String(d.Get("short_name").(string)) 355 } else { 356 req.Shortname = aws.String(lt.TypeName) 357 } 358 359 req.CustomJson = aws.String(d.Get("custom_json").(string)) 360 361 log.Printf("[DEBUG] Creating OpsWorks layer: %s", d.Id()) 362 363 resp, err := client.CreateLayer(req) 364 if err != nil { 365 return err 366 } 367 368 layerId := *resp.LayerId 369 d.SetId(layerId) 370 d.Set("id", layerId) 371 372 loadBalancer := aws.String(d.Get("elastic_load_balancer").(string)) 373 if loadBalancer != nil && *loadBalancer != "" { 374 log.Printf("[DEBUG] Attaching load balancer: %s", *loadBalancer) 375 _, err := client.AttachElasticLoadBalancer(&opsworks.AttachElasticLoadBalancerInput{ 376 ElasticLoadBalancerName: loadBalancer, 377 LayerId: &layerId, 378 }) 379 if err != nil { 380 return err 381 } 382 } 383 384 return lt.Read(d, client) 385 } 386 387 func (lt *opsworksLayerType) Update(d *schema.ResourceData, client *opsworks.OpsWorks) error { 388 389 req := &opsworks.UpdateLayerInput{ 390 LayerId: aws.String(d.Id()), 391 AutoAssignElasticIps: aws.Bool(d.Get("auto_assign_elastic_ips").(bool)), 392 AutoAssignPublicIps: aws.Bool(d.Get("auto_assign_public_ips").(bool)), 393 CustomInstanceProfileArn: aws.String(d.Get("custom_instance_profile_arn").(string)), 394 CustomRecipes: lt.CustomRecipes(d), 395 CustomSecurityGroupIds: expandStringSet(d.Get("custom_security_group_ids").(*schema.Set)), 396 EnableAutoHealing: aws.Bool(d.Get("auto_healing").(bool)), 397 InstallUpdatesOnBoot: aws.Bool(d.Get("install_updates_on_boot").(bool)), 398 LifecycleEventConfiguration: lt.LifecycleEventConfiguration(d), 399 Name: aws.String(d.Get("name").(string)), 400 Packages: expandStringSet(d.Get("system_packages").(*schema.Set)), 401 UseEbsOptimizedInstances: aws.Bool(d.Get("use_ebs_optimized_instances").(bool)), 402 Attributes: lt.AttributeMap(d), 403 VolumeConfigurations: lt.VolumeConfigurations(d), 404 } 405 406 if lt.CustomShortName { 407 req.Shortname = aws.String(d.Get("short_name").(string)) 408 } else { 409 req.Shortname = aws.String(lt.TypeName) 410 } 411 412 req.CustomJson = aws.String(d.Get("custom_json").(string)) 413 414 log.Printf("[DEBUG] Updating OpsWorks layer: %s", d.Id()) 415 416 if d.HasChange("elastic_load_balancer") { 417 lbo, lbn := d.GetChange("elastic_load_balancer") 418 419 loadBalancerOld := aws.String(lbo.(string)) 420 loadBalancerNew := aws.String(lbn.(string)) 421 422 if loadBalancerOld != nil && *loadBalancerOld != "" { 423 log.Printf("[DEBUG] Dettaching load balancer: %s", *loadBalancerOld) 424 _, err := client.DetachElasticLoadBalancer(&opsworks.DetachElasticLoadBalancerInput{ 425 ElasticLoadBalancerName: loadBalancerOld, 426 LayerId: aws.String(d.Id()), 427 }) 428 if err != nil { 429 return err 430 } 431 } 432 433 if loadBalancerNew != nil && *loadBalancerNew != "" { 434 log.Printf("[DEBUG] Attaching load balancer: %s", *loadBalancerNew) 435 _, err := client.AttachElasticLoadBalancer(&opsworks.AttachElasticLoadBalancerInput{ 436 ElasticLoadBalancerName: loadBalancerNew, 437 LayerId: aws.String(d.Id()), 438 }) 439 if err != nil { 440 return err 441 } 442 } 443 } 444 445 _, err := client.UpdateLayer(req) 446 if err != nil { 447 return err 448 } 449 450 return lt.Read(d, client) 451 } 452 453 func (lt *opsworksLayerType) Delete(d *schema.ResourceData, client *opsworks.OpsWorks) error { 454 req := &opsworks.DeleteLayerInput{ 455 LayerId: aws.String(d.Id()), 456 } 457 458 log.Printf("[DEBUG] Deleting OpsWorks layer: %s", d.Id()) 459 460 _, err := client.DeleteLayer(req) 461 return err 462 } 463 464 func (lt *opsworksLayerType) AttributeMap(d *schema.ResourceData) map[string]*string { 465 attrs := map[string]*string{} 466 467 for key, def := range lt.Attributes { 468 value := d.Get(key) 469 switch def.Type { 470 case schema.TypeString: 471 strValue := value.(string) 472 attrs[def.AttrName] = &strValue 473 case schema.TypeInt: 474 intValue := value.(int) 475 strValue := strconv.Itoa(intValue) 476 attrs[def.AttrName] = &strValue 477 case schema.TypeBool: 478 boolValue := value.(bool) 479 if boolValue { 480 attrs[def.AttrName] = &opsworksTrueString 481 } else { 482 attrs[def.AttrName] = &opsworksFalseString 483 } 484 default: 485 // should never happen 486 panic(fmt.Errorf("Unsupported OpsWorks layer attribute type")) 487 } 488 } 489 490 return attrs 491 } 492 493 func (lt *opsworksLayerType) SetAttributeMap(d *schema.ResourceData, attrs map[string]*string) { 494 for key, def := range lt.Attributes { 495 // Ignore write-only attributes; we'll just keep what we already have stored. 496 // (The AWS API returns garbage placeholder values for these.) 497 if def.WriteOnly { 498 continue 499 } 500 501 if strPtr, ok := attrs[def.AttrName]; ok && strPtr != nil { 502 strValue := *strPtr 503 504 switch def.Type { 505 case schema.TypeString: 506 d.Set(key, strValue) 507 case schema.TypeInt: 508 intValue, err := strconv.Atoi(strValue) 509 if err == nil { 510 d.Set(key, intValue) 511 } else { 512 // Got garbage from the AWS API 513 d.Set(key, nil) 514 } 515 case schema.TypeBool: 516 boolValue := true 517 if strValue == opsworksFalseString { 518 boolValue = false 519 } 520 d.Set(key, boolValue) 521 default: 522 // should never happen 523 panic(fmt.Errorf("Unsupported OpsWorks layer attribute type")) 524 } 525 return 526 527 } else { 528 d.Set(key, nil) 529 } 530 } 531 } 532 533 func (lt *opsworksLayerType) LifecycleEventConfiguration(d *schema.ResourceData) *opsworks.LifecycleEventConfiguration { 534 return &opsworks.LifecycleEventConfiguration{ 535 Shutdown: &opsworks.ShutdownEventConfiguration{ 536 DelayUntilElbConnectionsDrained: aws.Bool(d.Get("drain_elb_on_shutdown").(bool)), 537 ExecutionTimeout: aws.Int64(int64(d.Get("instance_shutdown_timeout").(int))), 538 }, 539 } 540 } 541 542 func (lt *opsworksLayerType) SetLifecycleEventConfiguration(d *schema.ResourceData, v *opsworks.LifecycleEventConfiguration) { 543 if v == nil || v.Shutdown == nil { 544 d.Set("drain_elb_on_shutdown", nil) 545 d.Set("instance_shutdown_timeout", nil) 546 } else { 547 d.Set("drain_elb_on_shutdown", v.Shutdown.DelayUntilElbConnectionsDrained) 548 d.Set("instance_shutdown_timeout", v.Shutdown.ExecutionTimeout) 549 } 550 } 551 552 func (lt *opsworksLayerType) CustomRecipes(d *schema.ResourceData) *opsworks.Recipes { 553 return &opsworks.Recipes{ 554 Configure: expandStringList(d.Get("custom_configure_recipes").([]interface{})), 555 Deploy: expandStringList(d.Get("custom_deploy_recipes").([]interface{})), 556 Setup: expandStringList(d.Get("custom_setup_recipes").([]interface{})), 557 Shutdown: expandStringList(d.Get("custom_shutdown_recipes").([]interface{})), 558 Undeploy: expandStringList(d.Get("custom_undeploy_recipes").([]interface{})), 559 } 560 } 561 562 func (lt *opsworksLayerType) SetCustomRecipes(d *schema.ResourceData, v *opsworks.Recipes) { 563 // Null out everything first, and then we'll consider what to put back. 564 d.Set("custom_configure_recipes", nil) 565 d.Set("custom_deploy_recipes", nil) 566 d.Set("custom_setup_recipes", nil) 567 d.Set("custom_shutdown_recipes", nil) 568 d.Set("custom_undeploy_recipes", nil) 569 570 if v == nil { 571 return 572 } 573 574 d.Set("custom_configure_recipes", flattenStringList(v.Configure)) 575 d.Set("custom_deploy_recipes", flattenStringList(v.Deploy)) 576 d.Set("custom_setup_recipes", flattenStringList(v.Setup)) 577 d.Set("custom_shutdown_recipes", flattenStringList(v.Shutdown)) 578 d.Set("custom_undeploy_recipes", flattenStringList(v.Undeploy)) 579 } 580 581 func (lt *opsworksLayerType) VolumeConfigurations(d *schema.ResourceData) []*opsworks.VolumeConfiguration { 582 configuredVolumes := d.Get("ebs_volume").(*schema.Set).List() 583 result := make([]*opsworks.VolumeConfiguration, len(configuredVolumes)) 584 585 for i := 0; i < len(configuredVolumes); i++ { 586 volumeData := configuredVolumes[i].(map[string]interface{}) 587 588 result[i] = &opsworks.VolumeConfiguration{ 589 MountPoint: aws.String(volumeData["mount_point"].(string)), 590 NumberOfDisks: aws.Int64(int64(volumeData["number_of_disks"].(int))), 591 Size: aws.Int64(int64(volumeData["size"].(int))), 592 VolumeType: aws.String(volumeData["type"].(string)), 593 } 594 iops := int64(volumeData["iops"].(int)) 595 if iops != 0 { 596 result[i].Iops = aws.Int64(iops) 597 } 598 599 raidLevelStr := volumeData["raid_level"].(string) 600 if raidLevelStr != "" { 601 raidLevel, err := strconv.Atoi(raidLevelStr) 602 if err == nil { 603 result[i].RaidLevel = aws.Int64(int64(raidLevel)) 604 } 605 } 606 } 607 608 return result 609 } 610 611 func (lt *opsworksLayerType) SetVolumeConfigurations(d *schema.ResourceData, v []*opsworks.VolumeConfiguration) { 612 newValue := make([]*map[string]interface{}, len(v)) 613 614 for i := 0; i < len(v); i++ { 615 config := v[i] 616 data := make(map[string]interface{}) 617 newValue[i] = &data 618 619 if config.Iops != nil { 620 data["iops"] = int(*config.Iops) 621 } else { 622 data["iops"] = 0 623 } 624 if config.MountPoint != nil { 625 data["mount_point"] = *config.MountPoint 626 } 627 if config.NumberOfDisks != nil { 628 data["number_of_disks"] = int(*config.NumberOfDisks) 629 } 630 if config.RaidLevel != nil { 631 data["raid_level"] = strconv.Itoa(int(*config.RaidLevel)) 632 } 633 if config.Size != nil { 634 data["size"] = int(*config.Size) 635 } 636 if config.VolumeType != nil { 637 data["type"] = *config.VolumeType 638 } 639 } 640 641 d.Set("ebs_volume", newValue) 642 }