Merge pull request #1045 from hashicorp/f-block-devices
providers/aws: rework instance block devices
This commit is contained in:
commit
46b63074e0
|
@ -24,6 +24,9 @@ func resourceAwsInstance() *schema.Resource {
|
||||||
Update: resourceAwsInstanceUpdate,
|
Update: resourceAwsInstanceUpdate,
|
||||||
Delete: resourceAwsInstanceDelete,
|
Delete: resourceAwsInstanceDelete,
|
||||||
|
|
||||||
|
SchemaVersion: 1,
|
||||||
|
MigrateState: resourceAwsInstanceMigrateState,
|
||||||
|
|
||||||
Schema: map[string]*schema.Schema{
|
Schema: map[string]*schema.Schema{
|
||||||
"ami": &schema.Schema{
|
"ami": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
|
@ -127,53 +130,28 @@ func resourceAwsInstance() *schema.Resource {
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"tenancy": &schema.Schema{
|
"tenancy": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"tags": tagsSchema(),
|
"tags": tagsSchema(),
|
||||||
|
|
||||||
"block_device": &schema.Schema{
|
"block_device": &schema.Schema{
|
||||||
|
Type: schema.TypeMap,
|
||||||
|
Optional: true,
|
||||||
|
Removed: "Split out into three sub-types; see Changelog and Docs",
|
||||||
|
},
|
||||||
|
|
||||||
|
"ebs_block_device": &schema.Schema{
|
||||||
Type: schema.TypeSet,
|
Type: schema.TypeSet,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
Elem: &schema.Resource{
|
Elem: &schema.Resource{
|
||||||
Schema: map[string]*schema.Schema{
|
Schema: map[string]*schema.Schema{
|
||||||
"device_name": &schema.Schema{
|
|
||||||
Type: schema.TypeString,
|
|
||||||
Required: true,
|
|
||||||
ForceNew: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
"virtual_name": &schema.Schema{
|
|
||||||
Type: schema.TypeString,
|
|
||||||
Optional: true,
|
|
||||||
ForceNew: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
"snapshot_id": &schema.Schema{
|
|
||||||
Type: schema.TypeString,
|
|
||||||
Optional: true,
|
|
||||||
Computed: true,
|
|
||||||
ForceNew: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
"volume_type": &schema.Schema{
|
|
||||||
Type: schema.TypeString,
|
|
||||||
Optional: true,
|
|
||||||
Computed: true,
|
|
||||||
ForceNew: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
"volume_size": &schema.Schema{
|
|
||||||
Type: schema.TypeInt,
|
|
||||||
Optional: true,
|
|
||||||
Computed: true,
|
|
||||||
ForceNew: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
"delete_on_termination": &schema.Schema{
|
"delete_on_termination": &schema.Schema{
|
||||||
Type: schema.TypeBool,
|
Type: schema.TypeBool,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
@ -181,6 +159,12 @@ func resourceAwsInstance() *schema.Resource {
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"device_name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
"encrypted": &schema.Schema{
|
"encrypted": &schema.Schema{
|
||||||
Type: schema.TypeBool,
|
Type: schema.TypeBool,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
@ -194,17 +178,79 @@ func resourceAwsInstance() *schema.Resource {
|
||||||
Computed: true,
|
Computed: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"snapshot_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Set: resourceAwsInstanceBlockDevicesHash,
|
},
|
||||||
|
Set: func(v interface{}) int {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
m := v.(map[string]interface{})
|
||||||
|
buf.WriteString(fmt.Sprintf("%t-", m["delete_on_termination"].(bool)))
|
||||||
|
buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
|
||||||
|
buf.WriteString(fmt.Sprintf("%t-", m["encrypted"].(bool)))
|
||||||
|
// NOTE: Not considering IOPS in hash; when using gp2, IOPS can come
|
||||||
|
// back set to something like "33", which throws off the set
|
||||||
|
// calculation and generates an unresolvable diff.
|
||||||
|
// buf.WriteString(fmt.Sprintf("%d-", m["iops"].(int)))
|
||||||
|
buf.WriteString(fmt.Sprintf("%s-", m["snapshot_id"].(string)))
|
||||||
|
buf.WriteString(fmt.Sprintf("%d-", m["volume_size"].(int)))
|
||||||
|
buf.WriteString(fmt.Sprintf("%s-", m["volume_type"].(string)))
|
||||||
|
return hashcode.String(buf.String())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"ephemeral_block_device": &schema.Schema{
|
||||||
|
Type: schema.TypeSet,
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"device_name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"virtual_name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Set: func(v interface{}) int {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
m := v.(map[string]interface{})
|
||||||
|
buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
|
||||||
|
buf.WriteString(fmt.Sprintf("%s-", m["virtual_name"].(string)))
|
||||||
|
return hashcode.String(buf.String())
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
"root_block_device": &schema.Schema{
|
"root_block_device": &schema.Schema{
|
||||||
// TODO: This is a list because we don't support singleton
|
// TODO: This is a set because we don't support singleton
|
||||||
// sub-resources today. We'll enforce that the list only ever has
|
// sub-resources today. We'll enforce that the set only ever has
|
||||||
// length zero or one below. When TF gains support for
|
// length zero or one below. When TF gains support for
|
||||||
// sub-resources this can be converted.
|
// sub-resources this can be converted.
|
||||||
Type: schema.TypeList,
|
Type: schema.TypeSet,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
Elem: &schema.Resource{
|
Elem: &schema.Resource{
|
||||||
|
@ -226,6 +272,13 @@ func resourceAwsInstance() *schema.Resource {
|
||||||
Default: "/dev/sda1",
|
Default: "/dev/sda1",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"iops": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
"volume_size": &schema.Schema{
|
"volume_size": &schema.Schema{
|
||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
@ -239,14 +292,18 @@ func resourceAwsInstance() *schema.Resource {
|
||||||
Computed: true,
|
Computed: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"iops": &schema.Schema{
|
|
||||||
Type: schema.TypeInt,
|
|
||||||
Optional: true,
|
|
||||||
Computed: true,
|
|
||||||
ForceNew: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Set: func(v interface{}) int {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
m := v.(map[string]interface{})
|
||||||
|
buf.WriteString(fmt.Sprintf("%t-", m["delete_on_termination"].(bool)))
|
||||||
|
buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
|
||||||
|
// See the NOTE in "ebs_block_device" for why we skip iops here.
|
||||||
|
// buf.WriteString(fmt.Sprintf("%d-", m["iops"].(int)))
|
||||||
|
buf.WriteString(fmt.Sprintf("%d-", m["volume_size"].(int)))
|
||||||
|
buf.WriteString(fmt.Sprintf("%s-", m["volume_type"].(string)))
|
||||||
|
return hashcode.String(buf.String())
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -349,44 +406,85 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
runOpts.KeyName = aws.String(v.(string))
|
runOpts.KeyName = aws.String(v.(string))
|
||||||
}
|
}
|
||||||
|
|
||||||
blockDevices := make([]interface{}, 0)
|
blockDevices := make([]ec2.BlockDeviceMapping, 0)
|
||||||
|
|
||||||
if v := d.Get("block_device"); v != nil {
|
if v, ok := d.GetOk("ebs_block_device"); ok {
|
||||||
blockDevices = append(blockDevices, v.(*schema.Set).List()...)
|
vL := v.(*schema.Set).List()
|
||||||
}
|
for _, v := range vL {
|
||||||
|
|
||||||
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.BlockDeviceMappings = make([]ec2.BlockDeviceMapping, len(blockDevices))
|
|
||||||
for i, v := range blockDevices {
|
|
||||||
bd := v.(map[string]interface{})
|
bd := v.(map[string]interface{})
|
||||||
runOpts.BlockDeviceMappings[i].DeviceName = aws.String(bd["device_name"].(string))
|
ebs := &ec2.EBSBlockDevice{
|
||||||
runOpts.BlockDeviceMappings[i].EBS = &ec2.EBSBlockDevice{
|
|
||||||
VolumeType: aws.String(bd["volume_type"].(string)),
|
|
||||||
VolumeSize: aws.Integer(bd["volume_size"].(int)),
|
|
||||||
DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)),
|
DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)),
|
||||||
}
|
}
|
||||||
|
|
||||||
if v, ok := bd["virtual_name"].(string); ok {
|
|
||||||
runOpts.BlockDeviceMappings[i].VirtualName = aws.String(v)
|
|
||||||
}
|
|
||||||
if v, ok := bd["snapshot_id"].(string); ok && v != "" {
|
if v, ok := bd["snapshot_id"].(string); ok && v != "" {
|
||||||
runOpts.BlockDeviceMappings[i].EBS.SnapshotID = aws.String(v)
|
ebs.SnapshotID = aws.String(v)
|
||||||
}
|
}
|
||||||
if v, ok := bd["encrypted"].(bool); ok {
|
|
||||||
runOpts.BlockDeviceMappings[i].EBS.Encrypted = aws.Boolean(v)
|
if v, ok := bd["volume_size"].(int); ok && v != 0 {
|
||||||
|
ebs.VolumeSize = aws.Integer(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v, ok := bd["volume_type"].(string); ok && v != "" {
|
||||||
|
ebs.VolumeType = aws.String(v)
|
||||||
|
}
|
||||||
|
|
||||||
if v, ok := bd["iops"].(int); ok && v > 0 {
|
if v, ok := bd["iops"].(int); ok && v > 0 {
|
||||||
runOpts.BlockDeviceMappings[i].EBS.IOPS = aws.Integer(v)
|
ebs.IOPS = aws.Integer(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
blockDevices = append(blockDevices, ec2.BlockDeviceMapping{
|
||||||
|
DeviceName: aws.String(bd["device_name"].(string)),
|
||||||
|
EBS: ebs,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v, ok := d.GetOk("ephemeral_block_device"); ok {
|
||||||
|
vL := v.(*schema.Set).List()
|
||||||
|
for _, v := range vL {
|
||||||
|
bd := v.(map[string]interface{})
|
||||||
|
blockDevices = append(blockDevices, ec2.BlockDeviceMapping{
|
||||||
|
DeviceName: aws.String(bd["device_name"].(string)),
|
||||||
|
VirtualName: aws.String(bd["virtual_name"].(string)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// if err := d.Set("ephemeral_block_device", vL); err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := d.GetOk("root_block_device"); ok {
|
||||||
|
vL := v.(*schema.Set).List()
|
||||||
|
if len(vL) > 1 {
|
||||||
|
return fmt.Errorf("Cannot specify more than one root_block_device.")
|
||||||
|
}
|
||||||
|
for _, v := range vL {
|
||||||
|
bd := v.(map[string]interface{})
|
||||||
|
ebs := &ec2.EBSBlockDevice{
|
||||||
|
DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)),
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := bd["volume_size"].(int); ok && v != 0 {
|
||||||
|
ebs.VolumeSize = aws.Integer(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := bd["volume_type"].(string); ok && v != "" {
|
||||||
|
ebs.VolumeType = aws.String(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := bd["iops"].(int); ok && v > 0 {
|
||||||
|
ebs.IOPS = aws.Integer(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
blockDevices = append(blockDevices, ec2.BlockDeviceMapping{
|
||||||
|
DeviceName: aws.String(bd["device_name"].(string)),
|
||||||
|
EBS: ebs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(blockDevices) > 0 {
|
||||||
|
runOpts.BlockDeviceMappings = blockDevices
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the instance
|
// Create the instance
|
||||||
|
@ -520,50 +618,10 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
}
|
}
|
||||||
d.Set("security_groups", sgs)
|
d.Set("security_groups", sgs)
|
||||||
|
|
||||||
blockDevices := make(map[string]ec2.InstanceBlockDeviceMapping)
|
if err := readBlockDevices(d, instance, ec2conn); err != nil {
|
||||||
for _, bd := range instance.BlockDeviceMappings {
|
|
||||||
blockDevices[*bd.EBS.VolumeID] = bd
|
|
||||||
}
|
|
||||||
|
|
||||||
volIDs := make([]string, 0, len(blockDevices))
|
|
||||||
for _, vol := range blockDevices {
|
|
||||||
volIDs = append(volIDs, *vol.EBS.VolumeID)
|
|
||||||
}
|
|
||||||
|
|
||||||
volResp, err := ec2conn.DescribeVolumes(&ec2.DescribeVolumesRequest{
|
|
||||||
VolumeIDs: volIDs,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
nonRootBlockDevices := make([]map[string]interface{}, 0)
|
|
||||||
rootBlockDevice := make([]interface{}, 0, 1)
|
|
||||||
for _, vol := range volResp.Volumes {
|
|
||||||
blockDevice := make(map[string]interface{})
|
|
||||||
blockDevice["device_name"] = *blockDevices[*vol.VolumeID].DeviceName
|
|
||||||
blockDevice["volume_type"] = *vol.VolumeType
|
|
||||||
blockDevice["volume_size"] = *vol.Size
|
|
||||||
if vol.IOPS != nil {
|
|
||||||
blockDevice["iops"] = *vol.IOPS
|
|
||||||
}
|
|
||||||
blockDevice["delete_on_termination"] =
|
|
||||||
*blockDevices[*vol.VolumeID].EBS.DeleteOnTermination
|
|
||||||
|
|
||||||
// If this is the root device, save it. We stop here since we
|
|
||||||
// can't put invalid keys into this map.
|
|
||||||
if blockDevice["device_name"] == *instance.RootDeviceName {
|
|
||||||
rootBlockDevice = []interface{}{blockDevice}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
blockDevice["snapshot_id"] = *vol.SnapshotID
|
|
||||||
blockDevice["encrypted"] = *vol.Encrypted
|
|
||||||
nonRootBlockDevices = append(nonRootBlockDevices, blockDevice)
|
|
||||||
}
|
|
||||||
d.Set("block_device", nonRootBlockDevices)
|
|
||||||
d.Set("root_block_device", rootBlockDevice)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -659,11 +717,89 @@ func InstanceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsInstanceBlockDevicesHash(v interface{}) int {
|
func readBlockDevices(d *schema.ResourceData, instance *ec2.Instance, ec2conn *ec2.EC2) error {
|
||||||
var buf bytes.Buffer
|
ibds, err := readBlockDevicesFromInstance(instance, ec2conn)
|
||||||
m := v.(map[string]interface{})
|
if err != nil {
|
||||||
buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
|
return err
|
||||||
buf.WriteString(fmt.Sprintf("%s-", m["virtual_name"].(string)))
|
}
|
||||||
buf.WriteString(fmt.Sprintf("%t-", m["delete_on_termination"].(bool)))
|
|
||||||
return hashcode.String(buf.String())
|
if err := d.Set("ebs_block_device", ibds["ebs"]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ibds["root"] != nil {
|
||||||
|
if err := d.Set("root_block_device", []interface{}{ibds["root"]}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readBlockDevicesFromInstance(instance *ec2.Instance, ec2conn *ec2.EC2) (map[string]interface{}, error) {
|
||||||
|
blockDevices := make(map[string]interface{})
|
||||||
|
blockDevices["ebs"] = make([]map[string]interface{}, 0)
|
||||||
|
blockDevices["root"] = nil
|
||||||
|
|
||||||
|
instanceBlockDevices := make(map[string]ec2.InstanceBlockDeviceMapping)
|
||||||
|
for _, bd := range instance.BlockDeviceMappings {
|
||||||
|
if bd.EBS != nil {
|
||||||
|
instanceBlockDevices[*(bd.EBS.VolumeID)] = bd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
volIDs := make([]string, 0, len(instanceBlockDevices))
|
||||||
|
for volID := range instanceBlockDevices {
|
||||||
|
volIDs = append(volIDs, volID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to call DescribeVolumes to get volume_size and volume_type for each
|
||||||
|
// EBS block device
|
||||||
|
volResp, err := ec2conn.DescribeVolumes(&ec2.DescribeVolumesRequest{
|
||||||
|
VolumeIDs: volIDs,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, vol := range volResp.Volumes {
|
||||||
|
instanceBd := instanceBlockDevices[*vol.VolumeID]
|
||||||
|
bd := make(map[string]interface{})
|
||||||
|
|
||||||
|
if instanceBd.EBS != nil && instanceBd.EBS.DeleteOnTermination != nil {
|
||||||
|
bd["delete_on_termination"] = *instanceBd.EBS.DeleteOnTermination
|
||||||
|
}
|
||||||
|
if instanceBd.DeviceName != nil {
|
||||||
|
bd["device_name"] = *instanceBd.DeviceName
|
||||||
|
}
|
||||||
|
if vol.Size != nil {
|
||||||
|
bd["volume_size"] = *vol.Size
|
||||||
|
}
|
||||||
|
if vol.VolumeType != nil {
|
||||||
|
bd["volume_type"] = *vol.VolumeType
|
||||||
|
}
|
||||||
|
if vol.IOPS != nil {
|
||||||
|
bd["iops"] = *vol.IOPS
|
||||||
|
}
|
||||||
|
|
||||||
|
if blockDeviceIsRoot(instanceBd, instance) {
|
||||||
|
blockDevices["root"] = bd
|
||||||
|
} else {
|
||||||
|
if vol.Encrypted != nil {
|
||||||
|
bd["encrypted"] = *vol.Encrypted
|
||||||
|
}
|
||||||
|
if vol.SnapshotID != nil {
|
||||||
|
bd["snapshot_id"] = *vol.SnapshotID
|
||||||
|
}
|
||||||
|
|
||||||
|
blockDevices["ebs"] = append(blockDevices["ebs"].([]map[string]interface{}), bd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return blockDevices, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func blockDeviceIsRoot(bd ec2.InstanceBlockDeviceMapping, instance *ec2.Instance) bool {
|
||||||
|
return (bd.DeviceName != nil &&
|
||||||
|
instance.RootDeviceName != nil &&
|
||||||
|
*bd.DeviceName == *instance.RootDeviceName)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/hashcode"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceAwsInstanceMigrateState(
|
||||||
|
v int, is *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) {
|
||||||
|
switch v {
|
||||||
|
case 0:
|
||||||
|
log.Println("[INFO] Found AWS Instance State v0; migrating to v1")
|
||||||
|
return migrateStateV0toV1(is)
|
||||||
|
default:
|
||||||
|
return is, fmt.Errorf("Unexpected schema version: %d", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return is, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func migrateStateV0toV1(is *terraform.InstanceState) (*terraform.InstanceState, error) {
|
||||||
|
log.Printf("[DEBUG] Attributes before migration: %#v", is.Attributes)
|
||||||
|
// Delete old count
|
||||||
|
delete(is.Attributes, "block_device.#")
|
||||||
|
|
||||||
|
oldBds, err := readV0BlockDevices(is)
|
||||||
|
if err != nil {
|
||||||
|
return is, err
|
||||||
|
}
|
||||||
|
// seed count fields for new types
|
||||||
|
is.Attributes["ebs_block_device.#"] = "0"
|
||||||
|
is.Attributes["ephemeral_block_device.#"] = "0"
|
||||||
|
// depending on if state was v0.3.7 or an earlier version, it might have
|
||||||
|
// root_block_device defined already
|
||||||
|
if _, ok := is.Attributes["root_block_device.#"]; !ok {
|
||||||
|
is.Attributes["root_block_device.#"] = "0"
|
||||||
|
}
|
||||||
|
for _, oldBd := range oldBds {
|
||||||
|
if err := writeV1BlockDevice(is, oldBd); err != nil {
|
||||||
|
return is, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Printf("[DEBUG] Attributes after migration: %#v", is.Attributes)
|
||||||
|
return is, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readV0BlockDevices(is *terraform.InstanceState) (map[string]map[string]string, error) {
|
||||||
|
oldBds := make(map[string]map[string]string)
|
||||||
|
for k, v := range is.Attributes {
|
||||||
|
if !strings.HasPrefix(k, "block_device.") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
path := strings.Split(k, ".")
|
||||||
|
if len(path) != 3 {
|
||||||
|
return oldBds, fmt.Errorf("Found unexpected block_device field: %#v", k)
|
||||||
|
}
|
||||||
|
hashcode, attribute := path[1], path[2]
|
||||||
|
oldBd, ok := oldBds[hashcode]
|
||||||
|
if !ok {
|
||||||
|
oldBd = make(map[string]string)
|
||||||
|
oldBds[hashcode] = oldBd
|
||||||
|
}
|
||||||
|
oldBd[attribute] = v
|
||||||
|
delete(is.Attributes, k)
|
||||||
|
}
|
||||||
|
return oldBds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeV1BlockDevice(
|
||||||
|
is *terraform.InstanceState, oldBd map[string]string) error {
|
||||||
|
code := hashcode.String(oldBd["device_name"])
|
||||||
|
bdType := "ebs_block_device"
|
||||||
|
if vn, ok := oldBd["virtual_name"]; ok && strings.HasPrefix(vn, "ephemeral") {
|
||||||
|
bdType = "ephemeral_block_device"
|
||||||
|
} else if dn, ok := oldBd["device_name"]; ok && dn == "/dev/sda1" {
|
||||||
|
bdType = "root_block_device"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch bdType {
|
||||||
|
case "ebs_block_device":
|
||||||
|
delete(oldBd, "virtual_name")
|
||||||
|
case "root_block_device":
|
||||||
|
delete(oldBd, "virtual_name")
|
||||||
|
delete(oldBd, "encrypted")
|
||||||
|
delete(oldBd, "snapshot_id")
|
||||||
|
case "ephemeral_block_device":
|
||||||
|
delete(oldBd, "delete_on_termination")
|
||||||
|
delete(oldBd, "encrypted")
|
||||||
|
delete(oldBd, "iops")
|
||||||
|
delete(oldBd, "volume_size")
|
||||||
|
delete(oldBd, "volume_type")
|
||||||
|
}
|
||||||
|
for attr, val := range oldBd {
|
||||||
|
attrKey := fmt.Sprintf("%s.%d.%s", bdType, code, attr)
|
||||||
|
is.Attributes[attrKey] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
countAttr := fmt.Sprintf("%s.#", bdType)
|
||||||
|
count, _ := strconv.Atoi(is.Attributes[countAttr])
|
||||||
|
is.Attributes[countAttr] = strconv.Itoa(count + 1)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAWSInstanceMigrateState(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
StateVersion int
|
||||||
|
Attributes map[string]string
|
||||||
|
Expected map[string]string
|
||||||
|
Meta interface{}
|
||||||
|
}{
|
||||||
|
"v0.3.6 and earlier": {
|
||||||
|
StateVersion: 0,
|
||||||
|
Attributes: map[string]string{
|
||||||
|
// EBS
|
||||||
|
"block_device.#": "2",
|
||||||
|
"block_device.3851383343.delete_on_termination": "true",
|
||||||
|
"block_device.3851383343.device_name": "/dev/sdx",
|
||||||
|
"block_device.3851383343.encrypted": "false",
|
||||||
|
"block_device.3851383343.snapshot_id": "",
|
||||||
|
"block_device.3851383343.virtual_name": "",
|
||||||
|
"block_device.3851383343.volume_size": "5",
|
||||||
|
"block_device.3851383343.volume_type": "standard",
|
||||||
|
// Ephemeral
|
||||||
|
"block_device.3101711606.delete_on_termination": "false",
|
||||||
|
"block_device.3101711606.device_name": "/dev/sdy",
|
||||||
|
"block_device.3101711606.encrypted": "false",
|
||||||
|
"block_device.3101711606.snapshot_id": "",
|
||||||
|
"block_device.3101711606.virtual_name": "ephemeral0",
|
||||||
|
"block_device.3101711606.volume_size": "",
|
||||||
|
"block_device.3101711606.volume_type": "",
|
||||||
|
// Root
|
||||||
|
"block_device.56575650.delete_on_termination": "true",
|
||||||
|
"block_device.56575650.device_name": "/dev/sda1",
|
||||||
|
"block_device.56575650.encrypted": "false",
|
||||||
|
"block_device.56575650.snapshot_id": "",
|
||||||
|
"block_device.56575650.volume_size": "10",
|
||||||
|
"block_device.56575650.volume_type": "standard",
|
||||||
|
},
|
||||||
|
Expected: map[string]string{
|
||||||
|
"ebs_block_device.#": "1",
|
||||||
|
"ebs_block_device.3851383343.delete_on_termination": "true",
|
||||||
|
"ebs_block_device.3851383343.device_name": "/dev/sdx",
|
||||||
|
"ebs_block_device.3851383343.encrypted": "false",
|
||||||
|
"ebs_block_device.3851383343.snapshot_id": "",
|
||||||
|
"ebs_block_device.3851383343.volume_size": "5",
|
||||||
|
"ebs_block_device.3851383343.volume_type": "standard",
|
||||||
|
"ephemeral_block_device.#": "1",
|
||||||
|
"ephemeral_block_device.2458403513.device_name": "/dev/sdy",
|
||||||
|
"ephemeral_block_device.2458403513.virtual_name": "ephemeral0",
|
||||||
|
"root_block_device.#": "1",
|
||||||
|
"root_block_device.3018388612.delete_on_termination": "true",
|
||||||
|
"root_block_device.3018388612.device_name": "/dev/sda1",
|
||||||
|
"root_block_device.3018388612.snapshot_id": "",
|
||||||
|
"root_block_device.3018388612.volume_size": "10",
|
||||||
|
"root_block_device.3018388612.volume_type": "standard",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"v0.3.7": {
|
||||||
|
StateVersion: 0,
|
||||||
|
Attributes: map[string]string{
|
||||||
|
// EBS
|
||||||
|
"block_device.#": "2",
|
||||||
|
"block_device.3851383343.delete_on_termination": "true",
|
||||||
|
"block_device.3851383343.device_name": "/dev/sdx",
|
||||||
|
"block_device.3851383343.encrypted": "false",
|
||||||
|
"block_device.3851383343.snapshot_id": "",
|
||||||
|
"block_device.3851383343.virtual_name": "",
|
||||||
|
"block_device.3851383343.volume_size": "5",
|
||||||
|
"block_device.3851383343.volume_type": "standard",
|
||||||
|
"block_device.3851383343.iops": "",
|
||||||
|
// Ephemeral
|
||||||
|
"block_device.3101711606.delete_on_termination": "false",
|
||||||
|
"block_device.3101711606.device_name": "/dev/sdy",
|
||||||
|
"block_device.3101711606.encrypted": "false",
|
||||||
|
"block_device.3101711606.snapshot_id": "",
|
||||||
|
"block_device.3101711606.virtual_name": "ephemeral0",
|
||||||
|
"block_device.3101711606.volume_size": "",
|
||||||
|
"block_device.3101711606.volume_type": "",
|
||||||
|
"block_device.3101711606.iops": "",
|
||||||
|
// Root
|
||||||
|
"root_block_device.#": "1",
|
||||||
|
"root_block_device.3018388612.delete_on_termination": "true",
|
||||||
|
"root_block_device.3018388612.device_name": "/dev/sda1",
|
||||||
|
"root_block_device.3018388612.snapshot_id": "",
|
||||||
|
"root_block_device.3018388612.volume_size": "10",
|
||||||
|
"root_block_device.3018388612.volume_type": "io1",
|
||||||
|
"root_block_device.3018388612.iops": "1000",
|
||||||
|
},
|
||||||
|
Expected: map[string]string{
|
||||||
|
"ebs_block_device.#": "1",
|
||||||
|
"ebs_block_device.3851383343.delete_on_termination": "true",
|
||||||
|
"ebs_block_device.3851383343.device_name": "/dev/sdx",
|
||||||
|
"ebs_block_device.3851383343.encrypted": "false",
|
||||||
|
"ebs_block_device.3851383343.snapshot_id": "",
|
||||||
|
"ebs_block_device.3851383343.volume_size": "5",
|
||||||
|
"ebs_block_device.3851383343.volume_type": "standard",
|
||||||
|
"ephemeral_block_device.#": "1",
|
||||||
|
"ephemeral_block_device.2458403513.device_name": "/dev/sdy",
|
||||||
|
"ephemeral_block_device.2458403513.virtual_name": "ephemeral0",
|
||||||
|
"root_block_device.#": "1",
|
||||||
|
"root_block_device.3018388612.delete_on_termination": "true",
|
||||||
|
"root_block_device.3018388612.device_name": "/dev/sda1",
|
||||||
|
"root_block_device.3018388612.snapshot_id": "",
|
||||||
|
"root_block_device.3018388612.volume_size": "10",
|
||||||
|
"root_block_device.3018388612.volume_type": "io1",
|
||||||
|
"root_block_device.3018388612.iops": "1000",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for tn, tc := range cases {
|
||||||
|
is := &terraform.InstanceState{
|
||||||
|
Attributes: tc.Attributes,
|
||||||
|
}
|
||||||
|
is, err := resourceAwsInstanceMigrateState(
|
||||||
|
tc.StateVersion, is, tc.Meta)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bad: %s, err: %#v", tn, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range tc.Expected {
|
||||||
|
if is.Attributes[k] != v {
|
||||||
|
t.Fatalf(
|
||||||
|
"bad: %s\n\n expected: %#v -> %#v\n got: %#v -> %#v\n in: %#v",
|
||||||
|
tn, k, v, k, is.Attributes[k], is.Attributes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -111,31 +111,33 @@ func TestAccAWSInstance_blockDevices(t *testing.T) {
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_instance.foo", "root_block_device.#", "1"),
|
"aws_instance.foo", "root_block_device.#", "1"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_instance.foo", "root_block_device.0.device_name", "/dev/sda1"),
|
"aws_instance.foo", "root_block_device.3018388612.device_name", "/dev/sda1"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_instance.foo", "root_block_device.0.volume_size", "11"),
|
"aws_instance.foo", "root_block_device.3018388612.volume_size", "11"),
|
||||||
// this one is important because it's the only root_block_device
|
|
||||||
// attribute that comes back from the API. so checking it verifies
|
|
||||||
// that we set state properly
|
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_instance.foo", "root_block_device.0.volume_type", "gp2"),
|
"aws_instance.foo", "root_block_device.3018388612.volume_type", "gp2"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_instance.foo", "block_device.#", "2"),
|
"aws_instance.foo", "ebs_block_device.#", "2"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_instance.foo", "block_device.172787947.device_name", "/dev/sdb"),
|
"aws_instance.foo", "ebs_block_device.418220885.device_name", "/dev/sdb"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_instance.foo", "block_device.172787947.volume_size", "9"),
|
"aws_instance.foo", "ebs_block_device.418220885.volume_size", "9"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_instance.foo", "block_device.172787947.iops", "0"),
|
"aws_instance.foo", "ebs_block_device.418220885.volume_type", "standard"),
|
||||||
// Check provisioned SSD device
|
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_instance.foo", "block_device.3336996981.volume_type", "io1"),
|
"aws_instance.foo", "ebs_block_device.1877654467.device_name", "/dev/sdc"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_instance.foo", "block_device.3336996981.device_name", "/dev/sdc"),
|
"aws_instance.foo", "ebs_block_device.1877654467.volume_size", "10"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_instance.foo", "block_device.3336996981.volume_size", "10"),
|
"aws_instance.foo", "ebs_block_device.1877654467.volume_type", "io1"),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_instance.foo", "block_device.3336996981.iops", "100"),
|
"aws_instance.foo", "ebs_block_device.1877654467.iops", "100"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"aws_instance.foo", "ephemeral_block_device.#", "1"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"aws_instance.foo", "ephemeral_block_device.2087552357.device_name", "/dev/sde"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"aws_instance.foo", "ephemeral_block_device.2087552357.virtual_name", "ephemeral0"),
|
||||||
testCheck(),
|
testCheck(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -420,21 +422,26 @@ 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 {
|
root_block_device {
|
||||||
device_name = "/dev/sda1"
|
device_name = "/dev/sda1"
|
||||||
volume_type = "gp2"
|
volume_type = "gp2"
|
||||||
volume_size = 11
|
volume_size = 11
|
||||||
}
|
}
|
||||||
block_device {
|
ebs_block_device {
|
||||||
device_name = "/dev/sdb"
|
device_name = "/dev/sdb"
|
||||||
volume_size = 9
|
volume_size = 9
|
||||||
}
|
}
|
||||||
block_device {
|
ebs_block_device {
|
||||||
device_name = "/dev/sdc"
|
device_name = "/dev/sdc"
|
||||||
volume_size = 10
|
volume_size = 10
|
||||||
volume_type = "io1"
|
volume_type = "io1"
|
||||||
iops = 100
|
iops = 100
|
||||||
}
|
}
|
||||||
|
ephemeral_block_device {
|
||||||
|
device_name = "/dev/sde"
|
||||||
|
virtual_name = "ephemeral0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
|
@ -151,7 +151,7 @@ func (r *Resource) Apply(
|
||||||
err = r.Update(data, meta)
|
err = r.Update(data, meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
return data.State(), err
|
return r.recordCurrentSchemaVersion(data.State()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Diff returns a diff of this resource and is API compatible with the
|
// Diff returns a diff of this resource and is API compatible with the
|
||||||
|
@ -207,14 +207,7 @@ func (r *Resource) Refresh(
|
||||||
state = nil
|
state = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if state != nil && r.SchemaVersion > 0 {
|
return r.recordCurrentSchemaVersion(state), err
|
||||||
if state.Meta == nil {
|
|
||||||
state.Meta = make(map[string]string)
|
|
||||||
}
|
|
||||||
state.Meta["schema_version"] = strconv.Itoa(r.SchemaVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
return state, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// InternalValidate should be called to validate the structure
|
// InternalValidate should be called to validate the structure
|
||||||
|
@ -241,3 +234,14 @@ func (r *Resource) checkSchemaVersion(is *terraform.InstanceState) (bool, int) {
|
||||||
stateSchemaVersion, _ := strconv.Atoi(is.Meta["schema_version"])
|
stateSchemaVersion, _ := strconv.Atoi(is.Meta["schema_version"])
|
||||||
return stateSchemaVersion < r.SchemaVersion, stateSchemaVersion
|
return stateSchemaVersion < r.SchemaVersion, stateSchemaVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Resource) recordCurrentSchemaVersion(
|
||||||
|
state *terraform.InstanceState) *terraform.InstanceState {
|
||||||
|
if state != nil && r.SchemaVersion > 0 {
|
||||||
|
if state.Meta == nil {
|
||||||
|
state.Meta = make(map[string]string)
|
||||||
|
}
|
||||||
|
state.Meta["schema_version"] = strconv.Itoa(r.SchemaVersion)
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
|
|
||||||
func TestResourceApply_create(t *testing.T) {
|
func TestResourceApply_create(t *testing.T) {
|
||||||
r := &Resource{
|
r := &Resource{
|
||||||
|
SchemaVersion: 2,
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"foo": &Schema{
|
"foo": &Schema{
|
||||||
Type: TypeInt,
|
Type: TypeInt,
|
||||||
|
@ -51,6 +52,9 @@ func TestResourceApply_create(t *testing.T) {
|
||||||
"id": "foo",
|
"id": "foo",
|
||||||
"foo": "42",
|
"foo": "42",
|
||||||
},
|
},
|
||||||
|
Meta: map[string]string{
|
||||||
|
"schema_version": "2",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(actual, expected) {
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
@ -339,6 +343,7 @@ func TestResourceInternalValidate(t *testing.T) {
|
||||||
|
|
||||||
func TestResourceRefresh(t *testing.T) {
|
func TestResourceRefresh(t *testing.T) {
|
||||||
r := &Resource{
|
r := &Resource{
|
||||||
|
SchemaVersion: 2,
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"foo": &Schema{
|
"foo": &Schema{
|
||||||
Type: TypeInt,
|
Type: TypeInt,
|
||||||
|
@ -368,6 +373,9 @@ func TestResourceRefresh(t *testing.T) {
|
||||||
"id": "bar",
|
"id": "bar",
|
||||||
"foo": "13",
|
"foo": "13",
|
||||||
},
|
},
|
||||||
|
Meta: map[string]string{
|
||||||
|
"schema_version": "2",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
actual, err := r.Refresh(s, 42)
|
actual, err := r.Refresh(s, 42)
|
||||||
|
|
|
@ -843,6 +843,9 @@ func (i *InstanceState) init() {
|
||||||
if i.Attributes == nil {
|
if i.Attributes == nil {
|
||||||
i.Attributes = make(map[string]string)
|
i.Attributes = make(map[string]string)
|
||||||
}
|
}
|
||||||
|
if i.Meta == nil {
|
||||||
|
i.Meta = make(map[string]string)
|
||||||
|
}
|
||||||
i.Ephemeral.init()
|
i.Ephemeral.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -860,6 +863,12 @@ func (i *InstanceState) deepcopy() *InstanceState {
|
||||||
n.Attributes[k] = v
|
n.Attributes[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if i.Meta != nil {
|
||||||
|
n.Meta = make(map[string]string, len(i.Meta))
|
||||||
|
for k, v := range i.Meta {
|
||||||
|
n.Meta[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,8 @@ and deleted. Instances also support [provisioning](/docs/provisioners/index.html
|
||||||
## Example Usage
|
## Example Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
# Create a new instance of the ami-1234 on an m1.small node with an AWS Tag naming it "HelloWorld"
|
# Create a new instance of the ami-1234 on an m1.small node
|
||||||
|
# with an AWS Tag naming it "HelloWorld"
|
||||||
resource "aws_instance" "web" {
|
resource "aws_instance" "web" {
|
||||||
ami = "ami-1234"
|
ami = "ami-1234"
|
||||||
instance_type = "m1.small"
|
instance_type = "m1.small"
|
||||||
|
@ -47,32 +48,79 @@ The following arguments are supported:
|
||||||
* `iam_instance_profile` - (Optional) The IAM Instance Profile to
|
* `iam_instance_profile` - (Optional) The IAM Instance Profile to
|
||||||
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.
|
|
||||||
* `root_block_device` - (Optional) Customize details about the root block
|
* `root_block_device` - (Optional) Customize details about the root block
|
||||||
device of the instance. Available keys are documented below.
|
device of the instance. See [Block Devices](#block-devices) below for details.
|
||||||
|
* `ebs_block_device` - (Optional) Additional EBS block devices to attach to the
|
||||||
|
instance. See [Block Devices](#block-devices) below for details.
|
||||||
|
* `ephemeral_block_device` - (Optional) Customize Ephemeral (also known as
|
||||||
|
"Instance Store") volumes on the instance. See [Block Devices](#block-devices) below for details.
|
||||||
|
|
||||||
Each `block_device` supports the following:
|
|
||||||
|
|
||||||
* `device_name` - The name of the device to mount.
|
<a id="block-devices"></a>
|
||||||
* `virtual_name` - (Optional) The virtual device name.
|
## Block devices
|
||||||
* `snapshot_id` - (Optional) The Snapshot ID to mount.
|
|
||||||
* `volume_type` - (Optional) The type of volume. Can be standard, gp2, or io1. Defaults to standard.
|
Each of the `*_block_device` attributes controls a portion of the AWS
|
||||||
* `volume_size` - (Optional) The size of the volume in gigabytes.
|
Instance's "Block Device Mapping". It's a good idea to familiarize yourself with [AWS's Block Device
|
||||||
* `iops` - (Optional) The amount of provisioned IOPS. Setting this implies a
|
Mapping docs](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html)
|
||||||
volume_type of "io1".
|
to understand the implications of using these attributes.
|
||||||
* `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:
|
The `root_block_device` mapping supports the following:
|
||||||
|
|
||||||
* `device_name` - The name of the root device on the target instance. Must
|
* `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
|
match the root device as defined in the AMI. Defaults to `"/dev/sda1"`, which
|
||||||
is the typical root volume for Linux instances.
|
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_type` - (Optional) The type of volume. Can be `"standard"`, `"gp2"`,
|
||||||
|
or `"io1"`. (Default: `"standard"`).
|
||||||
* `volume_size` - (Optional) The size of the volume in gigabytes.
|
* `volume_size` - (Optional) The size of the volume in gigabytes.
|
||||||
* `iops` - (Optional) The amount of provisioned IOPS. Setting this implies a
|
* `iops` - (Optional) The amount of provisioned
|
||||||
volume_type of "io1".
|
[IOPS](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-io-characteristics.html).
|
||||||
* `delete_on_termination` - (Optional) Should the volume be destroyed on instance termination (defaults true).
|
This must be set with a `volume_type` of `"io1"`.
|
||||||
|
* `delete_on_termination` - (Optional) Whether the volume should be destroyed
|
||||||
|
on instance termination (Default: `true`).
|
||||||
|
|
||||||
|
Modifying any of the `root_block_device` settings requires resource
|
||||||
|
replacement.
|
||||||
|
|
||||||
|
Each `ebs_block_device` supports the following:
|
||||||
|
|
||||||
|
* `device_name` - The name of the device to mount.
|
||||||
|
* `snapshot_id` - (Optional) The Snapshot ID to mount.
|
||||||
|
* `volume_type` - (Optional) The type of volume. Can be `"standard"`, `"gp2"`,
|
||||||
|
or `"io1"`. (Default: `"standard"`).
|
||||||
|
* `volume_size` - (Optional) The size of the volume in gigabytes.
|
||||||
|
* `iops` - (Optional) The amount of provisioned
|
||||||
|
[IOPS](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-io-characteristics.html).
|
||||||
|
This must be set with a `volume_type` of `"io1"`.
|
||||||
|
* `delete_on_termination` - (Optional) Whether the volume should be destroyed
|
||||||
|
on instance termination (Default: `true`).
|
||||||
|
* `encrypted` - (Optional) Enables [EBS
|
||||||
|
encryption](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html)
|
||||||
|
on the volume (Default: `false`).
|
||||||
|
|
||||||
|
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
|
||||||
|
Name](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/InstanceStorage.html#InstanceStoreDeviceNames)
|
||||||
|
(e.g. `"ephemeral0"`)
|
||||||
|
|
||||||
|
Each AWS Instance type has a different set of Instance Store block devices
|
||||||
|
available for attachment. AWS [publishes a
|
||||||
|
list](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/InstanceStorage.html#StorageOnInstanceTypes)
|
||||||
|
of which ephemeral devices are available on each type. The devices are always
|
||||||
|
identified by the `virtual_name` in the format `"ephemeral{0..N}"`.
|
||||||
|
|
||||||
|
|
||||||
|
~> **NOTE:** Because AWS [does not expose Instance Store mapping
|
||||||
|
details](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html#bdm-instance-metadata)
|
||||||
|
via an externally accessible API, `ephemeral_block_device` configuration may
|
||||||
|
only be applied at instance creation time, and changes to configuration of
|
||||||
|
existing resources cannot be detected by Terraform. Updates to Instance Store
|
||||||
|
block device configuration can be manually triggered by using the [`taint`
|
||||||
|
command](/docs/commands/taint.html).
|
||||||
|
|
||||||
|
|
||||||
## Attributes Reference
|
## Attributes Reference
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue