provider/aws: Fix root-block-device bug

Previously the `root_block_device` config map was a `schema.TypeSet` with an empty `Set` function, and a hard-limit of 1 on the attribute block.
This prevented a user from making any real changes inside the attribute block, thus leaving the user with a `Apply complete!` message, and nothing changed.

The schema API has since been updated, and we can now specify the `root_block_device` as a `schema.TypeList` with `MaxItems` set to `1`. This fixes the issue, and allows the user to update the `aws_instance`'s `root_block_device` attribute, and see changes actually propagate.
This commit is contained in:
Jake Champlin 2017-02-01 16:25:07 -05:00
parent 2c59c9d44e
commit 3d22adbd5d
No known key found for this signature in database
GPG Key ID: DC31F41958EF4AC2
1 changed files with 53 additions and 59 deletions

View File

@ -5,6 +5,7 @@ import (
"crypto/sha1"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"log"
"strings"
@ -32,67 +33,67 @@ func resourceAwsInstance() *schema.Resource {
MigrateState: resourceAwsInstanceMigrateState,
Schema: map[string]*schema.Schema{
"ami": &schema.Schema{
"ami": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"associate_public_ip_address": &schema.Schema{
"associate_public_ip_address": {
Type: schema.TypeBool,
ForceNew: true,
Computed: true,
Optional: true,
},
"availability_zone": &schema.Schema{
"availability_zone": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"placement_group": &schema.Schema{
"placement_group": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"instance_type": &schema.Schema{
"instance_type": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"key_name": &schema.Schema{
"key_name": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
},
"subnet_id": &schema.Schema{
"subnet_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"private_ip": &schema.Schema{
"private_ip": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
},
"source_dest_check": &schema.Schema{
"source_dest_check": {
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"user_data": &schema.Schema{
"user_data": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
@ -106,7 +107,7 @@ func resourceAwsInstance() *schema.Resource {
},
},
"security_groups": &schema.Schema{
"security_groups": {
Type: schema.TypeSet,
Optional: true,
Computed: true,
@ -115,7 +116,7 @@ func resourceAwsInstance() *schema.Resource {
Set: schema.HashString,
},
"vpc_security_group_ids": &schema.Schema{
"vpc_security_group_ids": {
Type: schema.TypeSet,
Optional: true,
Computed: true,
@ -123,59 +124,59 @@ func resourceAwsInstance() *schema.Resource {
Set: schema.HashString,
},
"public_dns": &schema.Schema{
"public_dns": {
Type: schema.TypeString,
Computed: true,
},
"network_interface_id": &schema.Schema{
"network_interface_id": {
Type: schema.TypeString,
Computed: true,
},
"public_ip": &schema.Schema{
"public_ip": {
Type: schema.TypeString,
Computed: true,
},
"instance_state": &schema.Schema{
"instance_state": {
Type: schema.TypeString,
Computed: true,
},
"private_dns": &schema.Schema{
"private_dns": {
Type: schema.TypeString,
Computed: true,
},
"ebs_optimized": &schema.Schema{
"ebs_optimized": {
Type: schema.TypeBool,
Optional: true,
ForceNew: true,
},
"disable_api_termination": &schema.Schema{
"disable_api_termination": {
Type: schema.TypeBool,
Optional: true,
},
"instance_initiated_shutdown_behavior": &schema.Schema{
"instance_initiated_shutdown_behavior": {
Type: schema.TypeString,
Optional: true,
},
"monitoring": &schema.Schema{
"monitoring": {
Type: schema.TypeBool,
Optional: true,
},
"iam_instance_profile": &schema.Schema{
"iam_instance_profile": {
Type: schema.TypeString,
ForceNew: true,
Optional: true,
},
"tenancy": &schema.Schema{
"tenancy": {
Type: schema.TypeString,
Optional: true,
Computed: true,
@ -184,60 +185,60 @@ func resourceAwsInstance() *schema.Resource {
"tags": tagsSchema(),
"block_device": &schema.Schema{
"block_device": {
Type: schema.TypeMap,
Optional: true,
Removed: "Split out into three sub-types; see Changelog and Docs",
},
"ebs_block_device": &schema.Schema{
"ebs_block_device": {
Type: schema.TypeSet,
Optional: true,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"delete_on_termination": &schema.Schema{
"delete_on_termination": {
Type: schema.TypeBool,
Optional: true,
Default: true,
ForceNew: true,
},
"device_name": &schema.Schema{
"device_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"encrypted": &schema.Schema{
"encrypted": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
ForceNew: true,
},
"iops": &schema.Schema{
"iops": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
ForceNew: true,
},
"snapshot_id": &schema.Schema{
"snapshot_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"volume_size": &schema.Schema{
"volume_size": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
ForceNew: true,
},
"volume_type": &schema.Schema{
"volume_type": {
Type: schema.TypeString,
Optional: true,
Computed: true,
@ -254,24 +255,24 @@ func resourceAwsInstance() *schema.Resource {
},
},
"ephemeral_block_device": &schema.Schema{
"ephemeral_block_device": {
Type: schema.TypeSet,
Optional: true,
Computed: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"device_name": &schema.Schema{
"device_name": {
Type: schema.TypeString,
Required: true,
},
"virtual_name": &schema.Schema{
"virtual_name": {
Type: schema.TypeString,
Optional: true,
},
"no_device": &schema.Schema{
"no_device": {
Type: schema.TypeBool,
Optional: true,
},
@ -289,41 +290,38 @@ func resourceAwsInstance() *schema.Resource {
},
},
"root_block_device": &schema.Schema{
// TODO: This is a set because we don't support singleton
// sub-resources today. We'll enforce that the set only ever has
// length zero or one below. When TF gains support for
// sub-resources this can be converted.
Type: schema.TypeSet,
"root_block_device": {
Type: schema.TypeList,
Optional: true,
Computed: true,
MaxItems: 1,
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{
"delete_on_termination": {
Type: schema.TypeBool,
Optional: true,
Default: true,
ForceNew: true,
},
"iops": &schema.Schema{
"iops": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
ForceNew: true,
},
"volume_size": &schema.Schema{
"volume_size": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
ForceNew: true,
},
"volume_type": &schema.Schema{
"volume_type": {
Type: schema.TypeString,
Optional: true,
Computed: true,
@ -331,10 +329,6 @@ func resourceAwsInstance() *schema.Resource {
},
},
},
Set: func(v interface{}) int {
// there can be only one root device; no need to hash anything
return 0
},
},
},
}
@ -380,12 +374,12 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
// IAM instance profiles can take ~10 seconds to propagate in AWS:
// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#launch-instance-with-role-console
if isAWSErr(err, "InvalidParameterValue", "Invalid IAM Instance Profile") {
log.Printf("[DEBUG] Invalid IAM Instance Profile referenced, retrying...")
log.Print("[DEBUG] Invalid IAM Instance Profile referenced, retrying...")
return resource.RetryableError(err)
}
// IAM roles can also take time to propagate in AWS:
if isAWSErr(err, "InvalidParameterValue", " has no associated IAM Roles") {
log.Printf("[DEBUG] IAM Instance Profile appears to have no IAM roles, retrying...")
log.Print("[DEBUG] IAM Instance Profile appears to have no IAM roles, retrying...")
return resource.RetryableError(err)
}
return resource.NonRetryableError(err)
@ -400,7 +394,7 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
return fmt.Errorf("Error launching source instance: %s", err)
}
if runResp == nil || len(runResp.Instances) == 0 {
return fmt.Errorf("Error launching source instance: no instances returned in response")
return errors.New("Error launching source instance: no instances returned in response")
}
instance := runResp.Instances[0]
@ -804,7 +798,7 @@ func blockDeviceIsRoot(bd *ec2.InstanceBlockDeviceMapping, instance *ec2.Instanc
func fetchRootDeviceName(ami string, conn *ec2.EC2) (*string, error) {
if ami == "" {
return nil, fmt.Errorf("Cannot fetch root device name for blank AMI ID.")
return nil, errors.New("Cannot fetch root device name for blank AMI ID.")
}
log.Printf("[DEBUG] Describing AMI %q to get root block device name", ami)
@ -911,7 +905,7 @@ func readBlockDeviceMappingsFromConfig(
}
if bdm.NoDevice == nil && aws.StringValue(bdm.VirtualName) == "" {
return nil, fmt.Errorf("virtual_name cannot be empty when no_device is false or undefined.")
return nil, errors.New("virtual_name cannot be empty when no_device is false or undefined.")
}
blockDevices = append(blockDevices, bdm)
@ -919,9 +913,9 @@ func readBlockDeviceMappingsFromConfig(
}
if v, ok := d.GetOk("root_block_device"); ok {
vL := v.(*schema.Set).List()
vL := v.([]interface{})
if len(vL) > 1 {
return nil, fmt.Errorf("Cannot specify more than one root_block_device.")
return nil, errors.New("Cannot specify more than one root_block_device.")
}
for _, v := range vL {
bd := v.(map[string]interface{})
@ -946,7 +940,7 @@ func readBlockDeviceMappingsFromConfig(
ebs.Iops = aws.Int64(int64(v))
} else if v, ok := bd["iops"].(int); ok && v > 0 && *ebs.VolumeType != "io1" {
// Message user about incompatibility
log.Printf("[WARN] IOPs is only valid for storate type io1 for EBS Volumes")
log.Print("[WARN] IOPs is only valid for storate type io1 for EBS Volumes")
}
if dn, err := fetchRootDeviceName(d.Get("ami").(string), conn); err == nil {
@ -1096,7 +1090,7 @@ func buildAwsInstanceOpts(
// See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RunInstances.html
sgs := v.(*schema.Set).List()
if len(sgs) > 0 && hasSubnet {
log.Printf("[WARN] Deprecated. Attempting to use 'security_groups' within a VPC instance. Use 'vpc_security_group_ids' instead.")
log.Print("[WARN] Deprecated. Attempting to use 'security_groups' within a VPC instance. Use 'vpc_security_group_ids' instead.")
}
for _, v := range sgs {
str := v.(string)