diff --git a/.changelog/45453.txt b/.changelog/45453.txt new file mode 100644 index 00000000000..22b9ec08306 --- /dev/null +++ b/.changelog/45453.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/aws_lakeformation_data_cells_filter: Fix `excluded_column_names` ordering causing "Provider produced inconsistent result after apply" errors +``` diff --git a/internal/service/lakeformation/data_cells_filter.go b/internal/service/lakeformation/data_cells_filter.go index eb06c42bf8b..3e037afc67b 100644 --- a/internal/service/lakeformation/data_cells_filter.go +++ b/internal/service/lakeformation/data_cells_filter.go @@ -23,7 +23,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" - sdkretry "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-provider-aws/internal/create" "github.com/hashicorp/terraform-provider-aws/internal/errs" intflex "github.com/hashicorp/terraform-provider-aws/internal/flex" @@ -115,8 +114,8 @@ func (r *dataCellsFilterResource) Schema(ctx context.Context, _ resource.SchemaR }, NestedObject: schema.NestedBlockObject{ Attributes: map[string]schema.Attribute{ - "excluded_column_names": schema.ListAttribute{ - CustomType: fwtypes.ListOfStringType, + "excluded_column_names": schema.SetAttribute{ + CustomType: fwtypes.SetOfStringType, Optional: true, }, }, @@ -168,9 +167,9 @@ func (r *dataCellsFilterResource) Create(ctx context.Context, req resource.Creat return } - in := &lakeformation.CreateDataCellsFilterInput{} + in := lakeformation.CreateDataCellsFilterInput{} - resp.Diagnostics.Append(fwflex.Expand(ctx, plan, in)...) + resp.Diagnostics.Append(fwflex.Expand(ctx, plan, &in)...) if resp.Diagnostics.HasError() { return @@ -182,7 +181,7 @@ func (r *dataCellsFilterResource) Create(ctx context.Context, req resource.Creat return } - _, err := conn.CreateDataCellsFilter(ctx, in) + _, err := conn.CreateDataCellsFilter(ctx, &in) if err != nil { resp.Diagnostics.AddError( create.ProblemStandardMessage(names.LakeFormation, create.ErrActionCreating, ResNameDataCellsFilter, planTD.Name.String(), err), @@ -281,15 +280,13 @@ func (r *dataCellsFilterResource) Update(ctx context.Context, req resource.Updat } if !plan.TableData.Equal(state.TableData) { - in := &lakeformation.UpdateDataCellsFilterInput{} - - resp.Diagnostics.Append(fwflex.Expand(ctx, plan, in)...) - + in := lakeformation.UpdateDataCellsFilterInput{} + resp.Diagnostics.Append(fwflex.Expand(ctx, plan, &in)...) if resp.Diagnostics.HasError() { return } - _, err := conn.UpdateDataCellsFilter(ctx, in) + _, err := conn.UpdateDataCellsFilter(ctx, &in) if err != nil { resp.Diagnostics.AddError( create.ProblemStandardMessage(names.LakeFormation, create.ErrActionUpdating, ResNameDataCellsFilter, plan.ID.String(), err), @@ -299,7 +296,6 @@ func (r *dataCellsFilterResource) Update(ctx context.Context, req resource.Updat } output, err := findDataCellsFilterByID(ctx, conn, state.ID.ValueString()) - if err != nil { resp.Diagnostics.AddError( create.ProblemStandardMessage(names.LakeFormation, create.ErrActionUpdating, ResNameDataCellsFilter, plan.ID.String(), err), @@ -310,7 +306,6 @@ func (r *dataCellsFilterResource) Update(ctx context.Context, req resource.Updat td := tableData{} resp.Diagnostics.Append(fwflex.Flatten(ctx, output, &td)...) - if resp.Diagnostics.HasError() { return } @@ -398,9 +393,8 @@ func findDataCellsFilterByID(ctx context.Context, conn *lakeformation.Client, id out, err := conn.GetDataCellsFilter(ctx, in) if errs.IsA[*awstypes.EntityNotFoundException](err) { - return nil, &sdkretry.NotFoundError{ - LastError: err, - LastRequest: in, + return nil, &retry.NotFoundError{ + LastError: err, } } @@ -427,14 +421,14 @@ type tableData struct { Name types.String `tfsdk:"name"` TableCatalogID types.String `tfsdk:"table_catalog_id"` TableName types.String `tfsdk:"table_name"` - ColumnNames fwtypes.SetValueOf[types.String] `tfsdk:"column_names"` + ColumnNames fwtypes.SetOfString `tfsdk:"column_names"` ColumnWildcard fwtypes.ListNestedObjectValueOf[columnWildcard] `tfsdk:"column_wildcard"` RowFilter fwtypes.ListNestedObjectValueOf[rowFilter] `tfsdk:"row_filter"` VersionID types.String `tfsdk:"version_id"` } type columnWildcard struct { - ExcludedColumnNames fwtypes.ListValueOf[types.String] `tfsdk:"excluded_column_names"` + ExcludedColumnNames fwtypes.SetOfString `tfsdk:"excluded_column_names"` } type rowFilter struct { diff --git a/internal/service/lakeformation/data_cells_filter_test.go b/internal/service/lakeformation/data_cells_filter_test.go index 1aba2f73b4f..337ae57208d 100644 --- a/internal/service/lakeformation/data_cells_filter_test.go +++ b/internal/service/lakeformation/data_cells_filter_test.go @@ -85,13 +85,49 @@ func testAccDataCellsFilter_columnWildcard(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "table_data.0.table_name", rName), resource.TestCheckResourceAttrSet(resourceName, "table_data.0.version_id"), resource.TestCheckResourceAttr(resourceName, "table_data.0.column_wildcard.0.excluded_column_names.#", "1"), - resource.TestCheckResourceAttr(resourceName, "table_data.0.column_wildcard.0.excluded_column_names.0", "my_column_12"), + resource.TestCheckTypeSetElemAttr(resourceName, "table_data.0.column_wildcard.0.excluded_column_names.*", "my_column_12"), ), }, }, }) } +func testAccDataCellsFilter_columnWildcardMultiple(t *testing.T) { + ctx := acctest.Context(t) + + var datacellsfilter awstypes.DataCellsFilter + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_lakeformation_data_cells_filter.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.LakeFormationEndpointID) + testAccDataCellsFilterPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.LakeFormationServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckDataCellsFilterDestroy(ctx), + Steps: []resource.TestStep{ + { + // Test with columns in non-alphabetical order to verify no ordering issues + Config: testAccDataCellsFilterConfig_columnWildcardMultiple(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckDataCellsFilterExists(ctx, resourceName, &datacellsfilter), + resource.TestCheckResourceAttr(resourceName, "table_data.0.column_wildcard.0.excluded_column_names.#", "2"), + resource.TestCheckTypeSetElemAttr(resourceName, "table_data.0.column_wildcard.0.excluded_column_names.*", "my_column_12"), + resource.TestCheckTypeSetElemAttr(resourceName, "table_data.0.column_wildcard.0.excluded_column_names.*", "my_column_22"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccDataCellsFilter_disappears(t *testing.T) { ctx := acctest.Context(t) @@ -344,6 +380,32 @@ resource "aws_lakeformation_data_cells_filter" "test" { `, rName, column)) } +func testAccDataCellsFilterConfig_columnWildcardMultiple(rName string) string { + return acctest.ConfigCompose( + testAccDataCellsFilterConfigBase(rName), + fmt.Sprintf(` +resource "aws_lakeformation_data_cells_filter" "test" { + table_data { + database_name = aws_glue_catalog_database.test.name + name = %[1]q + table_catalog_id = data.aws_caller_identity.current.account_id + table_name = aws_glue_catalog_table.test.name + + column_wildcard { + # Columns specified in non-alphabetical order to test ordering fix + excluded_column_names = ["my_column_22", "my_column_12"] + } + + row_filter { + filter_expression = "my_column_23='testing'" + } + } + + depends_on = [aws_lakeformation_data_lake_settings.test] +} +`, rName)) +} + func testAccDataCellsFilterConfig_rowFilter(rName, rowFilter string) string { return acctest.ConfigCompose( testAccDataCellsFilterConfigBase(rName), diff --git a/internal/service/lakeformation/lakeformation_test.go b/internal/service/lakeformation/lakeformation_test.go index e3bbf01dcfc..eadf13ea8ed 100644 --- a/internal/service/lakeformation/lakeformation_test.go +++ b/internal/service/lakeformation/lakeformation_test.go @@ -21,10 +21,11 @@ func TestAccLakeFormation_serial(t *testing.T) { "parameters": testAccDataLakeSettings_parameters, }, "DataCellsFilter": { - acctest.CtBasic: testAccDataCellsFilter_basic, - "columnWildcard": testAccDataCellsFilter_columnWildcard, - acctest.CtDisappears: testAccDataCellsFilter_disappears, - "rowFilter": testAccDataCellsFilter_rowFilter, + acctest.CtBasic: testAccDataCellsFilter_basic, + "columnWildcard": testAccDataCellsFilter_columnWildcard, + "columnWildcardMultiple": testAccDataCellsFilter_columnWildcardMultiple, + acctest.CtDisappears: testAccDataCellsFilter_disappears, + "rowFilter": testAccDataCellsFilter_rowFilter, }, "DataLakeSettingsDataSource": { acctest.CtBasic: testAccDataLakeSettingsDataSource_basic,