Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

resource/aws_ami: Fixes eventual consistency errors #34691

Merged
merged 5 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changelog/34691.txt
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to additionally mention aws_ami_from_instance and aws_ami_copy?

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
```release-note:bug
resource/aws_ami: Correctly sets `description` on update due to eventual consistency
ewbankkit marked this conversation as resolved.
Show resolved Hide resolved
```

```release-note:bug
resource/aws_ami: Correctly sets `deprecation_time` on creation and update due to eventual consistency
```

```release-note:bug
resource/aws_ami: Now allows removing `deprecation_time`
```
150 changes: 139 additions & 11 deletions internal/service/ec2/ec2_ami.go
Original file line number Diff line number Diff line change
Expand Up @@ -465,22 +465,22 @@ func resourceAMIUpdate(ctx context.Context, d *schema.ResourceData, meta interfa
var diags diag.Diagnostics
conn := meta.(*conns.AWSClient).EC2Conn(ctx)

if d.Get("description").(string) != "" {
_, err := conn.ModifyImageAttributeWithContext(ctx, &ec2.ModifyImageAttributeInput{
Description: &ec2.AttributeValue{
Value: aws.String(d.Get("description").(string)),
},
ImageId: aws.String(d.Id()),
})

if d.HasChange("description") {
err := updateDescription(ctx, conn, d.Id(), d.Get("description").(string))
if err != nil {
return sdkdiag.AppendErrorf(diags, "updating EC2 AMI (%s) description: %s", d.Id(), err)
return sdkdiag.AppendErrorf(diags, "updating EC2 AMI (%s): %s", d.Id(), err)
}
}

if d.HasChange("deprecation_time") {
if err := enableImageDeprecation(ctx, conn, d.Id(), d.Get("deprecation_time").(string)); err != nil {
return sdkdiag.AppendErrorf(diags, "updating EC2 AMI (%s): %s", d.Id(), err)
if v := d.Get("deprecation_time").(string); v != "" {
if err := enableImageDeprecation(ctx, conn, d.Id(), v); err != nil {
return sdkdiag.AppendErrorf(diags, "updating EC2 AMI (%s): %s", d.Id(), err)
}
} else {
if err := disableImageDeprecation(ctx, conn, d.Id()); err != nil {
return sdkdiag.AppendErrorf(diags, "updating EC2 AMI (%s): %s", d.Id(), err)
}
}
}

Expand Down Expand Up @@ -538,6 +538,27 @@ func resourceAMIDelete(ctx context.Context, d *schema.ResourceData, meta interfa
return diags
}

func updateDescription(ctx context.Context, conn *ec2.EC2, id string, description string) error {
input := &ec2.ModifyImageAttributeInput{
Description: &ec2.AttributeValue{
Value: aws.String(description),
},
ImageId: aws.String(id),
}

_, err := conn.ModifyImageAttributeWithContext(ctx, input)
if err != nil {
return fmt.Errorf("updating description: %s", err)
}

err = waitImageDescriptionUpdated(ctx, conn, id, description)
if err != nil {
return fmt.Errorf("updating description: waiting for completion: %s", err)
}

return nil
}

func enableImageDeprecation(ctx context.Context, conn *ec2.EC2, id string, deprecateAt string) error {
v, _ := time.Parse(time.RFC3339, deprecateAt)
input := &ec2.EnableImageDeprecationInput{
Expand All @@ -551,6 +572,32 @@ func enableImageDeprecation(ctx context.Context, conn *ec2.EC2, id string, depre
return fmt.Errorf("enabling deprecation: %w", err)
}

err = waitImageDeprecationTimeUpdated(ctx, conn, id, deprecateAt)

if err != nil {
return fmt.Errorf("enabling deprecation: waiting for completion: %w", err)
}

return nil
}

func disableImageDeprecation(ctx context.Context, conn *ec2.EC2, id string) error {
input := &ec2.DisableImageDeprecationInput{
ImageId: aws.String(id),
}

_, err := conn.DisableImageDeprecationWithContext(ctx, input)

if err != nil {
return fmt.Errorf("disabling deprecation: %w", err)
}

err = waitImageDeprecationTimeDisabled(ctx, conn, id)

if err != nil {
return fmt.Errorf("disabling deprecation: waiting for completion: %w", err)
}

return nil
}

Expand Down Expand Up @@ -782,3 +829,84 @@ func flattenBlockDeviceMappingsForAMIEphemeralBlockDevice(apiObjects []*ec2.Bloc

return tfList
}

const imageDeprecationPropagationTimeout = 2 * time.Minute

func waitImageDescriptionUpdated(ctx context.Context, conn *ec2.EC2, imageID, expectedValue string) error {
return tfresource.WaitUntil(ctx, imageDeprecationPropagationTimeout, func() (bool, error) {
output, err := FindImageByID(ctx, conn, imageID)

if tfresource.NotFound(err) {
return false, nil
}

if err != nil {
return false, err
}

return aws.StringValue(output.Description) == expectedValue, nil
},
tfresource.WaitOpts{
Delay: amiRetryDelay,
MinTimeout: amiRetryMinTimeout,
},
)
}

func waitImageDeprecationTimeUpdated(ctx context.Context, conn *ec2.EC2, imageID, expectedValue string) error {
expected, err := time.Parse(time.RFC3339, expectedValue)
if err != nil {
return err
}
expected = expected.Round(time.Minute)

return tfresource.WaitUntil(ctx, imageDeprecationPropagationTimeout, func() (bool, error) {
output, err := FindImageByID(ctx, conn, imageID)

if tfresource.NotFound(err) {
return false, nil
}

if err != nil {
return false, err
}

if output.DeprecationTime == nil {
return false, nil
}

dt, err := time.Parse(time.RFC3339, *output.DeprecationTime)
if err != nil {
return false, err
}
dt = dt.Round(time.Minute)

return expected.Equal(dt), nil
},
tfresource.WaitOpts{
Delay: amiRetryDelay,
MinTimeout: amiRetryMinTimeout,
},
)
}

func waitImageDeprecationTimeDisabled(ctx context.Context, conn *ec2.EC2, imageID string) error {
return tfresource.WaitUntil(ctx, imageDeprecationPropagationTimeout, func() (bool, error) {
output, err := FindImageByID(ctx, conn, imageID)

if tfresource.NotFound(err) {
return false, nil
}

if err != nil {
return false, err
}

return output.DeprecationTime == nil, nil
},
tfresource.WaitOpts{
Delay: amiRetryDelay,
MinTimeout: amiRetryMinTimeout,
},
)
}
Loading
Loading