Skip to content

Commit 5c382b1

Browse files
authored
Merge pull request #21916 from AndreKapraty/destroy-volume
Adding option to capture snapshots on destroy of ebs_volume using final_snapshot parameter
2 parents 1e4b5b8 + 800e89a commit 5c382b1

18 files changed

+832
-658
lines changed

.changelog/21916.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
resource/aws_ebs_volume: Add `final_snapshot` argument
3+
```

internal/service/ec2/consts.go

+8
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,10 @@ const (
211211
DefaultSecurityGroupName = "default"
212212
)
213213

214+
const (
215+
DefaultSnapshotImportRoleName = "vmimport"
216+
)
217+
214218
const (
215219
LaunchTemplateVersionDefault = "$Default"
216220
LaunchTemplateVersionLatest = "$Latest"
@@ -220,6 +224,10 @@ const (
220224
SriovNetSupportSimple = "simple"
221225
)
222226

227+
const (
228+
TargetStorageTierStandard = "standard"
229+
)
230+
223231
func removeFirstOccurrenceFromStringSlice(slice []string, s string) []string {
224232
for i, v := range slice {
225233
if v == s {

internal/service/ec2/ebs_snapshot.go

+54-83
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"github.com/aws/aws-sdk-go/aws/arn"
1010
"github.com/aws/aws-sdk-go/service/ec2"
1111
"github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr"
12-
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
1312
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1413
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
1514
"github.com/hashicorp/terraform-provider-aws/internal/conns"
@@ -24,6 +23,7 @@ func ResourceEBSSnapshot() *schema.Resource {
2423
Read: resourceEBSSnapshotRead,
2524
Update: resourceEBSSnapshotUpdate,
2625
Delete: resourceEBSSnapshotDelete,
26+
2727
Importer: &schema.ResourceImporter{
2828
State: schema.ImportStatePassthrough,
2929
},
@@ -76,13 +76,10 @@ func ResourceEBSSnapshot() *schema.Resource {
7676
Optional: true,
7777
},
7878
"storage_tier": {
79-
Type: schema.TypeString,
80-
Optional: true,
81-
Computed: true,
82-
ValidateFunc: validation.Any(
83-
validation.StringInSlice(ec2.TargetStorageTier_Values(), false),
84-
validation.StringInSlice([]string{"standard"}, false), //Enum slice does not include `standard` type.
85-
),
79+
Type: schema.TypeString,
80+
Optional: true,
81+
Computed: true,
82+
ValidateFunc: validation.StringInSlice(append(ec2.TargetStorageTier_Values(), TargetStorageTierStandard), false),
8683
},
8784
"tags": tftags.TagsSchema(),
8885
"tags_all": tftags.TagsSchemaComputed(),
@@ -108,45 +105,43 @@ func resourceEBSSnapshotCreate(d *schema.ResourceData, meta interface{}) error {
108105
defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig
109106
tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{})))
110107

111-
request := &ec2.CreateSnapshotInput{
112-
VolumeId: aws.String(d.Get("volume_id").(string)),
108+
volumeID := d.Get("volume_id").(string)
109+
input := &ec2.CreateSnapshotInput{
113110
TagSpecifications: tagSpecificationsFromKeyValueTags(tags, ec2.ResourceTypeSnapshot),
111+
VolumeId: aws.String(volumeID),
114112
}
113+
115114
if v, ok := d.GetOk("description"); ok {
116-
request.Description = aws.String(v.(string))
115+
input.Description = aws.String(v.(string))
117116
}
118117

119118
if v, ok := d.GetOk("outpost_arn"); ok {
120-
request.OutpostArn = aws.String(v.(string))
119+
input.OutpostArn = aws.String(v.(string))
121120
}
122121

123-
var res *ec2.Snapshot
124-
err := resource.Retry(1*time.Minute, func() *resource.RetryError {
125-
var err error
126-
res, err = conn.CreateSnapshot(request)
127-
128-
if tfawserr.ErrMessageContains(err, "SnapshotCreationPerVolumeRateExceeded", "The maximum per volume CreateSnapshot request rate has been exceeded") {
129-
return resource.RetryableError(err)
130-
}
131-
132-
if err != nil {
133-
return resource.NonRetryableError(err)
134-
}
122+
log.Printf("[DEBUG] Creating EBS Snapshot: %s", input)
123+
outputRaw, err := tfresource.RetryWhenAWSErrMessageContains(1*time.Minute,
124+
func() (interface{}, error) {
125+
return conn.CreateSnapshot(input)
126+
},
127+
errCodeSnapshotCreationPerVolumeRateExceeded, "The maximum per volume CreateSnapshot request rate has been exceeded")
135128

136-
return nil
137-
})
138-
if tfresource.TimedOut(err) {
139-
res, err = conn.CreateSnapshot(request)
140-
}
141129
if err != nil {
142-
return fmt.Errorf("error creating EBS Snapshot: %w", err)
130+
return fmt.Errorf("creating EBS Snapshot (%s): %w", volumeID, err)
143131
}
144132

145-
d.SetId(aws.StringValue(res.SnapshotId))
133+
d.SetId(aws.StringValue(outputRaw.(*ec2.Snapshot).SnapshotId))
134+
135+
_, err = tfresource.RetryWhenAWSErrCodeEquals(d.Timeout(schema.TimeoutCreate),
136+
func() (interface{}, error) {
137+
return nil, conn.WaitUntilSnapshotCompleted(&ec2.DescribeSnapshotsInput{
138+
SnapshotIds: aws.StringSlice([]string{d.Id()}),
139+
})
140+
},
141+
errCodeResourceNotReady)
146142

147-
err = resourceEBSSnapshotWaitForAvailable(d, conn)
148143
if err != nil {
149-
return err
144+
return fmt.Errorf("waiting for EBS Snapshot (%s) create: %w", d.Id(), err)
150145
}
151146

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

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

162-
_, err = WaitEBSSnapshotTierArchive(conn, d.Id())
163-
if err != nil {
164-
return fmt.Errorf("Error waiting for EBS Snapshot (%s) Storage Tier to be archived: %w", d.Id(), err)
157+
if _, err := waitEBSSnapshotTierArchive(conn, d.Id(), ebsSnapshotArchivedTimeout); err != nil {
158+
return fmt.Errorf("waiting for EBS Snapshot (%s) Storage Tier archive: %w", d.Id(), err)
165159
}
166160
}
167161

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

176-
snapshot, err := FindSnapshotById(conn, d.Id())
170+
snapshot, err := FindSnapshotByID(conn, d.Id())
177171

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

184178
if err != nil {
185-
return fmt.Errorf("error reading EBS Snapshot (%s): %w", d.Id(), err)
179+
return fmt.Errorf("reading EBS Snapshot (%s): %w", d.Id(), err)
186180
}
187181

182+
arn := arn.ARN{
183+
Partition: meta.(*conns.AWSClient).Partition,
184+
Service: ec2.ServiceName,
185+
Region: meta.(*conns.AWSClient).Region,
186+
Resource: fmt.Sprintf("snapshot/%s", d.Id()),
187+
}.String()
188+
d.Set("arn", arn)
188189
d.Set("data_encryption_key_id", snapshot.DataEncryptionKeyId)
189190
d.Set("description", snapshot.Description)
190191
d.Set("encrypted", snapshot.Encrypted)
@@ -200,43 +201,32 @@ func resourceEBSSnapshotRead(d *schema.ResourceData, meta interface{}) error {
200201

201202
//lintignore:AWSR002
202203
if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil {
203-
return fmt.Errorf("error setting tags: %w", err)
204+
return fmt.Errorf("setting tags: %w", err)
204205
}
205206

206207
if err := d.Set("tags_all", tags.Map()); err != nil {
207-
return fmt.Errorf("error setting tags_all: %w", err)
208+
return fmt.Errorf("setting tags_all: %w", err)
208209
}
209210

210-
snapshotArn := arn.ARN{
211-
Partition: meta.(*conns.AWSClient).Partition,
212-
Region: meta.(*conns.AWSClient).Region,
213-
Resource: fmt.Sprintf("snapshot/%s", d.Id()),
214-
Service: ec2.ServiceName,
215-
}.String()
216-
217-
d.Set("arn", snapshotArn)
218-
219211
return nil
220212
}
221213

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

225217
if d.HasChange("storage_tier") {
226-
tier := d.Get("storage_tier").(string)
227-
if tier == ec2.TargetStorageTierArchive {
218+
if tier := d.Get("storage_tier").(string); tier == ec2.TargetStorageTierArchive {
228219
_, err := conn.ModifySnapshotTier(&ec2.ModifySnapshotTierInput{
229220
SnapshotId: aws.String(d.Id()),
230221
StorageTier: aws.String(tier),
231222
})
232223

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

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

257247
if err != nil {
258-
return fmt.Errorf("error restoring EBS Snapshot (%s): %w", d.Id(), err)
248+
return fmt.Errorf("restoring EBS Snapshot (%s): %w", d.Id(), err)
259249
}
260250
}
261251
}
262252

263253
if d.HasChange("tags_all") {
264254
o, n := d.GetChange("tags_all")
255+
265256
if err := UpdateTags(conn, d.Id(), o, n); err != nil {
266-
return fmt.Errorf("error updating tags: %w", err)
257+
return fmt.Errorf("updating EBS Snapshot (%s) tags: %w", d.Id(), err)
267258
}
268259
}
269260

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

283-
if err != nil {
284-
return fmt.Errorf("error deleting EBS Snapshot (%s): %w", d.Id(), err)
274+
if tfawserr.ErrCodeEquals(err, errCodeInvalidSnapshotNotFound) {
275+
return nil
285276
}
286277

287-
return nil
288-
}
289-
290-
func resourceEBSSnapshotWaitForAvailable(d *schema.ResourceData, conn *ec2.EC2) error {
291-
log.Printf("Waiting for Snapshot %s to become available...", d.Id())
292-
input := &ec2.DescribeSnapshotsInput{
293-
SnapshotIds: []*string{aws.String(d.Id())},
294-
}
295-
err := resource.Retry(d.Timeout(schema.TimeoutCreate), func() *resource.RetryError {
296-
err := conn.WaitUntilSnapshotCompleted(input)
297-
if err == nil {
298-
return nil
299-
}
300-
if tfawserr.ErrCodeEquals(err, "ResourceNotReady") {
301-
return resource.RetryableError(fmt.Errorf("EBS Snapshot - waiting for snapshot to become available"))
302-
}
303-
return resource.NonRetryableError(err)
304-
})
305-
if tfresource.TimedOut(err) {
306-
err = conn.WaitUntilSnapshotCompleted(input)
307-
}
308278
if err != nil {
309-
return fmt.Errorf("Error waiting for EBS snapshot to complete: %w", err)
279+
return fmt.Errorf("deleting EBS Snapshot (%s): %w", d.Id(), err)
310280
}
281+
311282
return nil
312283
}

internal/service/ec2/ebs_snapshot_copy.go

+29-19
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
1010
"github.com/hashicorp/terraform-provider-aws/internal/conns"
1111
tftags "github.com/hashicorp/terraform-provider-aws/internal/tags"
12+
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
1213
"github.com/hashicorp/terraform-provider-aws/internal/verify"
1314
)
1415

@@ -72,13 +73,10 @@ func ResourceEBSSnapshotCopy() *schema.Resource {
7273
ForceNew: true,
7374
},
7475
"storage_tier": {
75-
Type: schema.TypeString,
76-
Optional: true,
77-
Computed: true,
78-
ValidateFunc: validation.Any(
79-
validation.StringInSlice(ec2.TargetStorageTier_Values(), false),
80-
validation.StringInSlice([]string{"standard"}, false), //Enum slice does not include `standard` type.
81-
),
76+
Type: schema.TypeString,
77+
Optional: true,
78+
Computed: true,
79+
ValidateFunc: validation.StringInSlice(append(ec2.TargetStorageTier_Values(), TargetStorageTierStandard), false),
8280
},
8381
"tags": tftags.TagsSchema(),
8482
"tags_all": tftags.TagsSchemaComputed(),
@@ -103,31 +101,42 @@ func resourceEBSSnapshotCopyCreate(d *schema.ResourceData, meta interface{}) err
103101
defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig
104102
tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{})))
105103

106-
request := &ec2.CopySnapshotInput{
104+
input := &ec2.CopySnapshotInput{
107105
SourceRegion: aws.String(d.Get("source_region").(string)),
108106
SourceSnapshotId: aws.String(d.Get("source_snapshot_id").(string)),
109107
TagSpecifications: tagSpecificationsFromKeyValueTags(tags, ec2.ResourceTypeSnapshot),
110108
}
109+
111110
if v, ok := d.GetOk("description"); ok {
112-
request.Description = aws.String(v.(string))
111+
input.Description = aws.String(v.(string))
113112
}
113+
114114
if v, ok := d.GetOk("encrypted"); ok {
115-
request.Encrypted = aws.Bool(v.(bool))
115+
input.Encrypted = aws.Bool(v.(bool))
116116
}
117+
117118
if v, ok := d.GetOk("kms_key_id"); ok {
118-
request.KmsKeyId = aws.String(v.(string))
119+
input.KmsKeyId = aws.String(v.(string))
119120
}
120121

121-
res, err := conn.CopySnapshot(request)
122+
output, err := conn.CopySnapshot(input)
123+
122124
if err != nil {
123-
return err
125+
return fmt.Errorf("creating EBS Snapshot Copy: %w", err)
124126
}
125127

126-
d.SetId(aws.StringValue(res.SnapshotId))
128+
d.SetId(aws.StringValue(output.SnapshotId))
129+
130+
_, err = tfresource.RetryWhenAWSErrCodeEquals(d.Timeout(schema.TimeoutCreate),
131+
func() (interface{}, error) {
132+
return nil, conn.WaitUntilSnapshotCompleted(&ec2.DescribeSnapshotsInput{
133+
SnapshotIds: aws.StringSlice([]string{d.Id()}),
134+
})
135+
},
136+
errCodeResourceNotReady)
127137

128-
err = resourceEBSSnapshotWaitForAvailable(d, conn)
129138
if err != nil {
130-
return err
139+
return fmt.Errorf("waiting for EBS Snapshot Copy (%s) create: %w", d.Id(), err)
131140
}
132141

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

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

143-
_, err = WaitEBSSnapshotTierArchive(conn, d.Id())
152+
_, err = waitEBSSnapshotTierArchive(conn, d.Id(), ebsSnapshotArchivedTimeout)
153+
144154
if err != nil {
145-
return fmt.Errorf("Error waiting for EBS Snapshot Copy (%s) Storage Tier to be archived: %w", d.Id(), err)
155+
return fmt.Errorf("waiting for EBS Snapshot Copy (%s) Storage Tier archive: %w", d.Id(), err)
146156
}
147157
}
148158

0 commit comments

Comments
 (0)