From c79ee0121d95755d54d71059db7ca9bf1753df88 Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Tue, 21 Aug 2018 13:17:24 -0400 Subject: [PATCH] resource/aws_launch_template: Prevent encrypted flag cannot be specified error with block_device_mappings ebs argument * Convert `block_device_mappings` > `ebs` > `delete_on_termination` and `encrypted` to `schema.TypeString`. This to allow an "unspecified" value for the attributes since `schema.TypeBool` only has true/false with false default. The conversion from bare true/false values in configurations to `schema.TypeString` value is currently safe. Previously: ``` --- FAIL: TestAccAWSLaunchTemplate_BlockDeviceMappings_EBS (11.23s) testing.go:527: Step 0 error: Error applying: 1 error occurred: * aws_autoscaling_group.test: 1 error occurred: * aws_autoscaling_group.test: Error creating AutoScaling Group: ValidationError: You must use a valid fully-formed launch template. the encrypted flag cannot be specified since device /dev/sda1 has a snapshot specified. ``` After code adjustments: ``` make testacc TEST=./aws TESTARGS='-run=TestAccAWSLaunchTemplate_' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws -v -run=TestAccAWSLaunchTemplate_ -timeout 120m === RUN TestAccAWSLaunchTemplate_importBasic --- PASS: TestAccAWSLaunchTemplate_importBasic (13.63s) === RUN TestAccAWSLaunchTemplate_importData --- PASS: TestAccAWSLaunchTemplate_importData (12.00s) === RUN TestAccAWSLaunchTemplate_basic --- PASS: TestAccAWSLaunchTemplate_basic (12.71s) === RUN TestAccAWSLaunchTemplate_BlockDeviceMappings_EBS --- PASS: TestAccAWSLaunchTemplate_BlockDeviceMappings_EBS (48.73s) === RUN TestAccAWSLaunchTemplate_data --- PASS: TestAccAWSLaunchTemplate_data (13.59s) === RUN TestAccAWSLaunchTemplate_update --- PASS: TestAccAWSLaunchTemplate_update (46.88s) === RUN TestAccAWSLaunchTemplate_tags --- PASS: TestAccAWSLaunchTemplate_tags (21.37s) === RUN TestAccAWSLaunchTemplate_nonBurstable --- PASS: TestAccAWSLaunchTemplate_nonBurstable (11.47s) === RUN TestAccAWSLaunchTemplate_networkInterface --- PASS: TestAccAWSLaunchTemplate_networkInterface (30.44s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 211.533s ``` --- aws/resource_aws_launch_template.go | 76 ++++++++++++++++++------ aws/resource_aws_launch_template_test.go | 21 ++++++- 2 files changed, 76 insertions(+), 21 deletions(-) diff --git a/aws/resource_aws_launch_template.go b/aws/resource_aws_launch_template.go index c599182c3c1..5bb5f6aeac8 100644 --- a/aws/resource_aws_launch_template.go +++ b/aws/resource_aws_launch_template.go @@ -89,12 +89,30 @@ func resourceAwsLaunchTemplate() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "delete_on_termination": { - Type: schema.TypeBool, + // Use TypeString to allow an "unspecified" value, + // since TypeBool only has true/false with false default. + // The conversion from bare true/false values in + // configurations to TypeString value is currently safe. + Type: schema.TypeString, Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "", + "false", + "true", + }, false), }, "encrypted": { - Type: schema.TypeBool, + // Use TypeString to allow an "unspecified" value, + // since TypeBool only has true/false with false default. + // The conversion from bare true/false values in + // configurations to TypeString value is currently safe. + Type: schema.TypeString, Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "", + "false", + "true", + }, false), }, "iops": { Type: schema.TypeInt, @@ -638,14 +656,18 @@ func getBlockDeviceMappings(m []*ec2.LaunchTemplateBlockDeviceMapping) []interfa "virtual_name": aws.StringValue(v.VirtualName), } if v.NoDevice != nil { - mapping["no_device"] = *v.NoDevice + mapping["no_device"] = aws.StringValue(v.NoDevice) } if v.Ebs != nil { ebs := map[string]interface{}{ - "delete_on_termination": aws.BoolValue(v.Ebs.DeleteOnTermination), - "encrypted": aws.BoolValue(v.Ebs.Encrypted), - "volume_size": int(aws.Int64Value(v.Ebs.VolumeSize)), - "volume_type": aws.StringValue(v.Ebs.VolumeType), + "volume_size": int(aws.Int64Value(v.Ebs.VolumeSize)), + "volume_type": aws.StringValue(v.Ebs.VolumeType), + } + if v.Ebs.DeleteOnTermination != nil { + ebs["delete_on_termination"] = strconv.FormatBool(aws.BoolValue(v.Ebs.DeleteOnTermination)) + } + if v.Ebs.Encrypted != nil { + ebs["encrypted"] = strconv.FormatBool(aws.BoolValue(v.Ebs.Encrypted)) } if v.Ebs.Iops != nil { ebs["iops"] = aws.Int64Value(v.Ebs.Iops) @@ -857,7 +879,11 @@ func buildLaunchTemplateData(d *schema.ResourceData, meta interface{}) (*ec2.Req bdms := v.([]interface{}) for _, bdm := range bdms { - blockDeviceMappings = append(blockDeviceMappings, readBlockDeviceMappingFromConfig(bdm.(map[string]interface{}))) + blockDeviceMapping, err := readBlockDeviceMappingFromConfig(bdm.(map[string]interface{})) + if err != nil { + return nil, err + } + blockDeviceMappings = append(blockDeviceMappings, blockDeviceMapping) } opts.BlockDeviceMappings = blockDeviceMappings } @@ -950,7 +976,7 @@ func buildLaunchTemplateData(d *schema.ResourceData, meta interface{}) (*ec2.Req return opts, nil } -func readBlockDeviceMappingFromConfig(bdm map[string]interface{}) *ec2.LaunchTemplateBlockDeviceMappingRequest { +func readBlockDeviceMappingFromConfig(bdm map[string]interface{}) (*ec2.LaunchTemplateBlockDeviceMappingRequest, error) { blockDeviceMapping := &ec2.LaunchTemplateBlockDeviceMappingRequest{} if v := bdm["device_name"].(string); v != "" { @@ -967,24 +993,36 @@ func readBlockDeviceMappingFromConfig(bdm map[string]interface{}) *ec2.LaunchTem if v := bdm["ebs"]; len(v.([]interface{})) > 0 { ebs := v.([]interface{}) - if len(ebs) > 0 { - ebsData := ebs[0] - blockDeviceMapping.Ebs = readEbsBlockDeviceFromConfig(ebsData.(map[string]interface{})) + if len(ebs) > 0 && ebs[0] != nil { + ebsData := ebs[0].(map[string]interface{}) + launchTemplateEbsBlockDeviceRequest, err := readEbsBlockDeviceFromConfig(ebsData) + if err != nil { + return nil, err + } + blockDeviceMapping.Ebs = launchTemplateEbsBlockDeviceRequest } } - return blockDeviceMapping + return blockDeviceMapping, nil } -func readEbsBlockDeviceFromConfig(ebs map[string]interface{}) *ec2.LaunchTemplateEbsBlockDeviceRequest { +func readEbsBlockDeviceFromConfig(ebs map[string]interface{}) (*ec2.LaunchTemplateEbsBlockDeviceRequest, error) { ebsDevice := &ec2.LaunchTemplateEbsBlockDeviceRequest{} - if v := ebs["delete_on_termination"]; v != nil { - ebsDevice.DeleteOnTermination = aws.Bool(v.(bool)) + if v, ok := ebs["delete_on_termination"]; ok && v.(string) != "" { + vBool, err := strconv.ParseBool(v.(string)) + if err != nil { + return nil, fmt.Errorf("error converting delete_on_termination %q from string to boolean: %s", v.(string), err) + } + ebsDevice.DeleteOnTermination = aws.Bool(vBool) } - if v := ebs["encrypted"]; v != nil { - ebsDevice.Encrypted = aws.Bool(v.(bool)) + if v, ok := ebs["encrypted"]; ok && v.(string) != "" { + vBool, err := strconv.ParseBool(v.(string)) + if err != nil { + return nil, fmt.Errorf("error converting encrypted %q from string to boolean: %s", v.(string), err) + } + ebsDevice.Encrypted = aws.Bool(vBool) } if v := ebs["iops"].(int); v > 0 { @@ -1007,7 +1045,7 @@ func readEbsBlockDeviceFromConfig(ebs map[string]interface{}) *ec2.LaunchTemplat ebsDevice.VolumeType = aws.String(v) } - return ebsDevice + return ebsDevice, nil } func readNetworkInterfacesFromConfig(ni map[string]interface{}) *ec2.LaunchTemplateInstanceNetworkInterfaceSpecificationRequest { diff --git a/aws/resource_aws_launch_template_test.go b/aws/resource_aws_launch_template_test.go index 151b7011ef7..7149997b713 100644 --- a/aws/resource_aws_launch_template_test.go +++ b/aws/resource_aws_launch_template_test.go @@ -293,9 +293,11 @@ data "aws_ami" "test" { } } +data "aws_availability_zones" "available" {} + resource "aws_launch_template" "test" { image_id = "${data.aws_ami.test.id}" - name = "%s" + name = %q block_device_mappings { device_name = "/dev/sda1" @@ -305,7 +307,22 @@ resource "aws_launch_template" "test" { } } } -`, rName) + +# Creating an AutoScaling Group verifies the launch template +# ValidationError: You must use a valid fully-formed launch template. the encrypted flag cannot be specified since device /dev/sda1 has a snapshot specified. +resource "aws_autoscaling_group" "test" { + availability_zones = ["${data.aws_availability_zones.available.names[0]}"] + desired_capacity = 0 + max_size = 0 + min_size = 0 + name = %q + + launch_template { + id = "${aws_launch_template.test.id}" + version = "${aws_launch_template.test.default_version}" + } +} +`, rName, rName) } func testAccAWSLaunchTemplateConfig_data(rInt int) string {