github.com/andresvia/terraform@v0.6.15-0.20160412045437-d51c75946785/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 = "1" 43 opsworksFalseString = "0" 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 "auto_healing": &schema.Schema{ 113 Type: schema.TypeBool, 114 Optional: true, 115 Default: true, 116 }, 117 118 "install_updates_on_boot": &schema.Schema{ 119 Type: schema.TypeBool, 120 Optional: true, 121 Default: true, 122 }, 123 124 "instance_shutdown_timeout": &schema.Schema{ 125 Type: schema.TypeInt, 126 Optional: true, 127 Default: 120, 128 }, 129 130 "drain_elb_on_shutdown": &schema.Schema{ 131 Type: schema.TypeBool, 132 Optional: true, 133 Default: true, 134 }, 135 136 "system_packages": &schema.Schema{ 137 Type: schema.TypeSet, 138 Optional: true, 139 Elem: &schema.Schema{Type: schema.TypeString}, 140 Set: schema.HashString, 141 }, 142 143 "stack_id": &schema.Schema{ 144 Type: schema.TypeString, 145 ForceNew: true, 146 Required: true, 147 }, 148 149 "use_ebs_optimized_instances": &schema.Schema{ 150 Type: schema.TypeBool, 151 Optional: true, 152 Default: false, 153 }, 154 155 "ebs_volume": &schema.Schema{ 156 Type: schema.TypeSet, 157 Optional: true, 158 Elem: &schema.Resource{ 159 Schema: map[string]*schema.Schema{ 160 161 "iops": &schema.Schema{ 162 Type: schema.TypeInt, 163 Optional: true, 164 Default: 0, 165 }, 166 167 "mount_point": &schema.Schema{ 168 Type: schema.TypeString, 169 Required: true, 170 }, 171 172 "number_of_disks": &schema.Schema{ 173 Type: schema.TypeInt, 174 Required: true, 175 }, 176 177 "raid_level": &schema.Schema{ 178 Type: schema.TypeString, 179 Optional: true, 180 Default: "", 181 }, 182 183 "size": &schema.Schema{ 184 Type: schema.TypeInt, 185 Required: true, 186 }, 187 188 "type": &schema.Schema{ 189 Type: schema.TypeString, 190 Optional: true, 191 Default: "standard", 192 }, 193 }, 194 }, 195 Set: func(v interface{}) int { 196 m := v.(map[string]interface{}) 197 return hashcode.String(m["mount_point"].(string)) 198 }, 199 }, 200 } 201 202 if lt.CustomShortName { 203 resourceSchema["short_name"] = &schema.Schema{ 204 Type: schema.TypeString, 205 Required: true, 206 } 207 } 208 209 if lt.DefaultLayerName != "" { 210 resourceSchema["name"] = &schema.Schema{ 211 Type: schema.TypeString, 212 Optional: true, 213 Default: lt.DefaultLayerName, 214 } 215 } else { 216 resourceSchema["name"] = &schema.Schema{ 217 Type: schema.TypeString, 218 Required: true, 219 } 220 } 221 222 for key, def := range lt.Attributes { 223 resourceSchema[key] = &schema.Schema{ 224 Type: def.Type, 225 Default: def.Default, 226 Required: def.Required, 227 Optional: !def.Required, 228 } 229 } 230 231 return &schema.Resource{ 232 Read: func(d *schema.ResourceData, meta interface{}) error { 233 client := meta.(*AWSClient).opsworksconn 234 return lt.Read(d, client) 235 }, 236 Create: func(d *schema.ResourceData, meta interface{}) error { 237 client := meta.(*AWSClient).opsworksconn 238 return lt.Create(d, client) 239 }, 240 Update: func(d *schema.ResourceData, meta interface{}) error { 241 client := meta.(*AWSClient).opsworksconn 242 return lt.Update(d, client) 243 }, 244 Delete: func(d *schema.ResourceData, meta interface{}) error { 245 client := meta.(*AWSClient).opsworksconn 246 return lt.Delete(d, client) 247 }, 248 249 Schema: resourceSchema, 250 } 251 } 252 253 func (lt *opsworksLayerType) Read(d *schema.ResourceData, client *opsworks.OpsWorks) error { 254 255 req := &opsworks.DescribeLayersInput{ 256 LayerIds: []*string{ 257 aws.String(d.Id()), 258 }, 259 } 260 261 log.Printf("[DEBUG] Reading OpsWorks layer: %s", d.Id()) 262 263 resp, err := client.DescribeLayers(req) 264 if err != nil { 265 if awserr, ok := err.(awserr.Error); ok { 266 if awserr.Code() == "ResourceNotFoundException" { 267 d.SetId("") 268 return nil 269 } 270 } 271 return err 272 } 273 274 layer := resp.Layers[0] 275 d.Set("id", layer.LayerId) 276 d.Set("auto_assign_elastic_ips", layer.AutoAssignElasticIps) 277 d.Set("auto_assign_public_ips", layer.AutoAssignPublicIps) 278 d.Set("custom_instance_profile_arn", layer.CustomInstanceProfileArn) 279 d.Set("custom_security_group_ids", flattenStringList(layer.CustomSecurityGroupIds)) 280 d.Set("auto_healing", layer.EnableAutoHealing) 281 d.Set("install_updates_on_boot", layer.InstallUpdatesOnBoot) 282 d.Set("name", layer.Name) 283 d.Set("system_packages", flattenStringList(layer.Packages)) 284 d.Set("stack_id", layer.StackId) 285 d.Set("use_ebs_optimized_instances", layer.UseEbsOptimizedInstances) 286 287 if lt.CustomShortName { 288 d.Set("short_name", layer.Shortname) 289 } 290 291 lt.SetAttributeMap(d, layer.Attributes) 292 lt.SetLifecycleEventConfiguration(d, layer.LifecycleEventConfiguration) 293 lt.SetCustomRecipes(d, layer.CustomRecipes) 294 lt.SetVolumeConfigurations(d, layer.VolumeConfigurations) 295 296 /* get ELB */ 297 ebsRequest := &opsworks.DescribeElasticLoadBalancersInput{ 298 LayerIds: []*string{ 299 aws.String(d.Id()), 300 }, 301 } 302 loadBalancers, err := client.DescribeElasticLoadBalancers(ebsRequest) 303 if err != nil { 304 return err 305 } 306 307 if loadBalancers.ElasticLoadBalancers == nil || len(loadBalancers.ElasticLoadBalancers) == 0 { 308 d.Set("elastic_load_balancer", "") 309 } else { 310 loadBalancer := loadBalancers.ElasticLoadBalancers[0] 311 if loadBalancer != nil { 312 d.Set("elastic_load_balancer", loadBalancer.ElasticLoadBalancerName) 313 } 314 } 315 316 return nil 317 } 318 319 func (lt *opsworksLayerType) Create(d *schema.ResourceData, client *opsworks.OpsWorks) error { 320 321 req := &opsworks.CreateLayerInput{ 322 AutoAssignElasticIps: aws.Bool(d.Get("auto_assign_elastic_ips").(bool)), 323 AutoAssignPublicIps: aws.Bool(d.Get("auto_assign_public_ips").(bool)), 324 CustomInstanceProfileArn: aws.String(d.Get("custom_instance_profile_arn").(string)), 325 CustomRecipes: lt.CustomRecipes(d), 326 CustomSecurityGroupIds: expandStringSet(d.Get("custom_security_group_ids").(*schema.Set)), 327 EnableAutoHealing: aws.Bool(d.Get("auto_healing").(bool)), 328 InstallUpdatesOnBoot: aws.Bool(d.Get("install_updates_on_boot").(bool)), 329 LifecycleEventConfiguration: lt.LifecycleEventConfiguration(d), 330 Name: aws.String(d.Get("name").(string)), 331 Packages: expandStringSet(d.Get("system_packages").(*schema.Set)), 332 Type: aws.String(lt.TypeName), 333 StackId: aws.String(d.Get("stack_id").(string)), 334 UseEbsOptimizedInstances: aws.Bool(d.Get("use_ebs_optimized_instances").(bool)), 335 Attributes: lt.AttributeMap(d), 336 VolumeConfigurations: lt.VolumeConfigurations(d), 337 } 338 339 if lt.CustomShortName { 340 req.Shortname = aws.String(d.Get("short_name").(string)) 341 } else { 342 req.Shortname = aws.String(lt.TypeName) 343 } 344 345 log.Printf("[DEBUG] Creating OpsWorks layer: %s", d.Id()) 346 347 resp, err := client.CreateLayer(req) 348 if err != nil { 349 return err 350 } 351 352 layerId := *resp.LayerId 353 d.SetId(layerId) 354 d.Set("id", layerId) 355 356 loadBalancer := aws.String(d.Get("elastic_load_balancer").(string)) 357 if loadBalancer != nil && *loadBalancer != "" { 358 log.Printf("[DEBUG] Attaching load balancer: %s", *loadBalancer) 359 _, err := client.AttachElasticLoadBalancer(&opsworks.AttachElasticLoadBalancerInput{ 360 ElasticLoadBalancerName: loadBalancer, 361 LayerId: &layerId, 362 }) 363 if err != nil { 364 return err 365 } 366 } 367 368 return lt.Read(d, client) 369 } 370 371 func (lt *opsworksLayerType) Update(d *schema.ResourceData, client *opsworks.OpsWorks) error { 372 373 req := &opsworks.UpdateLayerInput{ 374 LayerId: aws.String(d.Id()), 375 AutoAssignElasticIps: aws.Bool(d.Get("auto_assign_elastic_ips").(bool)), 376 AutoAssignPublicIps: aws.Bool(d.Get("auto_assign_public_ips").(bool)), 377 CustomInstanceProfileArn: aws.String(d.Get("custom_instance_profile_arn").(string)), 378 CustomRecipes: lt.CustomRecipes(d), 379 CustomSecurityGroupIds: expandStringSet(d.Get("custom_security_group_ids").(*schema.Set)), 380 EnableAutoHealing: aws.Bool(d.Get("auto_healing").(bool)), 381 InstallUpdatesOnBoot: aws.Bool(d.Get("install_updates_on_boot").(bool)), 382 LifecycleEventConfiguration: lt.LifecycleEventConfiguration(d), 383 Name: aws.String(d.Get("name").(string)), 384 Packages: expandStringSet(d.Get("system_packages").(*schema.Set)), 385 UseEbsOptimizedInstances: aws.Bool(d.Get("use_ebs_optimized_instances").(bool)), 386 Attributes: lt.AttributeMap(d), 387 VolumeConfigurations: lt.VolumeConfigurations(d), 388 } 389 390 if lt.CustomShortName { 391 req.Shortname = aws.String(d.Get("short_name").(string)) 392 } else { 393 req.Shortname = aws.String(lt.TypeName) 394 } 395 396 log.Printf("[DEBUG] Updating OpsWorks layer: %s", d.Id()) 397 398 if d.HasChange("elastic_load_balancer") { 399 lbo, lbn := d.GetChange("elastic_load_balancer") 400 401 loadBalancerOld := aws.String(lbo.(string)) 402 loadBalancerNew := aws.String(lbn.(string)) 403 404 if loadBalancerOld != nil && *loadBalancerOld != "" { 405 log.Printf("[DEBUG] Dettaching load balancer: %s", *loadBalancerOld) 406 _, err := client.DetachElasticLoadBalancer(&opsworks.DetachElasticLoadBalancerInput{ 407 ElasticLoadBalancerName: loadBalancerOld, 408 LayerId: aws.String(d.Id()), 409 }) 410 if err != nil { 411 return err 412 } 413 } 414 415 if loadBalancerNew != nil && *loadBalancerNew != "" { 416 log.Printf("[DEBUG] Attaching load balancer: %s", *loadBalancerNew) 417 _, err := client.AttachElasticLoadBalancer(&opsworks.AttachElasticLoadBalancerInput{ 418 ElasticLoadBalancerName: loadBalancerNew, 419 LayerId: aws.String(d.Id()), 420 }) 421 if err != nil { 422 return err 423 } 424 } 425 } 426 427 _, err := client.UpdateLayer(req) 428 if err != nil { 429 return err 430 } 431 432 return lt.Read(d, client) 433 } 434 435 func (lt *opsworksLayerType) Delete(d *schema.ResourceData, client *opsworks.OpsWorks) error { 436 req := &opsworks.DeleteLayerInput{ 437 LayerId: aws.String(d.Id()), 438 } 439 440 log.Printf("[DEBUG] Deleting OpsWorks layer: %s", d.Id()) 441 442 _, err := client.DeleteLayer(req) 443 return err 444 } 445 446 func (lt *opsworksLayerType) AttributeMap(d *schema.ResourceData) map[string]*string { 447 attrs := map[string]*string{} 448 449 for key, def := range lt.Attributes { 450 value := d.Get(key) 451 switch def.Type { 452 case schema.TypeString: 453 strValue := value.(string) 454 attrs[def.AttrName] = &strValue 455 case schema.TypeInt: 456 intValue := value.(int) 457 strValue := strconv.Itoa(intValue) 458 attrs[def.AttrName] = &strValue 459 case schema.TypeBool: 460 boolValue := value.(bool) 461 if boolValue { 462 attrs[def.AttrName] = &opsworksTrueString 463 } else { 464 attrs[def.AttrName] = &opsworksFalseString 465 } 466 default: 467 // should never happen 468 panic(fmt.Errorf("Unsupported OpsWorks layer attribute type")) 469 } 470 } 471 472 return attrs 473 } 474 475 func (lt *opsworksLayerType) SetAttributeMap(d *schema.ResourceData, attrs map[string]*string) { 476 for key, def := range lt.Attributes { 477 // Ignore write-only attributes; we'll just keep what we already have stored. 478 // (The AWS API returns garbage placeholder values for these.) 479 if def.WriteOnly { 480 continue 481 } 482 483 if strPtr, ok := attrs[def.AttrName]; ok && strPtr != nil { 484 strValue := *strPtr 485 486 switch def.Type { 487 case schema.TypeString: 488 d.Set(key, strValue) 489 case schema.TypeInt: 490 intValue, err := strconv.Atoi(strValue) 491 if err == nil { 492 d.Set(key, intValue) 493 } else { 494 // Got garbage from the AWS API 495 d.Set(key, nil) 496 } 497 case schema.TypeBool: 498 boolValue := true 499 if strValue == opsworksFalseString { 500 boolValue = false 501 } 502 d.Set(key, boolValue) 503 default: 504 // should never happen 505 panic(fmt.Errorf("Unsupported OpsWorks layer attribute type")) 506 } 507 return 508 509 } else { 510 d.Set(key, nil) 511 } 512 } 513 } 514 515 func (lt *opsworksLayerType) LifecycleEventConfiguration(d *schema.ResourceData) *opsworks.LifecycleEventConfiguration { 516 return &opsworks.LifecycleEventConfiguration{ 517 Shutdown: &opsworks.ShutdownEventConfiguration{ 518 DelayUntilElbConnectionsDrained: aws.Bool(d.Get("drain_elb_on_shutdown").(bool)), 519 ExecutionTimeout: aws.Int64(int64(d.Get("instance_shutdown_timeout").(int))), 520 }, 521 } 522 } 523 524 func (lt *opsworksLayerType) SetLifecycleEventConfiguration(d *schema.ResourceData, v *opsworks.LifecycleEventConfiguration) { 525 if v == nil || v.Shutdown == nil { 526 d.Set("drain_elb_on_shutdown", nil) 527 d.Set("instance_shutdown_timeout", nil) 528 } else { 529 d.Set("drain_elb_on_shutdown", v.Shutdown.DelayUntilElbConnectionsDrained) 530 d.Set("instance_shutdown_timeout", v.Shutdown.ExecutionTimeout) 531 } 532 } 533 534 func (lt *opsworksLayerType) CustomRecipes(d *schema.ResourceData) *opsworks.Recipes { 535 return &opsworks.Recipes{ 536 Configure: expandStringList(d.Get("custom_configure_recipes").([]interface{})), 537 Deploy: expandStringList(d.Get("custom_deploy_recipes").([]interface{})), 538 Setup: expandStringList(d.Get("custom_setup_recipes").([]interface{})), 539 Shutdown: expandStringList(d.Get("custom_shutdown_recipes").([]interface{})), 540 Undeploy: expandStringList(d.Get("custom_undeploy_recipes").([]interface{})), 541 } 542 } 543 544 func (lt *opsworksLayerType) SetCustomRecipes(d *schema.ResourceData, v *opsworks.Recipes) { 545 // Null out everything first, and then we'll consider what to put back. 546 d.Set("custom_configure_recipes", nil) 547 d.Set("custom_deploy_recipes", nil) 548 d.Set("custom_setup_recipes", nil) 549 d.Set("custom_shutdown_recipes", nil) 550 d.Set("custom_undeploy_recipes", nil) 551 552 if v == nil { 553 return 554 } 555 556 d.Set("custom_configure_recipes", flattenStringList(v.Configure)) 557 d.Set("custom_deploy_recipes", flattenStringList(v.Deploy)) 558 d.Set("custom_setup_recipes", flattenStringList(v.Setup)) 559 d.Set("custom_shutdown_recipes", flattenStringList(v.Shutdown)) 560 d.Set("custom_undeploy_recipes", flattenStringList(v.Undeploy)) 561 } 562 563 func (lt *opsworksLayerType) VolumeConfigurations(d *schema.ResourceData) []*opsworks.VolumeConfiguration { 564 configuredVolumes := d.Get("ebs_volume").(*schema.Set).List() 565 result := make([]*opsworks.VolumeConfiguration, len(configuredVolumes)) 566 567 for i := 0; i < len(configuredVolumes); i++ { 568 volumeData := configuredVolumes[i].(map[string]interface{}) 569 570 result[i] = &opsworks.VolumeConfiguration{ 571 MountPoint: aws.String(volumeData["mount_point"].(string)), 572 NumberOfDisks: aws.Int64(int64(volumeData["number_of_disks"].(int))), 573 Size: aws.Int64(int64(volumeData["size"].(int))), 574 VolumeType: aws.String(volumeData["type"].(string)), 575 } 576 iops := int64(volumeData["iops"].(int)) 577 if iops != 0 { 578 result[i].Iops = aws.Int64(iops) 579 } 580 581 raidLevelStr := volumeData["raid_level"].(string) 582 if raidLevelStr != "" { 583 raidLevel, err := strconv.Atoi(raidLevelStr) 584 if err == nil { 585 result[i].RaidLevel = aws.Int64(int64(raidLevel)) 586 } 587 } 588 } 589 590 return result 591 } 592 593 func (lt *opsworksLayerType) SetVolumeConfigurations(d *schema.ResourceData, v []*opsworks.VolumeConfiguration) { 594 newValue := make([]*map[string]interface{}, len(v)) 595 596 for i := 0; i < len(v); i++ { 597 config := v[i] 598 data := make(map[string]interface{}) 599 newValue[i] = &data 600 601 if config.Iops != nil { 602 data["iops"] = int(*config.Iops) 603 } else { 604 data["iops"] = 0 605 } 606 if config.MountPoint != nil { 607 data["mount_point"] = *config.MountPoint 608 } 609 if config.NumberOfDisks != nil { 610 data["number_of_disks"] = int(*config.NumberOfDisks) 611 } 612 if config.RaidLevel != nil { 613 data["raid_level"] = strconv.Itoa(int(*config.RaidLevel)) 614 } 615 if config.Size != nil { 616 data["size"] = int(*config.Size) 617 } 618 if config.VolumeType != nil { 619 data["type"] = *config.VolumeType 620 } 621 } 622 623 d.Set("ebs_volume", newValue) 624 }