diff --git a/builtin/providers/aws/resource_aws_instance.go b/builtin/providers/aws/resource_aws_instance.go index 35402ba80..fb44542e3 100644 --- a/builtin/providers/aws/resource_aws_instance.go +++ b/builtin/providers/aws/resource_aws_instance.go @@ -190,6 +190,50 @@ func resourceAwsInstance() *schema.Resource { }, Set: resourceAwsInstanceBlockDevicesHash, }, + + "root_block_device": &schema.Schema{ + // TODO: This is a list because we don't support singleton + // sub-resources today. We'll enforce that the list only ever has + // length zero or one below. When TF gains support for + // sub-resources this can be converted. + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + // "You can only modify the volume size, volume type, and Delete on + // Termination flag on the block device mapping entry for the root + // device volume." - bit.ly/ec2bdmap + Schema: map[string]*schema.Schema{ + "delete_on_termination": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + ForceNew: true, + }, + + "device_name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "/dev/sda1", + }, + + "volume_size": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "volume_type": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + }, + }, }, } } @@ -238,19 +282,36 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error { } } + blockDevices := make([]interface{}, 0) + if v := d.Get("block_device"); v != nil { - vs := v.(*schema.Set).List() - if len(vs) > 0 { - runOpts.BlockDevices = make([]ec2.BlockDeviceMapping, len(vs)) - for i, v := range vs { - bd := v.(map[string]interface{}) - runOpts.BlockDevices[i].DeviceName = bd["device_name"].(string) - runOpts.BlockDevices[i].VirtualName = bd["virtual_name"].(string) - runOpts.BlockDevices[i].SnapshotId = bd["snapshot_id"].(string) - runOpts.BlockDevices[i].VolumeType = bd["volume_type"].(string) - runOpts.BlockDevices[i].VolumeSize = int64(bd["volume_size"].(int)) - runOpts.BlockDevices[i].DeleteOnTermination = bd["delete_on_termination"].(bool) - runOpts.BlockDevices[i].Encrypted = bd["encrypted"].(bool) + blockDevices = append(blockDevices, v.(*schema.Set).List()...) + } + + if v := d.Get("root_block_device"); v != nil { + rootBlockDevices := v.([]interface{}) + if len(rootBlockDevices) > 1 { + return fmt.Errorf("Cannot specify more than one root_block_device.") + } + blockDevices = append(blockDevices, rootBlockDevices...) + } + + if len(blockDevices) > 0 { + runOpts.BlockDevices = make([]ec2.BlockDeviceMapping, len(blockDevices)) + for i, v := range blockDevices { + bd := v.(map[string]interface{}) + runOpts.BlockDevices[i].DeviceName = bd["device_name"].(string) + runOpts.BlockDevices[i].VolumeType = bd["volume_type"].(string) + runOpts.BlockDevices[i].VolumeSize = int64(bd["volume_size"].(int)) + runOpts.BlockDevices[i].DeleteOnTermination = bd["delete_on_termination"].(bool) + if v, ok := bd["virtual_name"].(string); ok { + runOpts.BlockDevices[i].VirtualName = v + } + if v, ok := bd["snapshot_id"].(string); ok { + runOpts.BlockDevices[i].SnapshotId = v + } + if v, ok := bd["encrypted"].(bool); ok { + runOpts.BlockDevices[i].Encrypted = v } } } @@ -379,11 +440,6 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error { blockDevices := make(map[string]ec2.BlockDevice) for _, bd := range instance.BlockDevices { - // Skip root device; AWS attaches it automatically and terraform does not - // manage it - if bd.DeviceName == instance.RootDeviceName { - continue - } blockDevices[bd.VolumeId] = bd } @@ -397,21 +453,26 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error { return err } - bds := make([]map[string]interface{}, len(volResp.Volumes)) - for i, vol := range volResp.Volumes { + nonRootBlockDevices := make([]map[string]interface{}, 0) + for _, vol := range volResp.Volumes { volSize, err := strconv.Atoi(vol.Size) if err != nil { return err } - bds[i] = make(map[string]interface{}) - bds[i]["device_name"] = blockDevices[vol.VolumeId].DeviceName - bds[i]["snapshot_id"] = vol.SnapshotId - bds[i]["volume_type"] = vol.VolumeType - bds[i]["volume_size"] = volSize - bds[i]["delete_on_termination"] = blockDevices[vol.VolumeId].DeleteOnTermination - bds[i]["encrypted"] = vol.Encrypted + blockDevice := make(map[string]interface{}) + blockDevice["device_name"] = blockDevices[vol.VolumeId].DeviceName + blockDevice["snapshot_id"] = vol.SnapshotId + blockDevice["volume_type"] = vol.VolumeType + blockDevice["volume_size"] = volSize + blockDevice["delete_on_termination"] = blockDevices[vol.VolumeId].DeleteOnTermination + blockDevice["encrypted"] = vol.Encrypted + if blockDevice["device_name"] == instance.RootDeviceName { + d.Set("root_block_device", []interface{}{blockDevice}) + } else { + nonRootBlockDevices = append(nonRootBlockDevices, blockDevice) + } } - d.Set("block_device", bds) + d.Set("block_device", nonRootBlockDevices) return nil } @@ -429,7 +490,7 @@ func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error { } if modify { - log.Printf("[INFO] Modifing instance %s: %#v", d.Id(), opts) + log.Printf("[INFO] Modifying instance %s: %#v", d.Id(), opts) if _, err := ec2conn.ModifyInstance(d.Id(), opts); err != nil { return err } diff --git a/builtin/providers/aws/resource_aws_instance_test.go b/builtin/providers/aws/resource_aws_instance_test.go index 7275dde92..7c34a3663 100644 --- a/builtin/providers/aws/resource_aws_instance_test.go +++ b/builtin/providers/aws/resource_aws_instance_test.go @@ -66,7 +66,7 @@ func TestAccAWSInstance_normal(t *testing.T) { }) } -func TestAccAWSInstance_blockDevicesCheck(t *testing.T) { +func TestAccAWSInstance_blockDevices(t *testing.T) { var v ec2.Instance testCheck := func() resource.TestCheckFunc { @@ -78,6 +78,11 @@ func TestAccAWSInstance_blockDevicesCheck(t *testing.T) { blockDevices[blockDevice.DeviceName] = blockDevice } + // Check if the root block device exists. + if _, ok := blockDevices["/dev/sda1"]; !ok { + fmt.Errorf("block device doesn't exist: /dev/sda1") + } + // Check if the secondary block device exists. if _, ok := blockDevices["/dev/sdb"]; !ok { fmt.Errorf("block device doesn't exist: /dev/sdb") @@ -97,11 +102,18 @@ func TestAccAWSInstance_blockDevicesCheck(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists( "aws_instance.foo", &v), - // though two block devices exist in EC2, terraform state should only - // have the one block device we created, as terraform does not manage - // the root device + resource.TestCheckResourceAttr( + "aws_instance.foo", "root_block_device.#", "1"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "root_block_device.0.device_name", "/dev/sda1"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "root_block_device.0.volume_size", "11"), resource.TestCheckResourceAttr( "aws_instance.foo", "block_device.#", "1"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "block_device.172787947.device_name", "/dev/sdb"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "block_device.172787947.volume_size", "9"), testCheck(), ), }, @@ -359,10 +371,15 @@ resource "aws_instance" "foo" { # us-west-2 ami = "ami-55a7ea65" instance_type = "m1.small" + root_block_device { + device_name = "/dev/sda1" + volume_type = "gp2" + volume_size = 11 + } block_device { device_name = "/dev/sdb" volume_type = "gp2" - volume_size = 10 + volume_size = 9 } } ` diff --git a/website/source/docs/providers/aws/r/instance.html.markdown b/website/source/docs/providers/aws/r/instance.html.markdown index 25e6cfe36..fc959fa2a 100644 --- a/website/source/docs/providers/aws/r/instance.html.markdown +++ b/website/source/docs/providers/aws/r/instance.html.markdown @@ -48,6 +48,8 @@ The following arguments are supported: launch the instance with. * `tags` - (Optional) A mapping of tags to assign to the resource. * `block_device` - (Optional) A list of block devices to add. Their keys are documented below. +* `root_block_device` - (Optional) Customize details about the root block + device of the instance. Available keys are documented below. Each `block_device` supports the following: @@ -59,6 +61,15 @@ Each `block_device` supports the following: * `delete_on_termination` - (Optional) Should the volume be destroyed on instance termination (defaults true). * `encrypted` - (Optional) Should encryption be enabled (defaults false). +The `root_block_device` mapping supports the following: + +* `device_name` - The name of the root device on the target instance. Must + match the root device as defined in the AMI. Defaults to "/dev/sda1", which + is the typical root volume for Linux instances. +* `volume_type` - (Optional) The type of volume. Can be standard, gp2, or io1. Defaults to standard. +* `volume_size` - (Optional) The size of the volume in gigabytes. +* `delete_on_termination` - (Optional) Should the volume be destroyed on instance termination (defaults true). + ## Attributes Reference The following attributes are exported: