Skip to content

Commit 164f0e2

Browse files
authored
Merge pull request #35745 from hashicorp/b-aws_instance.ReservationCapacityExceeded
r/aws_instance: Fix `ReservationCapacityExceeded` errors
2 parents e9c3c95 + 1182368 commit 164f0e2

7 files changed

+132
-111
lines changed

.changelog/33412.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:bug
2+
resource/aws_instance: Fix `ReservationCapacityExceeded` errors when updating `instance_type` and `capacity_reservation_specification.capacity_reservation_target.capacity_reservation_id`
3+
```

internal/service/autoscaling/launch_configuration_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -927,7 +927,7 @@ resource "aws_launch_configuration" "test" {
927927

928928
// testAccLatestAmazonLinuxPVInstanceStoreAMIConfig returns the configuration for a data source that
929929
// describes the latest Amazon Linux AMI using PV virtualization and an instance store root device.
930-
// The data source is named 'amzn-ami-minimal-pv-ebs'.
930+
// The data source is named 'amzn-ami-minimal-pv-instance-store'.
931931
func testAccLatestAmazonLinuxPVInstanceStoreAMIConfig() string {
932932
return `
933933
data "aws_ami" "amzn-ami-minimal-pv-instance-store" {

internal/service/ec2/ec2_ami_data_source_test.go

+10-10
Original file line numberDiff line numberDiff line change
@@ -116,15 +116,15 @@ func TestAccEC2AMIDataSource_windowsInstance(t *testing.T) {
116116

117117
func TestAccEC2AMIDataSource_instanceStore(t *testing.T) {
118118
ctx := acctest.Context(t)
119-
datasourceName := "data.aws_ami.amzn-ami-minimal-hvm-instance-store"
119+
datasourceName := "data.aws_ami.ubuntu-bionic-ami-hvm-instance-store"
120120

121121
resource.ParallelTest(t, resource.TestCase{
122122
PreCheck: func() { acctest.PreCheck(ctx, t) },
123123
ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID),
124124
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
125125
Steps: []resource.TestStep{
126126
{
127-
Config: testAccAMIDataSourceConfig_latestAmazonLinuxHVMInstanceStore(),
127+
Config: testAccAMIDataSourceConfig_latestUbuntuBionicHVMInstanceStore(),
128128
Check: resource.ComposeAggregateTestCheckFunc(
129129
resource.TestCheckResourceAttr(datasourceName, "architecture", "x86_64"),
130130
resource.TestCheckResourceAttr(datasourceName, "block_device_mappings.#", "0"),
@@ -133,10 +133,10 @@ func TestAccEC2AMIDataSource_instanceStore(t *testing.T) {
133133
resource.TestCheckResourceAttr(datasourceName, "ena_support", "true"),
134134
resource.TestCheckResourceAttr(datasourceName, "hypervisor", "xen"),
135135
resource.TestMatchResourceAttr(datasourceName, "image_id", regexache.MustCompile("^ami-")),
136-
resource.TestMatchResourceAttr(datasourceName, "image_location", regexache.MustCompile("amzn-ami-minimal-hvm")),
136+
resource.TestMatchResourceAttr(datasourceName, "image_location", regexache.MustCompile(`ubuntu-images-.*-release/.*/.*/hvm/instance-store`)),
137137
resource.TestCheckResourceAttr(datasourceName, "image_type", "machine"),
138138
resource.TestCheckResourceAttr(datasourceName, "most_recent", "true"),
139-
resource.TestMatchResourceAttr(datasourceName, "name", regexache.MustCompile("amzn-ami-minimal-hvm")),
139+
resource.TestMatchResourceAttr(datasourceName, "name", regexache.MustCompile(`ubuntu/images/hvm-instance/.*`)),
140140
acctest.MatchResourceAttrAccountID(datasourceName, "owner_id"),
141141
resource.TestCheckResourceAttr(datasourceName, "platform_details", "Linux/UNIX"),
142142
resource.TestCheckResourceAttr(datasourceName, "product_codes.#", "0"),
@@ -208,17 +208,17 @@ func TestAccEC2AMIDataSource_gp3BlockDevice(t *testing.T) {
208208
})
209209
}
210210

211-
// testAccAMIDataSourceConfig_latestAmazonLinuxHVMInstanceStore returns the configuration for a data source that
212-
// describes the latest Amazon Linux AMI using HVM virtualization and an instance store root device.
213-
// The data source is named 'amzn-ami-minimal-hvm-instance-store'.
214-
func testAccAMIDataSourceConfig_latestAmazonLinuxHVMInstanceStore() string {
211+
// testAccAMIDataSourceConfig_latestUbuntuBionicHVMInstanceStore returns the configuration for a data source that
212+
// describes the latest Ubuntu 18.04 AMI using HVM virtualization and an instance store root device.
213+
// The data source is named 'ubuntu-bionic-ami-hvm-instance-store'.
214+
func testAccAMIDataSourceConfig_latestUbuntuBionicHVMInstanceStore() string {
215215
return `
216-
data "aws_ami" "amzn-ami-minimal-hvm-instance-store" {
216+
data "aws_ami" "ubuntu-bionic-ami-hvm-instance-store" {
217217
most_recent = true
218218
219219
filter {
220220
name = "name"
221-
values = ["amzn-ami-minimal-hvm-*"]
221+
values = ["ubuntu/images/hvm-instance/ubuntu-bionic-18.04-amd64-server-*"]
222222
}
223223
224224
filter {

internal/service/ec2/ec2_instance.go

+85-53
Original file line numberDiff line numberDiff line change
@@ -1560,15 +1560,17 @@ func resourceInstanceUpdate(ctx context.Context, d *schema.ResourceData, meta in
15601560
// HasChange() thinks there is a diff between what is set on the instance and what is set in state. We need to ensure that
15611561
// if a diff has occurred, it's not because it's a new instance.
15621562
if d.HasChange("source_dest_check") && !d.IsNewResource() || d.IsNewResource() && !sourceDestCheck {
1563-
log.Printf("[INFO] Modifying `source_dest_check` on Instance %s", d.Id())
1564-
_, err := conn.ModifyInstanceAttributeWithContext(ctx, &ec2.ModifyInstanceAttributeInput{
1563+
input := &ec2.ModifyInstanceAttributeInput{
15651564
InstanceId: aws.String(d.Id()),
15661565
SourceDestCheck: &ec2.AttributeBooleanValue{
15671566
Value: aws.Bool(sourceDestCheck),
15681567
},
1569-
})
1568+
}
1569+
1570+
_, err := conn.ModifyInstanceAttributeWithContext(ctx, input)
1571+
15701572
if err != nil {
1571-
return create.AppendDiagError(diags, names.EC2, create.ErrActionUpdating, "Instance", d.Id(), err)
1573+
return sdkdiag.AppendErrorf(diags, "modifying EC2 Instance (%s) SourceDestCheck attribute: %s", d.Id(), err)
15721574
}
15731575
}
15741576
}
@@ -1668,17 +1670,18 @@ func resourceInstanceUpdate(ctx context.Context, d *schema.ResourceData, meta in
16681670
// Only one attribute can be modified at a time, else we get
16691671
// "InvalidParameterCombination: Fields for multiple attribute types specified"
16701672
if d.HasChange("instance_type") {
1671-
log.Printf("[INFO] Modifying instance type %s", d.Id())
1672-
1673-
input := &ec2.ModifyInstanceAttributeInput{
1674-
InstanceId: aws.String(d.Id()),
1675-
InstanceType: &ec2.AttributeValue{
1676-
Value: aws.String(d.Get("instance_type").(string)),
1677-
},
1678-
}
1673+
if !d.HasChange("capacity_reservation_specification.0.capacity_reservation_target.0.capacity_reservation_id") {
1674+
instanceType := d.Get("instance_type").(string)
1675+
input := &ec2.ModifyInstanceAttributeInput{
1676+
InstanceId: aws.String(d.Id()),
1677+
InstanceType: &ec2.AttributeValue{
1678+
Value: aws.String(instanceType),
1679+
},
1680+
}
16791681

1680-
if err := modifyInstanceAttributeWithStopStart(ctx, conn, input); err != nil {
1681-
return sdkdiag.AppendErrorf(diags, "updating EC2 Instance (%s) type: %s", d.Id(), err)
1682+
if err := modifyInstanceAttributeWithStopStart(ctx, conn, input, fmt.Sprintf("InstanceType (%s)", instanceType)); err != nil {
1683+
return sdkdiag.AppendErrorf(diags, "updating EC2 Instance (%s) type: %s", d.Id(), err)
1684+
}
16821685
}
16831686
}
16841687

@@ -1704,7 +1707,7 @@ func resourceInstanceUpdate(ctx context.Context, d *schema.ResourceData, meta in
17041707
},
17051708
}
17061709

1707-
if err := modifyInstanceAttributeWithStopStart(ctx, conn, input); err != nil {
1710+
if err := modifyInstanceAttributeWithStopStart(ctx, conn, input, "UserData"); err != nil {
17081711
return sdkdiag.AppendErrorf(diags, "updating EC2 Instance (%s) user data: %s", d.Id(), err)
17091712
}
17101713
}
@@ -1727,7 +1730,7 @@ func resourceInstanceUpdate(ctx context.Context, d *schema.ResourceData, meta in
17271730
},
17281731
}
17291732

1730-
if err := modifyInstanceAttributeWithStopStart(ctx, conn, input); err != nil {
1733+
if err := modifyInstanceAttributeWithStopStart(ctx, conn, input, "UserData (base64)"); err != nil {
17311734
return sdkdiag.AppendErrorf(diags, "updating EC2 Instance (%s) user data base64: %s", d.Id(), err)
17321735
}
17331736
}
@@ -1746,15 +1749,17 @@ func resourceInstanceUpdate(ctx context.Context, d *schema.ResourceData, meta in
17461749
}
17471750

17481751
if d.HasChange("instance_initiated_shutdown_behavior") {
1749-
log.Printf("[INFO] Modifying instance %s", d.Id())
1750-
_, err := conn.ModifyInstanceAttributeWithContext(ctx, &ec2.ModifyInstanceAttributeInput{
1752+
input := &ec2.ModifyInstanceAttributeInput{
17511753
InstanceId: aws.String(d.Id()),
17521754
InstanceInitiatedShutdownBehavior: &ec2.AttributeValue{
17531755
Value: aws.String(d.Get("instance_initiated_shutdown_behavior").(string)),
17541756
},
1755-
})
1757+
}
1758+
1759+
_, err := conn.ModifyInstanceAttributeWithContext(ctx, input)
1760+
17561761
if err != nil {
1757-
return sdkdiag.AppendErrorf(diags, "updating EC2 Instance (%s): modifying InstanceInitiatedShutdownBehavior: %s", d.Id(), err)
1762+
return sdkdiag.AppendErrorf(diags, "modifying EC2 Instance (%s) InstanceInitiatedShutdownBehavior attribute: %s", d.Id(), err)
17581763
}
17591764
}
17601765

@@ -1919,11 +1924,10 @@ func resourceInstanceUpdate(ctx context.Context, d *schema.ResourceData, meta in
19191924
InstanceId: aws.String(d.Id()),
19201925
}
19211926

1922-
log.Printf("[DEBUG] Modifying EC2 Instance attribute: %s", input)
19231927
_, err := conn.ModifyInstanceAttributeWithContext(ctx, input)
19241928

19251929
if err != nil {
1926-
return sdkdiag.AppendErrorf(diags, "updating EC2 Instance (%s) root block device (%s) DeleteOnTermination attribute: %s", d.Id(), deviceName, err)
1930+
return sdkdiag.AppendErrorf(diags, "modifying EC2 Instance (%s) BlockDeviceMappings (%s) attribute: %s", d.Id(), deviceName, err)
19271931
}
19281932

19291933
if _, err := WaitInstanceRootBlockDeviceDeleteOnTerminationUpdated(ctx, conn, d.Id(), v, d.Timeout(schema.TimeoutUpdate)); err != nil {
@@ -1946,12 +1950,31 @@ func resourceInstanceUpdate(ctx context.Context, d *schema.ResourceData, meta in
19461950
if d.HasChange("capacity_reservation_specification") && !d.IsNewResource() {
19471951
if v, ok := d.GetOk("capacity_reservation_specification"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil {
19481952
if v := expandCapacityReservationSpecification(v.([]interface{})[0].(map[string]interface{})); v != nil && (v.CapacityReservationPreference != nil || v.CapacityReservationTarget != nil) {
1953+
if err := stopInstance(ctx, conn, d.Id(), false, InstanceStopTimeout); err != nil {
1954+
return sdkdiag.AppendFromErr(diags, err)
1955+
}
1956+
1957+
if d.HasChange("capacity_reservation_specification.0.capacity_reservation_target.0.capacity_reservation_id") && d.HasChange("instance_type") {
1958+
instanceType := d.Get("instance_type").(string)
1959+
input := &ec2.ModifyInstanceAttributeInput{
1960+
InstanceId: aws.String(d.Id()),
1961+
InstanceType: &ec2.AttributeValue{
1962+
Value: aws.String(instanceType),
1963+
},
1964+
}
1965+
1966+
if _, err := conn.ModifyInstanceAttributeWithContext(ctx, input); err != nil {
1967+
return sdkdiag.AppendErrorf(diags, "modifying EC2 Instance (%s) InstanceType (%s) attribute: %s", d.Id(), instanceType, err)
1968+
}
1969+
}
1970+
19491971
input := &ec2.ModifyInstanceCapacityReservationAttributesInput{
19501972
CapacityReservationSpecification: v,
19511973
InstanceId: aws.String(d.Id()),
19521974
}
19531975

19541976
log.Printf("[DEBUG] Modifying EC2 Instance capacity reservation attributes: %s", input)
1977+
19551978
_, err := conn.ModifyInstanceCapacityReservationAttributesWithContext(ctx, input)
19561979

19571980
if err != nil {
@@ -1961,6 +1984,10 @@ func resourceInstanceUpdate(ctx context.Context, d *schema.ResourceData, meta in
19611984
if _, err := WaitInstanceCapacityReservationSpecificationUpdated(ctx, conn, d.Id(), v); err != nil {
19621985
return sdkdiag.AppendErrorf(diags, "waiting for EC2 Instance (%s) capacity reservation attributes update: %s", d.Id(), err)
19631986
}
1987+
1988+
if err := startInstance(ctx, conn, d.Id(), true, InstanceStartTimeout); err != nil {
1989+
return sdkdiag.AppendFromErr(diags, err)
1990+
}
19641991
}
19651992
}
19661993
}
@@ -2032,40 +2059,44 @@ func resourceInstanceDelete(ctx context.Context, d *schema.ResourceData, meta in
20322059
}
20332060

20342061
func disableInstanceAPIStop(ctx context.Context, conn *ec2.EC2, id string, disableAPIStop bool) error {
2035-
_, err := conn.ModifyInstanceAttributeWithContext(ctx, &ec2.ModifyInstanceAttributeInput{
2062+
input := &ec2.ModifyInstanceAttributeInput{
20362063
DisableApiStop: &ec2.AttributeBooleanValue{
20372064
Value: aws.Bool(disableAPIStop),
20382065
},
20392066
InstanceId: aws.String(id),
2040-
})
2067+
}
2068+
2069+
_, err := conn.ModifyInstanceAttributeWithContext(ctx, input)
20412070

20422071
if tfawserr.ErrMessageContains(err, errCodeUnsupportedOperation, "not supported for spot instances") {
20432072
log.Printf("[WARN] failed to modify EC2 Instance (%s) attribute: %s", id, err)
20442073
return nil
20452074
}
20462075

20472076
if err != nil {
2048-
return fmt.Errorf("modifying DisableApiStop: %w", err)
2077+
return fmt.Errorf("modifying EC2 Instance (%s) DisableApiStop attribute: %s", id, err)
20492078
}
20502079

20512080
return nil
20522081
}
20532082

20542083
func disableInstanceAPITermination(ctx context.Context, conn *ec2.EC2, id string, disableAPITermination bool) error {
2055-
_, err := conn.ModifyInstanceAttributeWithContext(ctx, &ec2.ModifyInstanceAttributeInput{
2084+
input := &ec2.ModifyInstanceAttributeInput{
20562085
DisableApiTermination: &ec2.AttributeBooleanValue{
20572086
Value: aws.Bool(disableAPITermination),
20582087
},
20592088
InstanceId: aws.String(id),
2060-
})
2089+
}
2090+
2091+
_, err := conn.ModifyInstanceAttributeWithContext(ctx, input)
20612092

20622093
if tfawserr.ErrMessageContains(err, errCodeUnsupportedOperation, "not supported for spot instances") {
20632094
log.Printf("[WARN] failed to modify EC2 Instance (%s) attribute: %s", id, err)
20642095
return nil
20652096
}
20662097

20672098
if err != nil {
2068-
return fmt.Errorf("modifying DisableApiTermination: %w", err)
2099+
return fmt.Errorf("modifying EC2 Instance (%s) DisableApiTermination attribute: %s", id, err)
20692100
}
20702101

20712102
return nil
@@ -2075,33 +2106,19 @@ func disableInstanceAPITermination(ctx context.Context, conn *ec2.EC2, id string
20752106
// as input by first stopping the EC2 instance before the modification
20762107
// and then starting up the EC2 instance after modification.
20772108
// Reference: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Stop_Start.html
2078-
func modifyInstanceAttributeWithStopStart(ctx context.Context, conn *ec2.EC2, input *ec2.ModifyInstanceAttributeInput) error {
2109+
func modifyInstanceAttributeWithStopStart(ctx context.Context, conn *ec2.EC2, input *ec2.ModifyInstanceAttributeInput, attrName string) error {
20792110
id := aws.StringValue(input.InstanceId)
20802111

20812112
if err := stopInstance(ctx, conn, id, false, InstanceStopTimeout); err != nil {
20822113
return err
20832114
}
20842115

20852116
if _, err := conn.ModifyInstanceAttributeWithContext(ctx, input); err != nil {
2086-
return fmt.Errorf("modifying EC2 Instance (%s) attribute: %w", id, err)
2117+
return fmt.Errorf("modifying EC2 Instance (%s) %s attribute: %w", id, attrName, err)
20872118
}
20882119

2089-
// Reference: https://github.com/hashicorp/terraform-provider-aws/issues/16433.
2090-
_, err := tfresource.RetryWhenAWSErrMessageContains(ctx, ec2PropagationTimeout,
2091-
func() (interface{}, error) {
2092-
return conn.StartInstancesWithContext(ctx, &ec2.StartInstancesInput{
2093-
InstanceIds: aws.StringSlice([]string{id}),
2094-
})
2095-
},
2096-
errCodeInvalidParameterValue, "LaunchPlan instance type does not match attribute value",
2097-
)
2098-
2099-
if err != nil {
2100-
return fmt.Errorf("starting EC2 Instance (%s): %w", id, err)
2101-
}
2102-
2103-
if _, err := waitInstanceStarted(ctx, conn, id, InstanceStartTimeout); err != nil {
2104-
return fmt.Errorf("starting EC2 Instance (%s): waiting for completion: %w", id, err)
2120+
if err := startInstance(ctx, conn, id, true, InstanceStartTimeout); err != nil {
2121+
return err
21052122
}
21062123

21072124
return nil
@@ -2977,20 +2994,34 @@ func buildInstanceOpts(ctx context.Context, d *schema.ResourceData, meta interfa
29772994
}
29782995

29792996
// startInstance starts an EC2 instance and waits for the instance to start.
2980-
func startInstance(ctx context.Context, conn *ec2.EC2, id string, timeout time.Duration) error {
2997+
func startInstance(ctx context.Context, conn *ec2.EC2, id string, retry bool, timeout time.Duration) error {
2998+
var err error
2999+
29813000
tflog.Info(ctx, "Starting EC2 Instance", map[string]any{
29823001
"ec2_instance_id": id,
29833002
})
2984-
_, err := conn.StartInstancesWithContext(ctx, &ec2.StartInstancesInput{
2985-
InstanceIds: aws.StringSlice([]string{id}),
2986-
})
3003+
if retry {
3004+
// Reference: https://github.com/hashicorp/terraform-provider-aws/issues/16433.
3005+
_, err = tfresource.RetryWhenAWSErrMessageContains(ctx, ec2PropagationTimeout,
3006+
func() (interface{}, error) {
3007+
return conn.StartInstancesWithContext(ctx, &ec2.StartInstancesInput{
3008+
InstanceIds: aws.StringSlice([]string{id}),
3009+
})
3010+
},
3011+
errCodeInvalidParameterValue, "LaunchPlan instance type does not match attribute value",
3012+
)
3013+
} else {
3014+
_, err = conn.StartInstancesWithContext(ctx, &ec2.StartInstancesInput{
3015+
InstanceIds: aws.StringSlice([]string{id}),
3016+
})
3017+
}
29873018

29883019
if err != nil {
29893020
return fmt.Errorf("starting EC2 Instance (%s): %w", id, err)
29903021
}
29913022

29923023
if _, err := waitInstanceStarted(ctx, conn, id, timeout); err != nil {
2993-
return fmt.Errorf("starting EC2 Instance (%s): waiting for completion: %w", id, err)
3024+
return fmt.Errorf("waiting for EC2 Instance (%s) start: %w", id, err)
29943025
}
29953026

29963027
return nil
@@ -3003,6 +3034,7 @@ func stopInstance(ctx context.Context, conn *ec2.EC2, id string, force bool, tim
30033034
"force": force,
30043035
})
30053036
_, err := conn.StopInstancesWithContext(ctx, &ec2.StopInstancesInput{
3037+
Force: aws.Bool(force),
30063038
InstanceIds: aws.StringSlice([]string{id}),
30073039
})
30083040

@@ -3011,7 +3043,7 @@ func stopInstance(ctx context.Context, conn *ec2.EC2, id string, force bool, tim
30113043
}
30123044

30133045
if _, err := waitInstanceStopped(ctx, conn, id, timeout); err != nil {
3014-
return fmt.Errorf("stopping EC2 Instance (%s): waiting for completion: %w", id, err)
3046+
return fmt.Errorf("waiting for EC2 Instance (%s) stop: %w", id, err)
30153047
}
30163048

30173049
return nil
@@ -3114,7 +3146,7 @@ func waitInstanceReady(ctx context.Context, conn *ec2.EC2, id string, timeout ti
31143146
return nil, err
31153147
}
31163148

3117-
func waitInstanceStarted(ctx context.Context, conn *ec2.EC2, id string, timeout time.Duration) (*ec2.Instance, error) { //nolint:unparam
3149+
func waitInstanceStarted(ctx context.Context, conn *ec2.EC2, id string, timeout time.Duration) (*ec2.Instance, error) {
31183150
stateConf := &retry.StateChangeConf{
31193151
Pending: []string{ec2.InstanceStateNamePending, ec2.InstanceStateNameStopped},
31203152
Target: []string{ec2.InstanceStateNameRunning},

internal/service/ec2/ec2_instance_state.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ func updateInstanceState(ctx context.Context, conn *ec2.EC2, id string, currentS
146146
}
147147

148148
if configuredState == "running" {
149-
if err := startInstance(ctx, conn, id, InstanceStartTimeout); err != nil {
149+
if err := startInstance(ctx, conn, id, false, InstanceStartTimeout); err != nil {
150150
return err
151151
}
152152
}

0 commit comments

Comments
 (0)