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

add override_provider block to s3 object copy #40689

Merged
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
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
Loading