Skip to content
Closed
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/41917.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
resource/aws_s3_bucket_lifecycle_configuration: remove warning when filter is empty
```
5 changes: 3 additions & 2 deletions internal/service/s3/bucket_lifecycle_configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func (r *resourceBucketLifecycleConfiguration) Schema(ctx context.Context, reque
Computed: true, // Because of Legacy value handling
DeprecationMessage: "Use filter instead",
Validators: []validator.String{
warnExactlyOneOf(
warnAtMostOneOf(
path.MatchRelative().AtParent().AtName(names.AttrFilter),
),
},
Expand Down Expand Up @@ -197,11 +197,12 @@ func (r *resourceBucketLifecycleConfiguration) Schema(ctx context.Context, reque
Optional: true,
Computed: true, // Because of Legacy value handling
Validators: []validator.String{
warnExactlyOneOf(
warnAtMostOneOf(
path.MatchRelative().AtParent().AtName("object_size_greater_than"),
path.MatchRelative().AtParent().AtName("object_size_less_than"),
path.MatchRelative().AtParent().AtName("and"),
path.MatchRelative().AtParent().AtName("tag"),
path.MatchRelative().AtParent().AtName(names.AttrPrefix),
),
},
},
Expand Down
69 changes: 69 additions & 0 deletions internal/service/s3/bucket_lifecycle_configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,53 @@ func TestAccS3BucketLifecycleConfiguration_filterWithEmptyPrefix(t *testing.T) {
})
}

func TestAccS3BucketLifecycleConfiguration_emptyFilter(t *testing.T) {
ctx := acctest.Context(t)
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_s3_bucket_lifecycle_configuration.test"
currTime := time.Now()
date := time.Date(currTime.Year(), currTime.Month()+1, currTime.Day(), 0, 0, 0, 0, time.UTC).Format(time.RFC3339)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, names.S3ServiceID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckBucketLifecycleConfigurationDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccBucketLifecycleConfigurationConfig_emptyFilter(rName, date),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckBucketLifecycleConfigurationExists(ctx, resourceName),
),
ConfigStateChecks: []statecheck.StateCheck{
statecheck.CompareValuePairs(resourceName, tfjsonpath.New(names.AttrBucket), "aws_s3_bucket.test", tfjsonpath.New(names.AttrBucket), compare.ValuesSame()),
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrExpectedBucketOwner), knownvalue.StringExact("")),
statecheck.CompareValuePairs(resourceName, tfjsonpath.New(names.AttrID), resourceName, tfjsonpath.New(names.AttrBucket), compare.ValuesSame()),
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRule), knownvalue.ListExact([]knownvalue.Check{
knownvalue.ObjectExact(map[string]knownvalue.Check{
"abort_incomplete_multipart_upload": checkAbortIncompleteMultipartUpload_None(),
"expiration": checkExpiration_Date(date),
names.AttrFilter: checkFilter_Prefix(""),
names.AttrID: knownvalue.StringExact(rName),
"noncurrent_version_expiration": checkNoncurrentVersionExpiration_None(),
"noncurrent_version_transition": checkNoncurrentVersionTransitions(),
names.AttrPrefix: knownvalue.StringExact(""),
names.AttrStatus: knownvalue.StringExact(tfs3.LifecycleRuleStatusEnabled),
"transition": checkTransitions(),
}),
})),
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("transition_default_minimum_object_size"), knownvalue.StringExact("all_storage_classes_128K")),
},
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccS3BucketLifecycleConfiguration_Filter_ObjectSizeGreaterThan(t *testing.T) {
ctx := acctest.Context(t)
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
Expand Down Expand Up @@ -2476,6 +2523,28 @@ resource "aws_s3_bucket_lifecycle_configuration" "test" {
`, rName, date)
}

func testAccBucketLifecycleConfigurationConfig_emptyFilter(rName, date string) string {
return fmt.Sprintf(`
resource "aws_s3_bucket" "test" {
bucket = %[1]q
}

resource "aws_s3_bucket_lifecycle_configuration" "test" {
bucket = aws_s3_bucket.test.bucket
rule {
id = %[1]q
status = "Enabled"

expiration {
date = %[2]q
}

filter {}
}
}
`, rName, date)
}

func testAccBucketLifecycleConfigurationConfig_rulePrefix(rName, prefix string) string {
return fmt.Sprintf(`
resource "aws_s3_bucket" "test" {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package s3
import (
"context"
"fmt"
"strings"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
Expand All @@ -14,54 +15,54 @@ import (
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
)

// This is a copy of `stringvalidator.ExactlyOneOf` and `schemavalidator.ExactlyOneOfValidator`
// that returns a warning instead of an error.
// This is inspired by `stringvalidator.ExactlyOneOf` and `schemavalidator.ExactlyOneOfValidator`
// It returns a warning instead of an error.
// It could likely be moved to an internal validators package if useful elsewhere.

func warnExactlyOneOf(expressions ...path.Expression) validator.String {
return ExactlyOneOfValidator{
func warnAtMostOneOf(expressions ...path.Expression) validator.String {
return AtMostOneOfValidator{
PathExpressions: expressions,
}
}

type ExactlyOneOfValidator struct {
type AtMostOneOfValidator struct {
PathExpressions path.Expressions
}

type ExactlyOneOfValidatorRequest struct {
type AtMostOneOfValidatorRequest struct {
Config tfsdk.Config
ConfigValue attr.Value
Path path.Path
PathExpression path.Expression
}

type ExactlyOneOfValidatorResponse struct {
type AtMostOneOfValidatorResponse struct {
Diagnostics diag.Diagnostics
}

func (av ExactlyOneOfValidator) Description(ctx context.Context) string {
func (av AtMostOneOfValidator) Description(ctx context.Context) string {
return av.MarkdownDescription(ctx)
}

func (av ExactlyOneOfValidator) MarkdownDescription(_ context.Context) string {
return fmt.Sprintf("Ensure that one and only one attribute from this collection is set: %q", av.PathExpressions)
func (av AtMostOneOfValidator) MarkdownDescription(_ context.Context) string {
return fmt.Sprintf("Ensure that at most one attribute from this collection is set: %q", av.PathExpressions)
}

func (av ExactlyOneOfValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) {
validateReq := ExactlyOneOfValidatorRequest{
func (av AtMostOneOfValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) {
validateReq := AtMostOneOfValidatorRequest{
Config: req.Config,
ConfigValue: req.ConfigValue,
Path: req.Path,
PathExpression: req.PathExpression,
}
validateResp := &ExactlyOneOfValidatorResponse{}
validateResp := &AtMostOneOfValidatorResponse{}

av.Validate(ctx, validateReq, validateResp)

resp.Diagnostics.Append(validateResp.Diagnostics...)
}

func (av ExactlyOneOfValidator) Validate(ctx context.Context, req ExactlyOneOfValidatorRequest, res *ExactlyOneOfValidatorResponse) {
func (av AtMostOneOfValidator) Validate(ctx context.Context, req AtMostOneOfValidatorRequest, res *AtMostOneOfValidatorResponse) {
count := 0
expressions := req.PathExpression.MergeExpressions(av.PathExpressions...)

Expand Down Expand Up @@ -115,17 +116,16 @@ func (av ExactlyOneOfValidator) Validate(ctx context.Context, req ExactlyOneOfVa
}
}

if count == 0 {
res.Diagnostics.Append(warnInvalidAttributeCombinationDiagnostic(
req.Path,
fmt.Sprintf("No attribute specified when one (and only one) of %s is required", expressions),
))
}

if count > 1 {
var attributeNames []string
for _, expr := range expressions {
attributeName, _ := expr.Steps().LastStep()
attributeNames = append(attributeNames, attributeName.String())
}

res.Diagnostics.Append(warnInvalidAttributeCombinationDiagnostic(
req.Path,
fmt.Sprintf("%d attributes specified when one (and only one) of %s is required", count, expressions),
fmt.Sprintf("%d attributes specified when at most one of [%s] is allowed", count, strings.Join(attributeNames, ", ")),
))
}
}
Expand Down