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