Skip to content

Commit

Permalink
Merge pull request #40689 from jafournier/b-s3-object-copy-ignore-def…
Browse files Browse the repository at this point in the history
…ault-tags

add override_provider  block to s3 object copy
  • Loading branch information
ewbankkit authored Jan 7, 2025
2 parents a8b6a52 + 29fa802 commit 598c2c4
Show file tree
Hide file tree
Showing 4 changed files with 298 additions and 4 deletions.
3 changes: 3 additions & 0 deletions .changelog/40689.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_s3_object_copy: Add `override_provider` configuration block, allowing tags inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) to be ignored
```
42 changes: 38 additions & 4 deletions internal/service/s3/object_copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ func resourceObjectCopy() *schema.Resource {
UpdateWithoutTimeout: resourceObjectCopyUpdate,
DeleteWithoutTimeout: resourceObjectCopyDelete,

CustomizeDiff: func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error {
if ignoreProviderDefaultTags(ctx, d) {
return d.SetNew(names.AttrTagsAll, d.Get(names.AttrTags))
}
return verify.SetTagsDiff(ctx, d, meta)
},

Schema: map[string]*schema.Schema{
"acl": {
Type: schema.TypeString,
Expand Down Expand Up @@ -263,6 +270,30 @@ func resourceObjectCopy() *schema.Resource {
Computed: true,
ValidateFunc: validation.IsRFC3339Time,
},
"override_provider": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"default_tags": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
names.AttrTags: {
Type: schema.TypeMap,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
ValidateDiagFunc: verify.MapSizeBetween(0, 0),
},
},
},
},
},
},
},
"request_charged": {
Type: schema.TypeBool,
Computed: true,
Expand Down Expand Up @@ -324,8 +355,6 @@ func resourceObjectCopy() *schema.Resource {
Computed: true,
},
},

CustomizeDiff: verify.SetTagsDiff,
}
}

Expand Down Expand Up @@ -649,8 +678,13 @@ func resourceObjectCopyDoCopy(ctx context.Context, d *schema.ResourceData, meta
}

defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig(ctx)
tags := tftags.New(ctx, getContextTags(ctx))
tags = defaultTagsConfig.MergeTags(tags)
tags := tftags.New(ctx, d.Get(names.AttrTags).(map[string]interface{}))
if ignoreProviderDefaultTags(ctx, d) {
tags = tags.RemoveDefaultConfig(defaultTagsConfig)
} else {
tags = defaultTagsConfig.MergeTags(tftags.New(ctx, tags))
}

if len(tags) > 0 {
// The tag-set must be encoded as URL Query parameters.
input.Tagging = aws.String(tags.IgnoreAWS().URLEncode())
Expand Down
233 changes: 233 additions & 0 deletions internal/service/s3/object_copy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,143 @@ func TestAccS3ObjectCopy_BucketKeyEnabled_object(t *testing.T) {
})
}

func TestAccS3ObjectCopy_DefaultTags_providerOnly(t *testing.T) {
ctx := acctest.Context(t)
rName1 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_s3_object_copy.test"
sourceKey := "dir1/dir2/source"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, names.S3ServiceID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckObjectCopyDestroy(ctx),
Steps: []resource.TestStep{
{
Config: acctest.ConfigCompose(
acctest.ConfigDefaultTags_Tags1(acctest.CtProviderKey1, acctest.CtProviderValue1),
testAccObjectCopyConfig_simple(rName1, sourceKey, rName2, names.AttrTarget),
),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckObjectCopyExists(ctx, resourceName),
resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "0"),
resource.TestCheckResourceAttr(resourceName, acctest.CtTagsAllPercent, "1"),
resource.TestCheckResourceAttr(resourceName, "tags_all.providerkey1", acctest.CtProviderValue1),
),
},
},
})
}

func TestAccS3ObjectCopy_DefaultTags_providerAndResource(t *testing.T) {
ctx := acctest.Context(t)
rName1 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_s3_object_copy.test"
sourceKey := "dir1/dir2/source"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, names.S3ServiceID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckObjectCopyDestroy(ctx),
Steps: []resource.TestStep{
{
Config: acctest.ConfigCompose(
acctest.ConfigDefaultTags_Tags1(acctest.CtProviderKey1, acctest.CtProviderValue1),
testAccObjectCopyConfig_tags(rName1, sourceKey, rName2, names.AttrTarget),
),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckObjectCopyExists(ctx, resourceName),
resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "3"),
resource.TestCheckResourceAttr(resourceName, "tags.Key1", "A@AA"),
resource.TestCheckResourceAttr(resourceName, "tags.Key2", "BBB"),
resource.TestCheckResourceAttr(resourceName, "tags.Key3", "CCC"),
resource.TestCheckResourceAttr(resourceName, acctest.CtTagsAllPercent, "4"),
resource.TestCheckResourceAttr(resourceName, "tags_all.providerkey1", acctest.CtProviderValue1),
resource.TestCheckResourceAttr(resourceName, "tags_all.Key1", "A@AA"),
resource.TestCheckResourceAttr(resourceName, "tags_all.Key2", "BBB"),
resource.TestCheckResourceAttr(resourceName, "tags_all.Key3", "CCC"),
),
},
{
Config: acctest.ConfigCompose(
acctest.ConfigDefaultTags_Tags1(acctest.CtProviderKey1, acctest.CtProviderValue1),
testAccObjectCopyConfig_updatedTags(rName1, sourceKey, rName2, names.AttrTarget),
),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckObjectCopyExists(ctx, resourceName),
resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "4"),
resource.TestCheckResourceAttr(resourceName, "tags.Key2", "B@BB"),
resource.TestCheckResourceAttr(resourceName, "tags.Key3", "X X"),
resource.TestCheckResourceAttr(resourceName, "tags.Key4", "DDD"),
resource.TestCheckResourceAttr(resourceName, "tags.Key5", "E:/"),
resource.TestCheckResourceAttr(resourceName, acctest.CtTagsAllPercent, "5"),
resource.TestCheckResourceAttr(resourceName, "tags_all.providerkey1", acctest.CtProviderValue1),
resource.TestCheckResourceAttr(resourceName, "tags_all.Key2", "B@BB"),
resource.TestCheckResourceAttr(resourceName, "tags_all.Key3", "X X"),
resource.TestCheckResourceAttr(resourceName, "tags_all.Key4", "DDD"),
resource.TestCheckResourceAttr(resourceName, "tags_all.Key5", "E:/"),
),
},
},
})
}

func TestAccS3ObjectCopy_DefaultTags_providerAndResourceWithOverride(t *testing.T) {
ctx := acctest.Context(t)
rName1 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_s3_object_copy.test"
sourceKey := "dir1/dir2/source"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, names.S3ServiceID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckObjectCopyDestroy(ctx),
Steps: []resource.TestStep{
{
Config: acctest.ConfigCompose(
acctest.ConfigDefaultTags_Tags1(acctest.CtProviderKey1, acctest.CtProviderValue1),
testAccObjectCopyConfig_tagsWithOverride(rName1, sourceKey, rName2, names.AttrTarget),
),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckObjectCopyExists(ctx, resourceName),
resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "3"),
resource.TestCheckResourceAttr(resourceName, "tags.Key1", "A@AA"),
resource.TestCheckResourceAttr(resourceName, "tags.Key2", "BBB"),
resource.TestCheckResourceAttr(resourceName, "tags.Key3", "CCC"),
resource.TestCheckResourceAttr(resourceName, acctest.CtTagsAllPercent, "3"),
resource.TestCheckResourceAttr(resourceName, "tags_all.Key1", "A@AA"),
resource.TestCheckResourceAttr(resourceName, "tags_all.Key2", "BBB"),
resource.TestCheckResourceAttr(resourceName, "tags_all.Key3", "CCC"),
),
},
{
Config: acctest.ConfigCompose(
acctest.ConfigDefaultTags_Tags1(acctest.CtProviderKey1, acctest.CtProviderValue1),
testAccObjectCopyConfig_updatedTagsOverride(rName1, sourceKey, rName2, names.AttrTarget),
),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckObjectCopyExists(ctx, resourceName),
resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "4"),
resource.TestCheckResourceAttr(resourceName, "tags.Key2", "B@BB"),
resource.TestCheckResourceAttr(resourceName, "tags.Key3", "X X"),
resource.TestCheckResourceAttr(resourceName, "tags.Key4", "DDD"),
resource.TestCheckResourceAttr(resourceName, "tags.Key5", "E:/"),
resource.TestCheckResourceAttr(resourceName, acctest.CtTagsAllPercent, "4"),
resource.TestCheckResourceAttr(resourceName, "tags.Key2", "B@BB"),
resource.TestCheckResourceAttr(resourceName, "tags.Key3", "X X"),
resource.TestCheckResourceAttr(resourceName, "tags.Key4", "DDD"),
resource.TestCheckResourceAttr(resourceName, "tags.Key5", "E:/"),
),
},
},
})
}

func TestAccS3ObjectCopy_sourceWithSlashes(t *testing.T) {
ctx := acctest.Context(t)
rName1 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
Expand Down Expand Up @@ -709,6 +846,102 @@ resource "aws_s3_object_copy" "test" {
`, targetKey))
}

func testAccObjectCopyConfig_tags(sourceBucket, sourceKey, targetBucket, targetKey string) string {
return acctest.ConfigCompose(testAccObjectCopyConfig_baseSourceObject(sourceBucket, sourceKey, targetBucket), fmt.Sprintf(`
resource "aws_s3_object_copy" "test" {
bucket = aws_s3_bucket.target.bucket
key = %[1]q
source = "${aws_s3_bucket.source.bucket}/${aws_s3_object.source.key}"
tags = {
Key1 = "A@AA"
Key2 = "BBB"
Key3 = "CCC"
}
tagging_directive = "REPLACE"
}
`, targetKey))
}

func testAccObjectCopyConfig_tagsWithOverride(sourceBucket, sourceKey, targetBucket, targetKey string) string {
return acctest.ConfigCompose(testAccObjectCopyConfig_baseSourceObject(sourceBucket, sourceKey, targetBucket), fmt.Sprintf(`
resource "aws_s3_object_copy" "test" {
bucket = aws_s3_bucket.target.bucket
key = %[1]q
source = "${aws_s3_bucket.source.bucket}/${aws_s3_object.source.key}"
tags = {
Key1 = "A@AA"
Key2 = "BBB"
Key3 = "CCC"
}
tagging_directive = "REPLACE"
override_provider {
default_tags {
tags = {}
}
}
}
`, targetKey))
}

func testAccObjectCopyConfig_updatedTags(sourceBucket, sourceKey, targetBucket, targetKey string) string {
return acctest.ConfigCompose(testAccObjectCopyConfig_baseSourceObject(sourceBucket, sourceKey, targetBucket), fmt.Sprintf(`
resource "aws_s3_object_copy" "test" {
bucket = aws_s3_bucket.target.bucket
key = %[1]q
source = "${aws_s3_bucket.source.bucket}/${aws_s3_object.source.key}"
tags = {
Key2 = "B@BB"
Key3 = "X X"
Key4 = "DDD"
Key5 = "E:/"
}
tagging_directive = "REPLACE"
}
`, targetKey))
}

func testAccObjectCopyConfig_updatedTagsOverride(sourceBucket, sourceKey, targetBucket, targetKey string) string {
return acctest.ConfigCompose(testAccObjectCopyConfig_baseSourceObject(sourceBucket, sourceKey, targetBucket), fmt.Sprintf(`
resource "aws_s3_object_copy" "test" {
bucket = aws_s3_bucket.target.bucket
key = %[1]q
source = "${aws_s3_bucket.source.bucket}/${aws_s3_object.source.key}"
tags = {
Key2 = "B@BB"
Key3 = "X X"
Key4 = "DDD"
Key5 = "E:/"
}
tagging_directive = "REPLACE"
override_provider {
default_tags {
tags = {}
}
}
}
`, targetKey))
}

func testAccObjectCopyConfig_simple(sourceBucket, sourceKey, targetBucket, targetKey string) string {
return acctest.ConfigCompose(testAccObjectCopyConfig_baseSourceObject(sourceBucket, sourceKey, targetBucket), fmt.Sprintf(`
resource "aws_s3_object_copy" "test" {
bucket = aws_s3_bucket.target.bucket
key = %[1]q
source = "${aws_s3_bucket.source.bucket}/${aws_s3_object.source.key}"
}
`, targetKey))
}

func testAccObjectCopyConfig_externalSourceObject(sourceBucket, sourceKey, targetBucket, targetKey string) string {
return acctest.ConfigCompose(testAccObjectCopyConfig_baseSourceAndTargetBuckets(sourceBucket, targetBucket), fmt.Sprintf(`
resource "aws_s3_object_copy" "test" {
Expand Down
24 changes: 24 additions & 0 deletions website/docs/r/s3_object_copy.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,24 @@ resource "aws_s3_object_copy" "test" {
}
```

### Ignoring Provider `default_tags`

S3 objects support a [maximum of 10 tags](https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-tagging.html).
If the resource's own `tags` and the provider-level `default_tags` would together lead to more than 10 tags on an S3 object copy, use the `override_provider` configuration block to suppress any provider-level `default_tags`.

```terraform
resource "aws_s3_object_copy" "test" {
bucket = "destination_bucket"
key = "destination_key"
source = "source_bucket/source_key"
override_provider {
default_tags {
tags = {}
}
}
}
```

## Argument Reference

The following arguments are required:
Expand Down Expand Up @@ -89,6 +107,12 @@ This configuration block has the following optional arguments (one of the three

-> **Note:** Terraform ignores all leading `/`s in the object's `key` and treats multiple `/`s in the rest of the object's `key` as a single `/`, so values of `/index.html` and `index.html` correspond to the same S3 object as do `first//second///third//` and `first/second/third/`.

### Override Provider

The `override_provider` block supports the following:

* `default_tags` - (Optional) Override the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block).

## Attribute Reference

This resource exports the following attributes in addition to the arguments above:
Expand Down

0 comments on commit 598c2c4

Please sign in to comment.