mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-20 11:48:24 -06:00
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"
|
"crypto/sha1"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
@ -32,67 +33,67 @@ func resourceAwsInstance() *schema.Resource {
|
|||||||
MigrateState: resourceAwsInstanceMigrateState,
|
MigrateState: resourceAwsInstanceMigrateState,
|
||||||
|
|
||||||
Schema: map[string]*schema.Schema{
|
Schema: map[string]*schema.Schema{
|
||||||
"ami": &schema.Schema{
|
"ami": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Required: true,
|
Required: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"associate_public_ip_address": &schema.Schema{
|
"associate_public_ip_address": {
|
||||||
Type: schema.TypeBool,
|
Type: schema.TypeBool,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"availability_zone": &schema.Schema{
|
"availability_zone": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"placement_group": &schema.Schema{
|
"placement_group": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"instance_type": &schema.Schema{
|
"instance_type": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Required: true,
|
Required: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"key_name": &schema.Schema{
|
"key_name": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"subnet_id": &schema.Schema{
|
"subnet_id": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"private_ip": &schema.Schema{
|
"private_ip": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"source_dest_check": &schema.Schema{
|
"source_dest_check": {
|
||||||
Type: schema.TypeBool,
|
Type: schema.TypeBool,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Default: true,
|
Default: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"user_data": &schema.Schema{
|
"user_data": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
@ -106,7 +107,7 @@ func resourceAwsInstance() *schema.Resource {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
"security_groups": &schema.Schema{
|
"security_groups": {
|
||||||
Type: schema.TypeSet,
|
Type: schema.TypeSet,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
@ -115,7 +116,7 @@ func resourceAwsInstance() *schema.Resource {
|
|||||||
Set: schema.HashString,
|
Set: schema.HashString,
|
||||||
},
|
},
|
||||||
|
|
||||||
"vpc_security_group_ids": &schema.Schema{
|
"vpc_security_group_ids": {
|
||||||
Type: schema.TypeSet,
|
Type: schema.TypeSet,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
@ -123,59 +124,59 @@ func resourceAwsInstance() *schema.Resource {
|
|||||||
Set: schema.HashString,
|
Set: schema.HashString,
|
||||||
},
|
},
|
||||||
|
|
||||||
"public_dns": &schema.Schema{
|
"public_dns": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"network_interface_id": &schema.Schema{
|
"network_interface_id": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"public_ip": &schema.Schema{
|
"public_ip": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"instance_state": &schema.Schema{
|
"instance_state": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"private_dns": &schema.Schema{
|
"private_dns": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"ebs_optimized": &schema.Schema{
|
"ebs_optimized": {
|
||||||
Type: schema.TypeBool,
|
Type: schema.TypeBool,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"disable_api_termination": &schema.Schema{
|
"disable_api_termination": {
|
||||||
Type: schema.TypeBool,
|
Type: schema.TypeBool,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"instance_initiated_shutdown_behavior": &schema.Schema{
|
"instance_initiated_shutdown_behavior": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"monitoring": &schema.Schema{
|
"monitoring": {
|
||||||
Type: schema.TypeBool,
|
Type: schema.TypeBool,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"iam_instance_profile": &schema.Schema{
|
"iam_instance_profile": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"tenancy": &schema.Schema{
|
"tenancy": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
@ -184,60 +185,60 @@ func resourceAwsInstance() *schema.Resource {
|
|||||||
|
|
||||||
"tags": tagsSchema(),
|
"tags": tagsSchema(),
|
||||||
|
|
||||||
"block_device": &schema.Schema{
|
"block_device": {
|
||||||
Type: schema.TypeMap,
|
Type: schema.TypeMap,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Removed: "Split out into three sub-types; see Changelog and Docs",
|
Removed: "Split out into three sub-types; see Changelog and Docs",
|
||||||
},
|
},
|
||||||
|
|
||||||
"ebs_block_device": &schema.Schema{
|
"ebs_block_device": {
|
||||||
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{
|
||||||
"delete_on_termination": &schema.Schema{
|
"delete_on_termination": {
|
||||||
Type: schema.TypeBool,
|
Type: schema.TypeBool,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Default: true,
|
Default: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"device_name": &schema.Schema{
|
"device_name": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Required: true,
|
Required: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"encrypted": &schema.Schema{
|
"encrypted": {
|
||||||
Type: schema.TypeBool,
|
Type: schema.TypeBool,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"iops": &schema.Schema{
|
"iops": {
|
||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"snapshot_id": &schema.Schema{
|
"snapshot_id": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"volume_size": &schema.Schema{
|
"volume_size": {
|
||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"volume_type": &schema.Schema{
|
"volume_type": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
@ -254,24 +255,24 @@ func resourceAwsInstance() *schema.Resource {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
"ephemeral_block_device": &schema.Schema{
|
"ephemeral_block_device": {
|
||||||
Type: schema.TypeSet,
|
Type: schema.TypeSet,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
Elem: &schema.Resource{
|
Elem: &schema.Resource{
|
||||||
Schema: map[string]*schema.Schema{
|
Schema: map[string]*schema.Schema{
|
||||||
"device_name": &schema.Schema{
|
"device_name": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Required: true,
|
Required: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"virtual_name": &schema.Schema{
|
"virtual_name": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"no_device": &schema.Schema{
|
"no_device": {
|
||||||
Type: schema.TypeBool,
|
Type: schema.TypeBool,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
@ -289,41 +290,38 @@ func resourceAwsInstance() *schema.Resource {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
"root_block_device": &schema.Schema{
|
"root_block_device": {
|
||||||
// TODO: This is a set because we don't support singleton
|
Type: schema.TypeList,
|
||||||
// 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,
|
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
|
MaxItems: 1,
|
||||||
Elem: &schema.Resource{
|
Elem: &schema.Resource{
|
||||||
// "You can only modify the volume size, volume type, and Delete on
|
// "You can only modify the volume size, volume type, and Delete on
|
||||||
// Termination flag on the block device mapping entry for the root
|
// Termination flag on the block device mapping entry for the root
|
||||||
// device volume." - bit.ly/ec2bdmap
|
// device volume." - bit.ly/ec2bdmap
|
||||||
Schema: map[string]*schema.Schema{
|
Schema: map[string]*schema.Schema{
|
||||||
"delete_on_termination": &schema.Schema{
|
"delete_on_termination": {
|
||||||
Type: schema.TypeBool,
|
Type: schema.TypeBool,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Default: true,
|
Default: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"iops": &schema.Schema{
|
"iops": {
|
||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"volume_size": &schema.Schema{
|
"volume_size": {
|
||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"volume_type": &schema.Schema{
|
"volume_type": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: 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:
|
// 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
|
// 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") {
|
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)
|
return resource.RetryableError(err)
|
||||||
}
|
}
|
||||||
// IAM roles can also take time to propagate in AWS:
|
// IAM roles can also take time to propagate in AWS:
|
||||||
if isAWSErr(err, "InvalidParameterValue", " has no associated IAM Roles") {
|
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.RetryableError(err)
|
||||||
}
|
}
|
||||||
return resource.NonRetryableError(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)
|
return fmt.Errorf("Error launching source instance: %s", err)
|
||||||
}
|
}
|
||||||
if runResp == nil || len(runResp.Instances) == 0 {
|
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]
|
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) {
|
func fetchRootDeviceName(ami string, conn *ec2.EC2) (*string, error) {
|
||||||
if ami == "" {
|
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)
|
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) == "" {
|
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)
|
blockDevices = append(blockDevices, bdm)
|
||||||
@ -919,9 +913,9 @@ func readBlockDeviceMappingsFromConfig(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if v, ok := d.GetOk("root_block_device"); ok {
|
if v, ok := d.GetOk("root_block_device"); ok {
|
||||||
vL := v.(*schema.Set).List()
|
vL := v.([]interface{})
|
||||||
if len(vL) > 1 {
|
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 {
|
for _, v := range vL {
|
||||||
bd := v.(map[string]interface{})
|
bd := v.(map[string]interface{})
|
||||||
@ -946,7 +940,7 @@ func readBlockDeviceMappingsFromConfig(
|
|||||||
ebs.Iops = aws.Int64(int64(v))
|
ebs.Iops = aws.Int64(int64(v))
|
||||||
} else if v, ok := bd["iops"].(int); ok && v > 0 && *ebs.VolumeType != "io1" {
|
} else if v, ok := bd["iops"].(int); ok && v > 0 && *ebs.VolumeType != "io1" {
|
||||||
// Message user about incompatibility
|
// 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 {
|
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
|
// See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RunInstances.html
|
||||||
sgs := v.(*schema.Set).List()
|
sgs := v.(*schema.Set).List()
|
||||||
if len(sgs) > 0 && hasSubnet {
|
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 {
|
for _, v := range sgs {
|
||||||
str := v.(string)
|
str := v.(string)
|
||||||
|
Loading…
Reference in New Issue
Block a user