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:
parent
2c59c9d44e
commit
3d22adbd5d
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue