Merge pull request #998 from hashicorp/f-aws-instance-root-block-device
providers/aws: add root_block_device to aws_instance
This commit is contained in:
commit
1aebf543c7
|
@ -190,6 +190,50 @@ func resourceAwsInstance() *schema.Resource {
|
||||||
},
|
},
|
||||||
Set: resourceAwsInstanceBlockDevicesHash,
|
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 {
|
if v := d.Get("block_device"); v != nil {
|
||||||
vs := v.(*schema.Set).List()
|
blockDevices = append(blockDevices, v.(*schema.Set).List()...)
|
||||||
if len(vs) > 0 {
|
}
|
||||||
runOpts.BlockDevices = make([]ec2.BlockDeviceMapping, len(vs))
|
|
||||||
for i, v := range vs {
|
if v := d.Get("root_block_device"); v != nil {
|
||||||
bd := v.(map[string]interface{})
|
rootBlockDevices := v.([]interface{})
|
||||||
runOpts.BlockDevices[i].DeviceName = bd["device_name"].(string)
|
if len(rootBlockDevices) > 1 {
|
||||||
runOpts.BlockDevices[i].VirtualName = bd["virtual_name"].(string)
|
return fmt.Errorf("Cannot specify more than one root_block_device.")
|
||||||
runOpts.BlockDevices[i].SnapshotId = bd["snapshot_id"].(string)
|
}
|
||||||
runOpts.BlockDevices[i].VolumeType = bd["volume_type"].(string)
|
blockDevices = append(blockDevices, rootBlockDevices...)
|
||||||
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)
|
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)
|
blockDevices := make(map[string]ec2.BlockDevice)
|
||||||
for _, bd := range instance.BlockDevices {
|
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
|
blockDevices[bd.VolumeId] = bd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,21 +453,26 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
bds := make([]map[string]interface{}, len(volResp.Volumes))
|
nonRootBlockDevices := make([]map[string]interface{}, 0)
|
||||||
for i, vol := range volResp.Volumes {
|
for _, vol := range volResp.Volumes {
|
||||||
volSize, err := strconv.Atoi(vol.Size)
|
volSize, err := strconv.Atoi(vol.Size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
bds[i] = make(map[string]interface{})
|
blockDevice := make(map[string]interface{})
|
||||||
bds[i]["device_name"] = blockDevices[vol.VolumeId].DeviceName
|
blockDevice["device_name"] = blockDevices[vol.VolumeId].DeviceName
|
||||||
bds[i]["snapshot_id"] = vol.SnapshotId
|
blockDevice["snapshot_id"] = vol.SnapshotId
|
||||||
bds[i]["volume_type"] = vol.VolumeType
|
blockDevice["volume_type"] = vol.VolumeType
|
||||||
bds[i]["volume_size"] = volSize
|
blockDevice["volume_size"] = volSize
|
||||||
bds[i]["delete_on_termination"] = blockDevices[vol.VolumeId].DeleteOnTermination
|
blockDevice["delete_on_termination"] = blockDevices[vol.VolumeId].DeleteOnTermination
|
||||||
bds[i]["encrypted"] = vol.Encrypted
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -429,7 +490,7 @@ func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if modify {
|
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 {
|
if _, err := ec2conn.ModifyInstance(d.Id(), opts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
var v ec2.Instance
|
||||||
|
|
||||||
testCheck := func() resource.TestCheckFunc {
|
testCheck := func() resource.TestCheckFunc {
|
||||||
|
@ -78,6 +78,11 @@ func TestAccAWSInstance_blockDevicesCheck(t *testing.T) {
|
||||||
blockDevices[blockDevice.DeviceName] = blockDevice
|
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.
|
// Check if the secondary block device exists.
|
||||||
if _, ok := blockDevices["/dev/sdb"]; !ok {
|
if _, ok := blockDevices["/dev/sdb"]; !ok {
|
||||||
fmt.Errorf("block device doesn't exist: /dev/sdb")
|
fmt.Errorf("block device doesn't exist: /dev/sdb")
|
||||||
|
@ -97,11 +102,18 @@ func TestAccAWSInstance_blockDevicesCheck(t *testing.T) {
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckInstanceExists(
|
testAccCheckInstanceExists(
|
||||||
"aws_instance.foo", &v),
|
"aws_instance.foo", &v),
|
||||||
// though two block devices exist in EC2, terraform state should only
|
resource.TestCheckResourceAttr(
|
||||||
// have the one block device we created, as terraform does not manage
|
"aws_instance.foo", "root_block_device.#", "1"),
|
||||||
// the root device
|
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(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_instance.foo", "block_device.#", "1"),
|
"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(),
|
testCheck(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -359,10 +371,15 @@ resource "aws_instance" "foo" {
|
||||||
# us-west-2
|
# us-west-2
|
||||||
ami = "ami-55a7ea65"
|
ami = "ami-55a7ea65"
|
||||||
instance_type = "m1.small"
|
instance_type = "m1.small"
|
||||||
|
root_block_device {
|
||||||
|
device_name = "/dev/sda1"
|
||||||
|
volume_type = "gp2"
|
||||||
|
volume_size = 11
|
||||||
|
}
|
||||||
block_device {
|
block_device {
|
||||||
device_name = "/dev/sdb"
|
device_name = "/dev/sdb"
|
||||||
volume_type = "gp2"
|
volume_type = "gp2"
|
||||||
volume_size = 10
|
volume_size = 9
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
|
@ -48,6 +48,8 @@ The following arguments are supported:
|
||||||
launch the instance with.
|
launch the instance with.
|
||||||
* `tags` - (Optional) A mapping of tags to assign to the resource.
|
* `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.
|
* `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:
|
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).
|
* `delete_on_termination` - (Optional) Should the volume be destroyed on instance termination (defaults true).
|
||||||
* `encrypted` - (Optional) Should encryption be enabled (defaults false).
|
* `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
|
## Attributes Reference
|
||||||
|
|
||||||
The following attributes are exported:
|
The following attributes are exported:
|
||||||
|
|
Loading…
Reference in New Issue