From 80afc6759beab55fafd0b4fd94d69b5439e36d0c Mon Sep 17 00:00:00 2001 From: Timon Wong Date: Thu, 8 Dec 2016 18:03:51 +0800 Subject: [PATCH] provider/aws: Add "no_device" support to ephemeral block devices (#10547) Fixes #8455, #5390 This add a new `no_device` attribute to `ephemeral_block_device` block, which allows users omit ephemeral devices from AMI's predefined block device mappings, which is useful for EBS-only instance types. --- .../providers/aws/resource_aws_instance.go | 25 ++++- .../aws/resource_aws_instance_test.go | 93 +++++++++++++++++++ .../providers/aws/r/instance.html.markdown | 5 +- 3 files changed, 118 insertions(+), 5 deletions(-) diff --git a/builtin/providers/aws/resource_aws_instance.go b/builtin/providers/aws/resource_aws_instance.go index ed9691251..f19cd4204 100644 --- a/builtin/providers/aws/resource_aws_instance.go +++ b/builtin/providers/aws/resource_aws_instance.go @@ -268,7 +268,12 @@ func resourceAwsInstance() *schema.Resource { "virtual_name": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, + }, + + "no_device": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, }, }, }, @@ -277,6 +282,9 @@ func resourceAwsInstance() *schema.Resource { m := v.(map[string]interface{}) buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) buf.WriteString(fmt.Sprintf("%s-", m["virtual_name"].(string))) + if v, ok := m["no_device"].(bool); ok && v { + buf.WriteString(fmt.Sprintf("%t-", v)) + } return hashcode.String(buf.String()) }, }, @@ -936,10 +944,21 @@ func readBlockDeviceMappingsFromConfig( vL := v.(*schema.Set).List() for _, v := range vL { bd := v.(map[string]interface{}) - blockDevices = append(blockDevices, &ec2.BlockDeviceMapping{ + bdm := &ec2.BlockDeviceMapping{ DeviceName: aws.String(bd["device_name"].(string)), VirtualName: aws.String(bd["virtual_name"].(string)), - }) + } + if v, ok := bd["no_device"].(bool); ok && v { + bdm.NoDevice = aws.String("") + // When NoDevice is true, just ignore VirtualName since it's not needed + bdm.VirtualName = nil + } + + if bdm.NoDevice == nil && aws.StringValue(bdm.VirtualName) == "" { + return nil, fmt.Errorf("virtual_name cannot be empty when no_device is false or undefined.") + } + + blockDevices = append(blockDevices, bdm) } } diff --git a/builtin/providers/aws/resource_aws_instance_test.go b/builtin/providers/aws/resource_aws_instance_test.go index 38c2dccfc..e7ad81c74 100644 --- a/builtin/providers/aws/resource_aws_instance_test.go +++ b/builtin/providers/aws/resource_aws_instance_test.go @@ -285,6 +285,99 @@ func TestAccAWSInstance_rootInstanceStore(t *testing.T) { }) } +func TestAcctABSInstance_noAMIEphemeralDevices(t *testing.T) { + var v ec2.Instance + + testCheck := func() resource.TestCheckFunc { + return func(*terraform.State) error { + + // Map out the block devices by name, which should be unique. + blockDevices := make(map[string]*ec2.InstanceBlockDeviceMapping) + for _, blockDevice := range v.BlockDeviceMappings { + blockDevices[*blockDevice.DeviceName] = blockDevice + } + + // Check if the root block device exists. + if _, ok := blockDevices["/dev/sda1"]; !ok { + return fmt.Errorf("block device doesn't exist: /dev/sda1") + } + + // Check if the secondary block not exists. + if _, ok := blockDevices["/dev/sdb"]; ok { + return fmt.Errorf("block device exist: /dev/sdb") + } + + // Check if the third block device not exists. + if _, ok := blockDevices["/dev/sdc"]; ok { + return fmt.Errorf("block device exist: /dev/sdc") + } + return nil + } + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "aws_instance.foo", + IDRefreshIgnore: []string{ + "ephemeral_block_device", "security_groups", "vpc_security_groups"}, + Providers: testAccProviders, + CheckDestroy: testAccCheckInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: ` + resource "aws_instance" "foo" { + # us-west-2 + ami = "ami-01f05461" // This AMI (Ubuntu) contains two ephemerals + + instance_type = "c3.large" + + root_block_device { + volume_type = "gp2" + volume_size = 11 + } + ephemeral_block_device { + device_name = "/dev/sdb" + no_device = true + } + ephemeral_block_device { + device_name = "/dev/sdc" + no_device = true + } + }`, + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceExists( + "aws_instance.foo", &v), + resource.TestCheckResourceAttr( + "aws_instance.foo", "ami", "ami-01f05461"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "ebs_optimized", "false"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "instance_type", "c3.large"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "root_block_device.#", "1"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "root_block_device.0.volume_size", "11"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "root_block_device.0.volume_type", "gp2"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "ebs_block_device.#", "0"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "ephemeral_block_device.#", "2"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "ephemeral_block_device.172787947.device_name", "/dev/sdb"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "ephemeral_block_device.172787947.no_device", "true"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "ephemeral_block_device.3336996981.device_name", "/dev/sdc"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "ephemeral_block_device.3336996981.no_device", "true"), + testCheck(), + ), + }, + }, + }) +} + func TestAccAWSInstance_sourceDestCheck(t *testing.T) { var v ec2.Instance diff --git a/website/source/docs/providers/aws/r/instance.html.markdown b/website/source/docs/providers/aws/r/instance.html.markdown index cd331da91..6a6ef6941 100644 --- a/website/source/docs/providers/aws/r/instance.html.markdown +++ b/website/source/docs/providers/aws/r/instance.html.markdown @@ -126,9 +126,10 @@ Modifying any `ebs_block_device` currently requires resource replacement. Each `ephemeral_block_device` supports the following: * `device_name` - The name of the block device to mount on the instance. -* `virtual_name` - The [Instance Store Device +* `virtual_name` - (Optional) The [Instance Store Device Name](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/InstanceStorage.html#InstanceStoreDeviceNames) - (e.g. `"ephemeral0"`) + (e.g. `"ephemeral0"`). +* `no_device` - (Optional) Suppresses the specified device included in the AMI's block device mapping. Each AWS Instance type has a different set of Instance Store block devices available for attachment. AWS [publishes a