Skip to content

Commit af25c23

Browse files
committed
r/aws_s3control_bucket: Switch to 'WithoutTimeout' CRUD handlers (#15090).
Acceptance test output: % make testacc TESTARGS='-run=TestAccS3ControlBucket_' PKG=s3control ACCTEST_PARALLELISM=3 ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/s3control/... -v -count 1 -parallel 3 -run=TestAccS3ControlBucket_ -timeout 180m === RUN TestAccS3ControlBucket_basic === PAUSE TestAccS3ControlBucket_basic === RUN TestAccS3ControlBucket_disappears === PAUSE TestAccS3ControlBucket_disappears === RUN TestAccS3ControlBucket_tags acctest.go:71: S3 Control Bucket resource tagging requires additional eventual consistency handling, see also: #15572 --- SKIP: TestAccS3ControlBucket_tags (0.00s) === CONT TestAccS3ControlBucket_basic === CONT TestAccS3ControlBucket_disappears acctest.go:1368: skipping since no Outposts found --- SKIP: TestAccS3ControlBucket_disappears (1.00s) === CONT TestAccS3ControlBucket_basic acctest.go:1368: skipping since no Outposts found --- SKIP: TestAccS3ControlBucket_basic (1.07s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/s3control 6.289s
1 parent edcb1a7 commit af25c23

File tree

7 files changed

+156
-186
lines changed

7 files changed

+156
-186
lines changed

internal/provider/provider.go

-1
Original file line numberDiff line numberDiff line change
@@ -2005,7 +2005,6 @@ func New(ctx context.Context) (*schema.Provider, error) {
20052005
"aws_s3_object_copy": s3.ResourceObjectCopy(),
20062006
"aws_s3_bucket_object": s3.ResourceBucketObject(), // DEPRECATED: use aws_s3_object instead
20072007

2008-
"aws_s3control_bucket": s3control.ResourceBucket(),
20092008
"aws_s3control_bucket_policy": s3control.ResourceBucketPolicy(),
20102009
"aws_s3control_multi_region_access_point": s3control.ResourceMultiRegionAccessPoint(),
20112010
"aws_s3control_multi_region_access_point_policy": s3control.ResourceMultiRegionAccessPointPolicy(),

internal/service/s3control/bucket.go

+138-65
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package s3control
22

33
import (
4+
"context"
45
"fmt"
56
"log"
67
"regexp"
@@ -11,6 +12,7 @@ import (
1112
"github.com/aws/aws-sdk-go/aws/arn"
1213
"github.com/aws/aws-sdk-go/service/s3control"
1314
"github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr"
15+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
1416
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
1517
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1618
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
@@ -25,12 +27,16 @@ const (
2527
bucketStatePropagationTimeout = 5 * time.Minute
2628
)
2729

28-
func ResourceBucket() *schema.Resource {
30+
func init() {
31+
_sp.registerSDKResourceFactory("aws_s3control_bucket", resourceBucket)
32+
}
33+
34+
func resourceBucket() *schema.Resource {
2935
return &schema.Resource{
30-
Create: resourceBucketCreate,
31-
Read: resourceBucketRead,
32-
Update: resourceBucketUpdate,
33-
Delete: resourceBucketDelete,
36+
CreateWithoutTimeout: resourceBucketCreate,
37+
ReadWithoutTimeout: resourceBucketRead,
38+
UpdateWithoutTimeout: resourceBucketUpdate,
39+
DeleteWithoutTimeout: resourceBucketDelete,
3440

3541
Importer: &schema.ResourceImporter{
3642
State: schema.ImportStatePassthrough,
@@ -74,135 +80,113 @@ func ResourceBucket() *schema.Resource {
7480
}
7581
}
7682

77-
func resourceBucketCreate(d *schema.ResourceData, meta interface{}) error {
83+
func resourceBucketCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
7884
conn := meta.(*conns.AWSClient).S3ControlConn()
7985
defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig
8086
tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{})))
8187

8288
bucket := d.Get("bucket").(string)
83-
8489
input := &s3control.CreateBucketInput{
8590
Bucket: aws.String(bucket),
8691
OutpostId: aws.String(d.Get("outpost_id").(string)),
8792
}
8893

89-
output, err := conn.CreateBucket(input)
94+
output, err := conn.CreateBucketWithContext(ctx, input)
9095

9196
if err != nil {
92-
return fmt.Errorf("error creating S3 Control Bucket (%s): %w", bucket, err)
93-
}
94-
95-
if output == nil {
96-
return fmt.Errorf("error creating S3 Control Bucket (%s): empty response", bucket)
97+
return diag.Errorf("creating S3 Control Bucket (%s): %s", bucket, err)
9798
}
9899

99100
d.SetId(aws.StringValue(output.BucketArn))
100101

101102
if len(tags) > 0 {
102-
if err := bucketUpdateTags(conn, d.Id(), nil, tags); err != nil {
103-
return fmt.Errorf("error adding S3 Control Bucket (%s) tags: %w", d.Id(), err)
103+
if err := bucketUpdateTags(ctx, conn, d.Id(), nil, tags); err != nil {
104+
return diag.Errorf("adding S3 Control Bucket (%s) tags: %s", d.Id(), err)
104105
}
105106
}
106107

107-
return resourceBucketRead(d, meta)
108+
return resourceBucketRead(ctx, d, meta)
108109
}
109110

110-
func resourceBucketRead(d *schema.ResourceData, meta interface{}) error {
111+
func resourceBucketRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
111112
conn := meta.(*conns.AWSClient).S3ControlConn()
112113
defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig
113114
ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig
114115

115116
parsedArn, err := arn.Parse(d.Id())
116117

117118
if err != nil {
118-
return fmt.Errorf("error parsing S3 Control Bucket ARN (%s): %w", d.Id(), err)
119+
return diag.FromErr(err)
119120
}
120121

121122
// ARN resource format: outpost/<outpost-id>/bucket/<my-bucket-name>
122123
arnResourceParts := strings.Split(parsedArn.Resource, "/")
123124

124125
if parsedArn.AccountID == "" || len(arnResourceParts) != 4 {
125-
return fmt.Errorf("error parsing S3 Control Bucket ARN (%s): unknown format", d.Id())
126-
}
127-
128-
input := &s3control.GetBucketInput{
129-
AccountId: aws.String(parsedArn.AccountID),
130-
Bucket: aws.String(d.Id()),
126+
return diag.Errorf("parsing S3 Control Bucket ARN (%s): unknown format", d.Id())
131127
}
132128

133-
output, err := conn.GetBucket(input)
134-
135-
if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, "NoSuchBucket") {
136-
log.Printf("[WARN] S3 Control Bucket (%s) not found, removing from state", d.Id())
137-
d.SetId("")
138-
return nil
139-
}
129+
output, err := FindBucketByTwoPartKey(ctx, conn, parsedArn.AccountID, d.Id())
140130

141-
if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, "NoSuchOutpost") {
131+
if !d.IsNewResource() && tfresource.NotFound(err) {
142132
log.Printf("[WARN] S3 Control Bucket (%s) not found, removing from state", d.Id())
143133
d.SetId("")
144134
return nil
145135
}
146136

147137
if err != nil {
148-
return fmt.Errorf("error reading S3 Control Bucket (%s): %w", d.Id(), err)
149-
}
150-
151-
if output == nil {
152-
return fmt.Errorf("error reading S3 Control Bucket (%s): empty response", d.Id())
138+
return diag.Errorf("reading S3 Control Bucket (%s): %s", d.Id(), err)
153139
}
154140

155141
d.Set("arn", d.Id())
156142
d.Set("bucket", output.Bucket)
157-
158143
if output.CreationDate != nil {
159144
d.Set("creation_date", aws.TimeValue(output.CreationDate).Format(time.RFC3339))
160145
}
161-
162146
d.Set("outpost_id", arnResourceParts[1])
163147
d.Set("public_access_block_enabled", output.PublicAccessBlockEnabled)
164148

165-
tags, err := bucketListTags(conn, d.Id())
149+
tags, err := bucketListTags(ctx, conn, d.Id())
166150

167151
if err != nil {
168-
return fmt.Errorf("error listing tags for S3 Control Bucket (%s): %w", d.Id(), err)
152+
return diag.Errorf("listing tags for S3 Control Bucket (%s): %s", d.Id(), err)
169153
}
170154

171155
tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig)
172156

173157
//lintignore:AWSR002
174158
if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil {
175-
return fmt.Errorf("error setting tags: %w", err)
159+
return diag.Errorf("setting tags: %s", err)
176160
}
177161

178162
if err := d.Set("tags_all", tags.Map()); err != nil {
179-
return fmt.Errorf("error setting tags_all: %w", err)
163+
return diag.Errorf("setting tags_all: %s", err)
180164
}
181165

182166
return nil
183167
}
184168

185-
func resourceBucketUpdate(d *schema.ResourceData, meta interface{}) error {
169+
func resourceBucketUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
186170
conn := meta.(*conns.AWSClient).S3ControlConn()
187171

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

191-
if err := bucketUpdateTags(conn, d.Id(), o, n); err != nil {
192-
return fmt.Errorf("error updating S3 Control Bucket (%s) tags: %w", d.Id(), err)
175+
if err := bucketUpdateTags(ctx, conn, d.Id(), o, n); err != nil {
176+
return diag.Errorf("updating S3 Control Bucket (%s) tags: %s", d.Id(), err)
193177
}
194178
}
195179

196-
return resourceBucketRead(d, meta)
180+
return resourceBucketRead(ctx, d, meta)
197181
}
198182

199-
func resourceBucketDelete(d *schema.ResourceData, meta interface{}) error {
183+
func resourceBucketDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
200184
conn := meta.(*conns.AWSClient).S3ControlConn()
201185

202186
parsedArn, err := arn.Parse(d.Id())
203187

204188
if err != nil {
205-
return fmt.Errorf("error parsing S3 Control Bucket ARN (%s): %w", d.Id(), err)
189+
return diag.FromErr(err)
206190
}
207191

208192
input := &s3control.DeleteBucketInput{
@@ -213,34 +197,123 @@ func resourceBucketDelete(d *schema.ResourceData, meta interface{}) error {
213197
// S3 Control Bucket have a backend state which cannot be checked so this error
214198
// can occur on deletion:
215199
// InvalidBucketState: Bucket is in an invalid state
216-
err = resource.Retry(bucketStatePropagationTimeout, func() *resource.RetryError {
217-
_, err := conn.DeleteBucket(input)
200+
log.Printf("[DEBUG] Deleting S3 Control Bucket: %s", d.Id())
201+
_, err = tfresource.RetryWhenAWSErrCodeEqualsContext(ctx, bucketStatePropagationTimeout, func() (interface{}, error) {
202+
return conn.DeleteBucketWithContext(ctx, input)
203+
}, errCodeInvalidBucketState)
218204

219-
if tfawserr.ErrCodeEquals(err, "InvalidBucketState") {
220-
return resource.RetryableError(err)
221-
}
205+
if tfawserr.ErrCodeEquals(err, errCodeNoSuchBucket, errCodeNoSuchOutpost) {
206+
return nil
207+
}
222208

223-
if err != nil {
224-
return resource.NonRetryableError(err)
209+
if err != nil {
210+
return diag.Errorf("deleting S3 Control Bucket (%s): %s", d.Id(), err)
211+
}
212+
213+
return nil
214+
}
215+
216+
func FindBucketByTwoPartKey(ctx context.Context, conn *s3control.S3Control, accountID, bucket string) (*s3control.GetBucketOutput, error) {
217+
input := &s3control.GetBucketInput{
218+
AccountId: aws.String(accountID),
219+
Bucket: aws.String(bucket),
220+
}
221+
222+
output, err := conn.GetBucketWithContext(ctx, input)
223+
224+
if tfawserr.ErrCodeEquals(err, errCodeNoSuchBucket, errCodeNoSuchOutpost) {
225+
return nil, &resource.NotFoundError{
226+
LastError: err,
227+
LastRequest: input,
225228
}
229+
}
226230

227-
return nil
228-
})
231+
if err != nil {
232+
return nil, err
233+
}
229234

230-
if tfresource.TimedOut(err) {
231-
_, err = conn.DeleteBucket(input)
235+
if output == nil {
236+
return nil, tfresource.NewEmptyResultError(input)
232237
}
233238

234-
if tfawserr.ErrCodeEquals(err, "NoSuchBucket") {
235-
return nil
239+
return output, nil
240+
}
241+
242+
// Custom S3control tagging functions using similar formatting as other service generated code.
243+
244+
// bucketListTags lists S3control bucket tags.
245+
// The identifier is the bucket ARN.
246+
func bucketListTags(ctx context.Context, conn *s3control.S3Control, identifier string) (tftags.KeyValueTags, error) {
247+
parsedArn, err := arn.Parse(identifier)
248+
249+
if err != nil {
250+
return tftags.New(nil), err
236251
}
237252

238-
if tfawserr.ErrCodeEquals(err, "NoSuchOutpost") {
239-
return nil
253+
input := &s3control.GetBucketTaggingInput{
254+
AccountId: aws.String(parsedArn.AccountID),
255+
Bucket: aws.String(identifier),
256+
}
257+
258+
output, err := conn.GetBucketTaggingWithContext(ctx, input)
259+
260+
if tfawserr.ErrCodeEquals(err, errCodeNoSuchTagSet) {
261+
return tftags.New(nil), nil
262+
}
263+
264+
if err != nil {
265+
return tftags.New(nil), err
266+
}
267+
268+
return KeyValueTags(output.TagSet), nil
269+
}
270+
271+
// bucketUpdateTags updates S3control bucket tags.
272+
// The identifier is the bucket ARN.
273+
func bucketUpdateTags(ctx context.Context, conn *s3control.S3Control, identifier string, oldTagsMap interface{}, newTagsMap interface{}) error {
274+
parsedArn, err := arn.Parse(identifier)
275+
276+
if err != nil {
277+
return err
240278
}
241279

280+
oldTags := tftags.New(oldTagsMap)
281+
newTags := tftags.New(newTagsMap)
282+
283+
// We need to also consider any existing ignored tags.
284+
allTags, err := bucketListTags(ctx, conn, identifier)
285+
242286
if err != nil {
243-
return fmt.Errorf("error deleting S3 Control Bucket (%s): %w", d.Id(), err)
287+
return fmt.Errorf("listing resource tags (%s): %w", identifier, err)
288+
}
289+
290+
ignoredTags := allTags.Ignore(oldTags).Ignore(newTags)
291+
292+
if len(newTags)+len(ignoredTags) > 0 {
293+
input := &s3control.PutBucketTaggingInput{
294+
AccountId: aws.String(parsedArn.AccountID),
295+
Bucket: aws.String(identifier),
296+
Tagging: &s3control.Tagging{
297+
TagSet: Tags(newTags.Merge(ignoredTags)),
298+
},
299+
}
300+
301+
_, err := conn.PutBucketTaggingWithContext(ctx, input)
302+
303+
if err != nil {
304+
return fmt.Errorf("setting resource tags (%s): %s", identifier, err)
305+
}
306+
} else if len(oldTags) > 0 && len(ignoredTags) == 0 {
307+
input := &s3control.DeleteBucketTaggingInput{
308+
AccountId: aws.String(parsedArn.AccountID),
309+
Bucket: aws.String(identifier),
310+
}
311+
312+
_, err := conn.DeleteBucketTaggingWithContext(ctx, input)
313+
314+
if err != nil {
315+
return fmt.Errorf("deleting resource tags (%s): %s", identifier, err)
316+
}
244317
}
245318

246319
return nil

internal/service/s3control/bucket_lifecycle_configuration.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -236,12 +236,11 @@ func resourceBucketLifecycleConfigurationDelete(ctx context.Context, d *schema.R
236236
return diag.Errorf("parsing S3 Control Bucket ARN (%s): unknown format", d.Id())
237237
}
238238

239-
input := &s3control.DeleteBucketLifecycleConfigurationInput{
239+
log.Printf("[DEBUG] Deleting S3 Control Bucket Lifecycle Configuration: %s", d.Id())
240+
_, err = conn.DeleteBucketLifecycleConfigurationWithContext(ctx, &s3control.DeleteBucketLifecycleConfigurationInput{
240241
AccountId: aws.String(parsedArn.AccountID),
241242
Bucket: aws.String(d.Id()),
242-
}
243-
244-
_, err = conn.DeleteBucketLifecycleConfigurationWithContext(ctx, input)
243+
})
245244

246245
if tfawserr.ErrCodeEquals(err, errCodeNoSuchBucket, errCodeNoSuchLifecycleConfiguration, errCodeNoSuchOutpost) {
247246
return nil

0 commit comments

Comments
 (0)