Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

providers/aws: add root_block_device to aws_instance #998

Merged
merged 1 commit into from
Feb 18, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 89 additions & 28 deletions builtin/providers/aws/resource_aws_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this at the bottom? /nitpick.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't see a coherent order. You'd prefer alphabetical?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH I'd prefer alphabetical, but that's prolly a separate PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OH I can at least alphabetize the sub-schema. 👍


"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,
},
},
},
},
},
}
}
Expand Down Expand Up @@ -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
}
}
}
Expand Down Expand Up @@ -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
}

Expand All @@ -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
}
Expand All @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😑

if _, err := ec2conn.ModifyInstance(d.Id(), opts); err != nil {
return err
}
Expand Down
27 changes: 22 additions & 5 deletions builtin/providers/aws/resource_aws_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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")
Expand All @@ -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(),
),
},
Expand Down Expand Up @@ -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
}
}
`
Expand Down
11 changes: 11 additions & 0 deletions website/source/docs/providers/aws/r/instance.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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:
Expand Down