Skip to content

Commit

Permalink
Merge pull request #21916 from AndreKapraty/destroy-volume
Browse files Browse the repository at this point in the history
Adding option to capture snapshots on destroy of ebs_volume using final_snapshot parameter
  • Loading branch information
ewbankkit authored Jun 3, 2022
2 parents 1e4b5b8 + 800e89a commit 5c382b1
Show file tree
Hide file tree
Showing 18 changed files with 832 additions and 658 deletions.
3 changes: 3 additions & 0 deletions .changelog/21916.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_ebs_volume: Add `final_snapshot` argument
```
8 changes: 8 additions & 0 deletions internal/service/ec2/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@ const (
DefaultSecurityGroupName = "default"
)

const (
DefaultSnapshotImportRoleName = "vmimport"
)

const (
LaunchTemplateVersionDefault = "$Default"
LaunchTemplateVersionLatest = "$Latest"
Expand All @@ -220,6 +224,10 @@ const (
SriovNetSupportSimple = "simple"
)

const (
TargetStorageTierStandard = "standard"
)

func removeFirstOccurrenceFromStringSlice(slice []string, s string) []string {
for i, v := range slice {
if v == s {
Expand Down
137 changes: 54 additions & 83 deletions internal/service/ec2/ebs_snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
Expand All @@ -24,6 +23,7 @@ func ResourceEBSSnapshot() *schema.Resource {
Read: resourceEBSSnapshotRead,
Update: resourceEBSSnapshotUpdate,
Delete: resourceEBSSnapshotDelete,

Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Expand Down Expand Up @@ -76,13 +76,10 @@ func ResourceEBSSnapshot() *schema.Resource {
Optional: true,
},
"storage_tier": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.Any(
validation.StringInSlice(ec2.TargetStorageTier_Values(), false),
validation.StringInSlice([]string{"standard"}, false), //Enum slice does not include `standard` type.
),
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.StringInSlice(append(ec2.TargetStorageTier_Values(), TargetStorageTierStandard), false),
},
"tags": tftags.TagsSchema(),
"tags_all": tftags.TagsSchemaComputed(),
Expand All @@ -108,45 +105,43 @@ func resourceEBSSnapshotCreate(d *schema.ResourceData, meta interface{}) error {
defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig
tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{})))

request := &ec2.CreateSnapshotInput{
VolumeId: aws.String(d.Get("volume_id").(string)),
volumeID := d.Get("volume_id").(string)
input := &ec2.CreateSnapshotInput{
TagSpecifications: tagSpecificationsFromKeyValueTags(tags, ec2.ResourceTypeSnapshot),
VolumeId: aws.String(volumeID),
}

if v, ok := d.GetOk("description"); ok {
request.Description = aws.String(v.(string))
input.Description = aws.String(v.(string))
}

if v, ok := d.GetOk("outpost_arn"); ok {
request.OutpostArn = aws.String(v.(string))
input.OutpostArn = aws.String(v.(string))
}

var res *ec2.Snapshot
err := resource.Retry(1*time.Minute, func() *resource.RetryError {
var err error
res, err = conn.CreateSnapshot(request)

if tfawserr.ErrMessageContains(err, "SnapshotCreationPerVolumeRateExceeded", "The maximum per volume CreateSnapshot request rate has been exceeded") {
return resource.RetryableError(err)
}

if err != nil {
return resource.NonRetryableError(err)
}
log.Printf("[DEBUG] Creating EBS Snapshot: %s", input)
outputRaw, err := tfresource.RetryWhenAWSErrMessageContains(1*time.Minute,
func() (interface{}, error) {
return conn.CreateSnapshot(input)
},
errCodeSnapshotCreationPerVolumeRateExceeded, "The maximum per volume CreateSnapshot request rate has been exceeded")

return nil
})
if tfresource.TimedOut(err) {
res, err = conn.CreateSnapshot(request)
}
if err != nil {
return fmt.Errorf("error creating EBS Snapshot: %w", err)
return fmt.Errorf("creating EBS Snapshot (%s): %w", volumeID, err)
}

d.SetId(aws.StringValue(res.SnapshotId))
d.SetId(aws.StringValue(outputRaw.(*ec2.Snapshot).SnapshotId))

_, err = tfresource.RetryWhenAWSErrCodeEquals(d.Timeout(schema.TimeoutCreate),
func() (interface{}, error) {
return nil, conn.WaitUntilSnapshotCompleted(&ec2.DescribeSnapshotsInput{
SnapshotIds: aws.StringSlice([]string{d.Id()}),
})
},
errCodeResourceNotReady)

err = resourceEBSSnapshotWaitForAvailable(d, conn)
if err != nil {
return err
return fmt.Errorf("waiting for EBS Snapshot (%s) create: %w", d.Id(), err)
}

if v, ok := d.GetOk("storage_tier"); ok && v.(string) == ec2.TargetStorageTierArchive {
Expand All @@ -156,12 +151,11 @@ func resourceEBSSnapshotCreate(d *schema.ResourceData, meta interface{}) error {
})

if err != nil {
return fmt.Errorf("error setting EBS Snapshot (%s) Storage Tier: %w", d.Id(), err)
return fmt.Errorf("updating EBS Snapshot (%s) Storage Tier: %w", d.Id(), err)
}

_, err = WaitEBSSnapshotTierArchive(conn, d.Id())
if err != nil {
return fmt.Errorf("Error waiting for EBS Snapshot (%s) Storage Tier to be archived: %w", d.Id(), err)
if _, err := waitEBSSnapshotTierArchive(conn, d.Id(), ebsSnapshotArchivedTimeout); err != nil {
return fmt.Errorf("waiting for EBS Snapshot (%s) Storage Tier archive: %w", d.Id(), err)
}
}

Expand All @@ -173,18 +167,25 @@ func resourceEBSSnapshotRead(d *schema.ResourceData, meta interface{}) error {
defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig
ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig

snapshot, err := FindSnapshotById(conn, d.Id())
snapshot, err := FindSnapshotByID(conn, d.Id())

if !d.IsNewResource() && tfresource.NotFound(err) {
log.Printf("[WARN] EBS Snapshot (%s) Not found - removing from state", d.Id())
log.Printf("[WARN] EBS Snapshot %s not found, removing from state", d.Id())
d.SetId("")
return nil
}

if err != nil {
return fmt.Errorf("error reading EBS Snapshot (%s): %w", d.Id(), err)
return fmt.Errorf("reading EBS Snapshot (%s): %w", d.Id(), err)
}

arn := arn.ARN{
Partition: meta.(*conns.AWSClient).Partition,
Service: ec2.ServiceName,
Region: meta.(*conns.AWSClient).Region,
Resource: fmt.Sprintf("snapshot/%s", d.Id()),
}.String()
d.Set("arn", arn)
d.Set("data_encryption_key_id", snapshot.DataEncryptionKeyId)
d.Set("description", snapshot.Description)
d.Set("encrypted", snapshot.Encrypted)
Expand All @@ -200,43 +201,32 @@ func resourceEBSSnapshotRead(d *schema.ResourceData, meta interface{}) error {

//lintignore:AWSR002
if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil {
return fmt.Errorf("error setting tags: %w", err)
return fmt.Errorf("setting tags: %w", err)
}

if err := d.Set("tags_all", tags.Map()); err != nil {
return fmt.Errorf("error setting tags_all: %w", err)
return fmt.Errorf("setting tags_all: %w", err)
}

snapshotArn := arn.ARN{
Partition: meta.(*conns.AWSClient).Partition,
Region: meta.(*conns.AWSClient).Region,
Resource: fmt.Sprintf("snapshot/%s", d.Id()),
Service: ec2.ServiceName,
}.String()

d.Set("arn", snapshotArn)

return nil
}

func resourceEBSSnapshotUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).EC2Conn

if d.HasChange("storage_tier") {
tier := d.Get("storage_tier").(string)
if tier == ec2.TargetStorageTierArchive {
if tier := d.Get("storage_tier").(string); tier == ec2.TargetStorageTierArchive {
_, err := conn.ModifySnapshotTier(&ec2.ModifySnapshotTierInput{
SnapshotId: aws.String(d.Id()),
StorageTier: aws.String(tier),
})

if err != nil {
return fmt.Errorf("error upadating EBS Snapshot (%s) Storage Tier: %w", d.Id(), err)
return fmt.Errorf("updating EBS Snapshot (%s) Storage Tier: %w", d.Id(), err)
}

_, err = WaitEBSSnapshotTierArchive(conn, d.Id())
if err != nil {
return fmt.Errorf("Error waiting for EBS Snapshot (%s) Storage Tier to be archived: %w", d.Id(), err)
if _, err := waitEBSSnapshotTierArchive(conn, d.Id(), ebsSnapshotArchivedTimeout); err != nil {
return fmt.Errorf("waiting for EBS Snapshot (%s) Storage Tier archive: %w", d.Id(), err)
}
} else {
input := &ec2.RestoreSnapshotTierInput{
Expand All @@ -255,15 +245,16 @@ func resourceEBSSnapshotUpdate(d *schema.ResourceData, meta interface{}) error {
_, err := conn.RestoreSnapshotTier(input)

if err != nil {
return fmt.Errorf("error restoring EBS Snapshot (%s): %w", d.Id(), err)
return fmt.Errorf("restoring EBS Snapshot (%s): %w", d.Id(), err)
}
}
}

if d.HasChange("tags_all") {
o, n := d.GetChange("tags_all")

if err := UpdateTags(conn, d.Id(), o, n); err != nil {
return fmt.Errorf("error updating tags: %w", err)
return fmt.Errorf("updating EBS Snapshot (%s) tags: %w", d.Id(), err)
}
}

Expand All @@ -280,33 +271,13 @@ func resourceEBSSnapshotDelete(d *schema.ResourceData, meta interface{}) error {
})
}, errCodeInvalidSnapshotInUse)

if err != nil {
return fmt.Errorf("error deleting EBS Snapshot (%s): %w", d.Id(), err)
if tfawserr.ErrCodeEquals(err, errCodeInvalidSnapshotNotFound) {
return nil
}

return nil
}

func resourceEBSSnapshotWaitForAvailable(d *schema.ResourceData, conn *ec2.EC2) error {
log.Printf("Waiting for Snapshot %s to become available...", d.Id())
input := &ec2.DescribeSnapshotsInput{
SnapshotIds: []*string{aws.String(d.Id())},
}
err := resource.Retry(d.Timeout(schema.TimeoutCreate), func() *resource.RetryError {
err := conn.WaitUntilSnapshotCompleted(input)
if err == nil {
return nil
}
if tfawserr.ErrCodeEquals(err, "ResourceNotReady") {
return resource.RetryableError(fmt.Errorf("EBS Snapshot - waiting for snapshot to become available"))
}
return resource.NonRetryableError(err)
})
if tfresource.TimedOut(err) {
err = conn.WaitUntilSnapshotCompleted(input)
}
if err != nil {
return fmt.Errorf("Error waiting for EBS snapshot to complete: %w", err)
return fmt.Errorf("deleting EBS Snapshot (%s): %w", d.Id(), err)
}

return nil
}
48 changes: 29 additions & 19 deletions internal/service/ec2/ebs_snapshot_copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
tftags "github.com/hashicorp/terraform-provider-aws/internal/tags"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
"github.com/hashicorp/terraform-provider-aws/internal/verify"
)

Expand Down Expand Up @@ -72,13 +73,10 @@ func ResourceEBSSnapshotCopy() *schema.Resource {
ForceNew: true,
},
"storage_tier": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.Any(
validation.StringInSlice(ec2.TargetStorageTier_Values(), false),
validation.StringInSlice([]string{"standard"}, false), //Enum slice does not include `standard` type.
),
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.StringInSlice(append(ec2.TargetStorageTier_Values(), TargetStorageTierStandard), false),
},
"tags": tftags.TagsSchema(),
"tags_all": tftags.TagsSchemaComputed(),
Expand All @@ -103,31 +101,42 @@ func resourceEBSSnapshotCopyCreate(d *schema.ResourceData, meta interface{}) err
defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig
tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{})))

request := &ec2.CopySnapshotInput{
input := &ec2.CopySnapshotInput{
SourceRegion: aws.String(d.Get("source_region").(string)),
SourceSnapshotId: aws.String(d.Get("source_snapshot_id").(string)),
TagSpecifications: tagSpecificationsFromKeyValueTags(tags, ec2.ResourceTypeSnapshot),
}

if v, ok := d.GetOk("description"); ok {
request.Description = aws.String(v.(string))
input.Description = aws.String(v.(string))
}

if v, ok := d.GetOk("encrypted"); ok {
request.Encrypted = aws.Bool(v.(bool))
input.Encrypted = aws.Bool(v.(bool))
}

if v, ok := d.GetOk("kms_key_id"); ok {
request.KmsKeyId = aws.String(v.(string))
input.KmsKeyId = aws.String(v.(string))
}

res, err := conn.CopySnapshot(request)
output, err := conn.CopySnapshot(input)

if err != nil {
return err
return fmt.Errorf("creating EBS Snapshot Copy: %w", err)
}

d.SetId(aws.StringValue(res.SnapshotId))
d.SetId(aws.StringValue(output.SnapshotId))

_, err = tfresource.RetryWhenAWSErrCodeEquals(d.Timeout(schema.TimeoutCreate),
func() (interface{}, error) {
return nil, conn.WaitUntilSnapshotCompleted(&ec2.DescribeSnapshotsInput{
SnapshotIds: aws.StringSlice([]string{d.Id()}),
})
},
errCodeResourceNotReady)

err = resourceEBSSnapshotWaitForAvailable(d, conn)
if err != nil {
return err
return fmt.Errorf("waiting for EBS Snapshot Copy (%s) create: %w", d.Id(), err)
}

if v, ok := d.GetOk("storage_tier"); ok && v.(string) == ec2.TargetStorageTierArchive {
Expand All @@ -137,12 +146,13 @@ func resourceEBSSnapshotCopyCreate(d *schema.ResourceData, meta interface{}) err
})

if err != nil {
return fmt.Errorf("error setting EBS Snapshot Copy (%s) Storage Tier: %w", d.Id(), err)
return fmt.Errorf("setting EBS Snapshot Copy (%s) Storage Tier: %w", d.Id(), err)
}

_, err = WaitEBSSnapshotTierArchive(conn, d.Id())
_, err = waitEBSSnapshotTierArchive(conn, d.Id(), ebsSnapshotArchivedTimeout)

if err != nil {
return fmt.Errorf("Error waiting for EBS Snapshot Copy (%s) Storage Tier to be archived: %w", d.Id(), err)
return fmt.Errorf("waiting for EBS Snapshot Copy (%s) Storage Tier archive: %w", d.Id(), err)
}
}

Expand Down
Loading

0 comments on commit 5c382b1

Please sign in to comment.