diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b712b1b0..d956ace9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -50,7 +50,7 @@ jobs: name: 'Acc. Tests (OS: ${{ matrix.os }} / TF: ${{ matrix.terraform }})' needs: build runs-on: ${{ matrix.os }} - timeout-minutes: 15 + timeout-minutes: 20 strategy: fail-fast: false matrix: diff --git a/CHANGELOG.md b/CHANGELOG.md index e407c882..9b5b101d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ BUG FIXES: +* all: Prevent `keeper` with `null` values from forcing replacement ([305](https://github.com/hashicorp/terraform-provider-random/pull/305)). * resource/random_password: During upgrade state, ensure `min_upper` is populated ([304](https://github.com/hashicorp/terraform-provider-random/pull/304)). * resource/random_string: During upgrade state, ensure `min_upper` is populated ([304](https://github.com/hashicorp/terraform-provider-random/pull/304)). diff --git a/internal/planmodifiers/attribute.go b/internal/planmodifiers/attribute.go index 9464b1e8..4f3291f1 100644 --- a/internal/planmodifiers/attribute.go +++ b/internal/planmodifiers/attribute.go @@ -143,3 +143,121 @@ func (d *numberNumericAttributePlanModifier) Modify(ctx context.Context, req tfs return } } + +func RequiresReplaceIfValuesNotNull() tfsdk.AttributePlanModifier { + return requiresReplaceIfValuesNotNullModifier{} +} + +type requiresReplaceIfValuesNotNullModifier struct{} + +func (r requiresReplaceIfValuesNotNullModifier) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { + if req.AttributeConfig == nil || req.AttributePlan == nil || req.AttributeState == nil { + // shouldn't happen, but let's not panic if it does + return + } + + if req.State.Raw.IsNull() { + // if we're creating the resource, no need to delete and + // recreate it + return + } + + if req.Plan.Raw.IsNull() { + // if we're deleting the resource, no need to delete and + // recreate it + return + } + + // If there are no differences, do not mark the resource for replacement + // and ensure the plan matches the configuration. + if req.AttributeConfig.Equal(req.AttributeState) { + return + } + + if req.AttributeState.IsNull() { + // terraform-plugin-sdk would store maps as null if all keys had null + // values. To prevent unintentional replacement plans when migrating + // to terraform-plugin-framework, only trigger replacement when the + // prior state (map) is null and when there are not null map values. + allNullValues := true + + configMap, ok := req.AttributeConfig.(types.Map) + + if !ok { + return + } + + for _, configValue := range configMap.Elems { + if !configValue.IsNull() { + allNullValues = false + } + } + + if allNullValues { + return + } + } else { + // terraform-plugin-sdk would completely omit storing map keys with + // null values, so this also must prevent unintentional replacement + // in that case as well. + allNewNullValues := true + + configMap, ok := req.AttributeConfig.(types.Map) + + if !ok { + return + } + + stateMap, ok := req.AttributeState.(types.Map) + + if !ok { + return + } + + for configKey, configValue := range configMap.Elems { + stateValue, ok := stateMap.Elems[configKey] + + // If the key doesn't exist in state and the config value is + // null, do not trigger replacement. + if !ok && configValue.IsNull() { + continue + } + + // If the state value exists and it is equal to the config value, + // do not trigger replacement. + if configValue.Equal(stateValue) { + continue + } + + allNewNullValues = false + break + } + + for stateKey := range stateMap.Elems { + _, ok := configMap.Elems[stateKey] + + // If the key doesn't exist in the config, but there is a state + // value, trigger replacement. + if !ok { + allNewNullValues = false + break + } + } + + if allNewNullValues { + return + } + } + + resp.RequiresReplace = true +} + +// Description returns a human-readable description of the plan modifier. +func (r requiresReplaceIfValuesNotNullModifier) Description(ctx context.Context) string { + return "If the value of this attribute changes, Terraform will destroy and recreate the resource." +} + +// MarkdownDescription returns a markdown description of the plan modifier. +func (r requiresReplaceIfValuesNotNullModifier) MarkdownDescription(ctx context.Context) string { + return "If the value of this attribute changes, Terraform will destroy and recreate the resource." +} diff --git a/internal/provider/resource_id.go b/internal/provider/resource_id.go index 95629bd2..4145fa55 100644 --- a/internal/provider/resource_id.go +++ b/internal/provider/resource_id.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/terraform-providers/terraform-provider-random/internal/diagnostics" + "github.com/terraform-providers/terraform-provider-random/internal/planmodifiers" ) var _ provider.ResourceType = (*idResourceType)(nil) @@ -47,7 +48,7 @@ exist concurrently. }, Optional: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - resource.RequiresReplace(), + planmodifiers.RequiresReplaceIfValuesNotNull(), }, }, "byte_length": { @@ -73,27 +74,42 @@ exist concurrently. "case-sensitive letters, digits and the characters `_` and `-`.", Type: types.StringType, Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + resource.UseStateForUnknown(), + }, }, "b64_std": { Description: "The generated id presented in base64 without additional transformations.", Type: types.StringType, Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + resource.UseStateForUnknown(), + }, }, "hex": { Description: "The generated id presented in padded hexadecimal digits. This result will " + "always be twice as long as the requested byte length.", Type: types.StringType, Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + resource.UseStateForUnknown(), + }, }, "dec": { Description: "The generated id presented in non-padded decimal digits.", Type: types.StringType, Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + resource.UseStateForUnknown(), + }, }, "id": { Description: "The generated id presented in base64 without additional transformations or prefix.", Type: types.StringType, Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + resource.UseStateForUnknown(), + }, }, }, }, nil @@ -163,9 +179,17 @@ func (r *idResource) Create(ctx context.Context, req resource.CreateRequest, res func (r *idResource) Read(context.Context, resource.ReadRequest, *resource.ReadResponse) { } -// Update is intentionally left blank as all required and optional attributes force replacement of the resource -// through the RequiresReplace AttributePlanModifier. -func (r *idResource) Update(context.Context, resource.UpdateRequest, *resource.UpdateResponse) { +// Update ensures the plan value is copied to the state to complete the update. +func (r *idResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var model idModelV0 + + resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &model)...) } // Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the diff --git a/internal/provider/resource_id_test.go b/internal/provider/resource_id_test.go index 80dec3df..46735ef0 100644 --- a/internal/provider/resource_id_test.go +++ b/internal/provider/resource_id_test.go @@ -96,3 +96,746 @@ func TestAccResourceID_UpgradeFromVersion3_3_2(t *testing.T) { }, }) } + +func TestAccResourceID_Keepers_Keep_EmptyMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id1), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceID_Keepers_Keep_EmptyMapToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id1), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceID_Keepers_Keep_NullMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id1), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceID_Keepers_Keep_NullMapToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id1), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceID_Keepers_Keep_NullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id1), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceID_Keepers_Keep_NullValues(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id1), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "2"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceID_Keepers_Keep_Value(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id1), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceID_Keepers_Keep_Values(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id1), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "2"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceID_Keepers_Replace_EmptyMapToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id1), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceID_Keepers_Replace_NullMapToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id1), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceID_Keepers_Replace_NullValueToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id1), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceID_Keepers_Replace_ValueToEmptyMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id1), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceID_Keepers_Replace_ValueToNullMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id1), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceID_Keepers_Replace_ValueToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id1), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceID_Keepers_Replace_ValueToNewValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id1), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceID_Keepers_FrameworkMigration_NullMapToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id1), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceID_Keepers_FrameworkMigration_NullMapToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id1), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceID_Keepers_FrameworkMigration_NullMapToMultipleNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id1), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceID_Keepers_FrameworkMigration_NullMapToMultipleValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id1), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceID_Keepers_FrameworkMigration_NullMapValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key1" = "123" + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id1), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key1" = "123" + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceID_Keepers_FrameworkMigration_NullMapValueToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key1" = "123" + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id1), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_id" "test" { + byte_length = 4 + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_id.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_id.test", "keepers.%", "2"), + ), + }, + }, + }) +} diff --git a/internal/provider/resource_integer.go b/internal/provider/resource_integer.go index c1e50b56..9c974ac8 100644 --- a/internal/provider/resource_integer.go +++ b/internal/provider/resource_integer.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/terraform-providers/terraform-provider-random/internal/planmodifiers" "github.com/terraform-providers/terraform-provider-random/internal/random" ) @@ -34,8 +35,10 @@ func (r *integerResourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Dia Type: types.MapType{ ElemType: types.StringType, }, - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{resource.RequiresReplace()}, + Optional: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + planmodifiers.RequiresReplaceIfValuesNotNull(), + }, }, "min": { Description: "The minimum inclusive value of the range.", @@ -59,11 +62,17 @@ func (r *integerResourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Dia Description: "The random integer result.", Type: types.Int64Type, Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + resource.UseStateForUnknown(), + }, }, "id": { Description: "The string representation of the integer result.", Type: types.StringType, Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + resource.UseStateForUnknown(), + }, }, }, }, nil @@ -129,9 +138,17 @@ func (r *integerResource) Create(ctx context.Context, req resource.CreateRequest func (r *integerResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { } -// Update is intentionally left blank as all required and optional attributes force replacement of the resource -// through the RequiresReplace AttributePlanModifier. +// Update ensures the plan value is copied to the state to complete the update. func (r *integerResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var model integerModelV0 + + resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &model)...) } // Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the diff --git a/internal/provider/resource_integer_test.go b/internal/provider/resource_integer_test.go index b8a59ddc..4600c91d 100644 --- a/internal/provider/resource_integer_test.go +++ b/internal/provider/resource_integer_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) func TestAccResourceInteger(t *testing.T) { @@ -177,6 +178,791 @@ func TestAccResourceInteger_UpgradeFromVersion3_3_2(t *testing.T) { }) } +func TestAccResourceInteger_Keepers_Keep_EmptyMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id1), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceInteger_Keepers_Keep_EmptyMapToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id1), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceInteger_Keepers_Keep_NullMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id1), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceInteger_Keepers_Keep_NullMapToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id1), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceInteger_Keepers_Keep_NullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id1), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceInteger_Keepers_Keep_NullValues(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id1), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "2"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceInteger_Keepers_Keep_Value(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id1), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceInteger_Keepers_Keep_Values(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id1), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "2"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceInteger_Keepers_Replace_EmptyMapToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id1), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceInteger_Keepers_Replace_NullMapToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id1), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceInteger_Keepers_Replace_NullValueToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id1), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceInteger_Keepers_Replace_ValueToEmptyMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id1), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceInteger_Keepers_Replace_ValueToNullMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id1), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceInteger_Keepers_Replace_ValueToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id1), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceInteger_Keepers_Replace_ValueToNewValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id1), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceInteger_Keepers_FrameworkMigration_NullMapToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id1), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceInteger_Keepers_FrameworkMigration_NullMapToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id1), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceInteger_Keepers_FrameworkMigration_NullMapToMultipleNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id1), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceInteger_Keepers_FrameworkMigration_NullMapToMultipleValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id1), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceInteger_Keepers_FrameworkMigration_NullMapValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key1" = "123" + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id1), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key1" = "123" + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceInteger_Keepers_FrameworkMigration_NullMapValueToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key1" = "123" + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id1), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_integer" "test" { + min = 1 + max = 100000000 + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_integer.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_integer.test", "keepers.%", "2"), + ), + }, + }, + }) +} + func testCheckNotEmptyString(field string) func(input string) error { return func(input string) error { if input == "" { @@ -186,3 +972,51 @@ func testCheckNotEmptyString(field string) func(input string) error { return nil } } + +func testExtractResourceAttr(resourceName string, attributeName string, attributeValue *string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + + if !ok { + return fmt.Errorf("resource name %s not found in state", resourceName) + } + + attrValue, ok := rs.Primary.Attributes[attributeName] + + if !ok { + return fmt.Errorf("attribute %s not found in resource %s state", attributeName, resourceName) + } + + *attributeValue = attrValue + + return nil + } +} + +func testCheckAttributeValuesDiffer(i *string, j *string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if testStringValue(i) == testStringValue(j) { + return fmt.Errorf("attribute values are the same") + } + + return nil + } +} + +func testCheckAttributeValuesEqual(i *string, j *string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if testStringValue(i) != testStringValue(j) { + return fmt.Errorf("attribute values are different, got %s and %s", testStringValue(i), testStringValue(j)) + } + + return nil + } +} + +func testStringValue(sPtr *string) string { + if sPtr == nil { + return "" + } + + return *sPtr +} diff --git a/internal/provider/resource_password.go b/internal/provider/resource_password.go index 2d205ae9..4d4abb3a 100644 --- a/internal/provider/resource_password.go +++ b/internal/provider/resource_password.go @@ -100,9 +100,17 @@ func (r *passwordResource) Create(ctx context.Context, req resource.CreateReques func (r *passwordResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { } -// Update is intentionally left blank as all required and optional attributes force replacement of the resource -// through the RequiresReplace AttributePlanModifier. +// Update ensures the plan value is copied to the state to complete the update. func (r *passwordResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var model passwordModelV2 + + resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &model)...) } // Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the @@ -283,7 +291,7 @@ func passwordSchemaV2() tfsdk.Schema { }, Optional: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - resource.RequiresReplace(), + planmodifiers.RequiresReplaceIfValuesNotNull(), }, }, @@ -415,6 +423,7 @@ func passwordSchemaV2() tfsdk.Schema { Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ + planmodifiers.DefaultValue(types.String{Value: ""}), resource.RequiresReplace(), }, }, @@ -424,6 +433,9 @@ func passwordSchemaV2() tfsdk.Schema { Type: types.StringType, Computed: true, Sensitive: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + resource.UseStateForUnknown(), + }, }, "bcrypt_hash": { @@ -431,12 +443,18 @@ func passwordSchemaV2() tfsdk.Schema { Type: types.StringType, Computed: true, Sensitive: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + resource.UseStateForUnknown(), + }, }, "id": { Description: "A static value used internally by Terraform, this should not be referenced in configurations.", Computed: true, Type: types.StringType, + PlanModifiers: []tfsdk.AttributePlanModifier{ + resource.UseStateForUnknown(), + }, }, }, } diff --git a/internal/provider/resource_password_test.go b/internal/provider/resource_password_test.go index 2445c963..62636d06 100644 --- a/internal/provider/resource_password_test.go +++ b/internal/provider/resource_password_test.go @@ -882,3 +882,746 @@ func TestAccResourcePassword_NumberNumericErrors(t *testing.T) { }, }) } + +func TestAccResourcePassword_Keepers_Keep_EmptyMap(t *testing.T) { + var result1, result2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result1), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result2), + testCheckAttributeValuesEqual(&result1, &result2), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourcePassword_Keepers_Keep_EmptyMapToNullValue(t *testing.T) { + var result1, result2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result1), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result2), + testCheckAttributeValuesEqual(&result1, &result2), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourcePassword_Keepers_Keep_NullMap(t *testing.T) { + var result1, result2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result1), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result2), + testCheckAttributeValuesEqual(&result1, &result2), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourcePassword_Keepers_Keep_NullMapToNullValue(t *testing.T) { + var result1, result2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result1), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result2), + testCheckAttributeValuesEqual(&result1, &result2), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourcePassword_Keepers_Keep_NullValue(t *testing.T) { + var result1, result2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result1), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result2), + testCheckAttributeValuesEqual(&result1, &result2), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourcePassword_Keepers_Keep_NullValues(t *testing.T) { + var result1, result2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result1), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "2"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result2), + testCheckAttributeValuesEqual(&result1, &result2), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourcePassword_Keepers_Keep_Value(t *testing.T) { + var result1, result2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result1), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result2), + testCheckAttributeValuesEqual(&result1, &result2), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourcePassword_Keepers_Keep_Values(t *testing.T) { + var result1, result2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result1), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "2"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result2), + testCheckAttributeValuesEqual(&result1, &result2), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourcePassword_Keepers_Replace_EmptyMapToValue(t *testing.T) { + var result1, result2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result1), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result2), + testCheckAttributeValuesDiffer(&result1, &result2), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourcePassword_Keepers_Replace_NullMapToValue(t *testing.T) { + var result1, result2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result1), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result2), + testCheckAttributeValuesDiffer(&result1, &result2), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourcePassword_Keepers_Replace_NullValueToValue(t *testing.T) { + var result1, result2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result1), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result2), + testCheckAttributeValuesDiffer(&result1, &result2), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourcePassword_Keepers_Replace_ValueToEmptyMap(t *testing.T) { + var result1, result2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result1), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result2), + testCheckAttributeValuesDiffer(&result1, &result2), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourcePassword_Keepers_Replace_ValueToNullMap(t *testing.T) { + var result1, result2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result1), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result2), + testCheckAttributeValuesDiffer(&result1, &result2), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourcePassword_Keepers_Replace_ValueToNullValue(t *testing.T) { + var result1, result2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result1), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result2), + testCheckAttributeValuesDiffer(&result1, &result2), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourcePassword_Keepers_Replace_ValueToNewValue(t *testing.T) { + var result1, result2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result1), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result2), + testCheckAttributeValuesDiffer(&result1, &result2), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourcePassword_Keepers_FrameworkMigration_NullMapToNullValue(t *testing.T) { + var result1, result2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result1), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result2), + testCheckAttributeValuesEqual(&result1, &result2), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourcePassword_Keepers_FrameworkMigration_NullMapToValue(t *testing.T) { + var result1, result2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result1), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result2), + testCheckAttributeValuesDiffer(&result1, &result2), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourcePassword_Keepers_FrameworkMigration_NullMapToMultipleNullValue(t *testing.T) { + var result1, result2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result1), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result2), + testCheckAttributeValuesEqual(&result1, &result2), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourcePassword_Keepers_FrameworkMigration_NullMapToMultipleValue(t *testing.T) { + var result1, result2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result1), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result2), + testCheckAttributeValuesDiffer(&result1, &result2), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourcePassword_Keepers_FrameworkMigration_NullMapValue(t *testing.T) { + var result1, result2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key1" = "123" + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result1), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key1" = "123" + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result2), + testCheckAttributeValuesEqual(&result1, &result2), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourcePassword_Keepers_FrameworkMigration_NullMapValueToValue(t *testing.T) { + var result1, result2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key1" = "123" + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result1), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_password" "test" { + length = 12 + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_password.test", "result", &result2), + testCheckAttributeValuesDiffer(&result1, &result2), + resource.TestCheckResourceAttr("random_password.test", "keepers.%", "2"), + ), + }, + }, + }) +} diff --git a/internal/provider/resource_pet.go b/internal/provider/resource_pet.go index 40c570e7..10a4657a 100644 --- a/internal/provider/resource_pet.go +++ b/internal/provider/resource_pet.go @@ -36,7 +36,7 @@ func (r *petResourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnos }, Optional: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - resource.RequiresReplace(), + planmodifiers.RequiresReplaceIfValuesNotNull(), }, }, "length": { @@ -69,6 +69,9 @@ func (r *petResourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnos Description: "The random pet name.", Type: types.StringType, Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + resource.UseStateForUnknown(), + }, }, }, }, nil @@ -128,9 +131,17 @@ func (r *petResource) Create(ctx context.Context, req resource.CreateRequest, re func (r *petResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { } -// Update is intentionally left blank as all required and optional attributes force replacement of the resource -// through the RequiresReplace AttributePlanModifier. +// Update ensures the plan value is copied to the state to complete the update. func (r *petResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var model petModelV0 + + resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &model)...) } // Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the diff --git a/internal/provider/resource_pet_test.go b/internal/provider/resource_pet_test.go index 721ea463..0c8261c3 100644 --- a/internal/provider/resource_pet_test.go +++ b/internal/provider/resource_pet_test.go @@ -24,6 +24,707 @@ func TestAccResourcePet(t *testing.T) { }) } +func TestAccResourcePet_Keepers_Keep_EmptyMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id1), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourcePet_Keepers_Keep_EmptyMapToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id1), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourcePet_Keepers_Keep_NullMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id1), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourcePet_Keepers_Keep_NullMapToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id1), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourcePet_Keepers_Keep_NullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id1), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourcePet_Keepers_Keep_NullValues(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id1), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "2"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourcePet_Keepers_Keep_Value(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id1), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourcePet_Keepers_Keep_Values(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id1), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "2"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourcePet_Keepers_Replace_EmptyMapToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id1), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourcePet_Keepers_Replace_NullMapToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id1), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourcePet_Keepers_Replace_NullValueToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id1), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourcePet_Keepers_Replace_ValueToEmptyMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id1), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourcePet_Keepers_Replace_ValueToNullMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id1), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourcePet_Keepers_Replace_ValueToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id1), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourcePet_Keepers_Replace_ValueToNewValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id1), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = { + "key" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourcePet_Keepers_FrameworkMigration_NullMapToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_pet" "test" { + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id1), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourcePet_Keepers_FrameworkMigration_NullMapToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_pet" "test" { + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id1), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourcePet_Keepers_FrameworkMigration_NullMapToMultipleNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_pet" "test" { + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id1), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourcePet_Keepers_FrameworkMigration_NullMapToMultipleValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_pet" "test" { + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id1), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourcePet_Keepers_FrameworkMigration_NullMapValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_pet" "test" { + keepers = { + "key1" = "123" + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id1), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = { + "key1" = "123" + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourcePet_Keepers_FrameworkMigration_NullMapValueToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_pet" "test" { + keepers = { + "key1" = "123" + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id1), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_pet" "test" { + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_pet.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_pet.test", "keepers.%", "2"), + ), + }, + }, + }) +} + func TestAccResourcePet_Length(t *testing.T) { resource.UnitTest(t, resource.TestCase{ ProtoV5ProviderFactories: protoV5ProviderFactories(), diff --git a/internal/provider/resource_shuffle.go b/internal/provider/resource_shuffle.go index 2e855d3e..0329c097 100644 --- a/internal/provider/resource_shuffle.go +++ b/internal/provider/resource_shuffle.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/terraform-providers/terraform-provider-random/internal/planmodifiers" "github.com/terraform-providers/terraform-provider-random/internal/random" ) @@ -30,7 +31,7 @@ func (r *shuffleResourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Dia }, Optional: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - resource.RequiresReplace(), + planmodifiers.RequiresReplaceIfValuesNotNull(), }, }, "seed": { @@ -73,11 +74,17 @@ func (r *shuffleResourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Dia ElemType: types.StringType, }, Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + resource.UseStateForUnknown(), + }, }, "id": { Description: "A static value used internally by Terraform, this should not be referenced in configurations.", Type: types.StringType, Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + resource.UseStateForUnknown(), + }, }, }, }, nil @@ -162,9 +169,17 @@ func (r *shuffleResource) Create(ctx context.Context, req resource.CreateRequest func (r *shuffleResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { } -// Update is intentionally left blank as all required and optional attributes force replacement of the resource -// through the RequiresReplace AttributePlanModifier. +// Update ensures the plan value is copied to the state to complete the update. func (r *shuffleResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var model shuffleModelV0 + + resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &model)...) } // Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the diff --git a/internal/provider/resource_shuffle_test.go b/internal/provider/resource_shuffle_test.go index 0cdbdf92..d27de7cc 100644 --- a/internal/provider/resource_shuffle_test.go +++ b/internal/provider/resource_shuffle_test.go @@ -2,9 +2,12 @@ package provider import ( "fmt" + "reflect" + "strconv" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) // These results are current as of Go 1.6. The Go @@ -38,6 +41,749 @@ func TestAccResourceShuffle(t *testing.T) { }) } +func TestAccResourceShuffle_Keepers_Keep_EmptyMap(t *testing.T) { + var result1, result2 []string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result1), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result2), + testCheckAttributeValueListsEqual(&result1, &result2), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceShuffle_Keepers_Keep_EmptyMapToNullValue(t *testing.T) { + var result1, result2 []string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result1), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result2), + testCheckAttributeValueListsEqual(&result1, &result2), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceShuffle_Keepers_Keep_NullMap(t *testing.T) { + var result1, result2 []string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result1), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result2), + testCheckAttributeValueListsEqual(&result1, &result2), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceShuffle_Keepers_Keep_NullMapToNullValue(t *testing.T) { + var result1, result2 []string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result1), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result2), + testCheckAttributeValueListsEqual(&result1, &result2), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceShuffle_Keepers_Keep_NullValue(t *testing.T) { + var result1, result2 []string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result1), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result2), + testCheckAttributeValueListsEqual(&result1, &result2), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceShuffle_Keepers_Keep_NullValues(t *testing.T) { + var result1, result2 []string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result1), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "2"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result2), + testCheckAttributeValueListsEqual(&result1, &result2), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceShuffle_Keepers_Keep_Value(t *testing.T) { + var result1, result2 []string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result1), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result2), + testCheckAttributeValueListsEqual(&result1, &result2), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceShuffle_Keepers_Keep_Values(t *testing.T) { + var result1, result2 []string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result1), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "2"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result2), + testCheckAttributeValueListsEqual(&result1, &result2), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceShuffle_Keepers_Replace_EmptyMapToValue(t *testing.T) { + var result1, result2 []string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result1), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result2), + testCheckAttributeValueListsDiffer(&result1, &result2), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceShuffle_Keepers_Replace_NullMapToValue(t *testing.T) { + var result1, result2 []string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result1), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result2), + testCheckAttributeValueListsDiffer(&result1, &result2), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceShuffle_Keepers_Replace_NullValueToValue(t *testing.T) { + var result1, result2 []string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result1), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result2), + testCheckAttributeValueListsDiffer(&result1, &result2), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceShuffle_Keepers_Replace_ValueToEmptyMap(t *testing.T) { + var result1, result2 []string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result1), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result2), + testCheckAttributeValueListsDiffer(&result1, &result2), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceShuffle_Keepers_Replace_ValueToNullMap(t *testing.T) { + var result1, result2 []string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result1), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result2), + testCheckAttributeValueListsDiffer(&result1, &result2), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceShuffle_Keepers_Replace_ValueToNullValue(t *testing.T) { + var result1, result2 []string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result1), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result2), + testCheckAttributeValueListsDiffer(&result1, &result2), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceShuffle_Keepers_Replace_ValueToNewValue(t *testing.T) { + var result1, result2 []string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result1), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result2), + testCheckAttributeValueListsDiffer(&result1, &result2), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceShuffle_Keepers_FrameworkMigration_NullMapToNullValue(t *testing.T) { + var result1, result2 []string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result1), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result2), + testCheckAttributeValueListsEqual(&result1, &result2), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceShuffle_Keepers_FrameworkMigration_NullMapToValue(t *testing.T) { + var result1, result2 []string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result1), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result2), + testCheckAttributeValueListsDiffer(&result1, &result2), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceShuffle_Keepers_FrameworkMigration_NullMapToMultipleNullValue(t *testing.T) { + var result1, result2 []string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result1), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result2), + testCheckAttributeValueListsEqual(&result1, &result2), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceShuffle_Keepers_FrameworkMigration_NullMapToMultipleValue(t *testing.T) { + var result1, result2 []string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result1), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result2), + testCheckAttributeValueListsDiffer(&result1, &result2), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceShuffle_Keepers_FrameworkMigration_NullMapValue(t *testing.T) { + var result1, result2 []string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key1" = "123" + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result1), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key1" = "123" + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result2), + testCheckAttributeValueListsEqual(&result1, &result2), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceShuffle_Keepers_FrameworkMigration_NullMapValueToValue(t *testing.T) { + var result1, result2 []string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key1" = "123" + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result1), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_shuffle" "test" { + input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttrList("random_shuffle.test", "result", &result2), + testCheckAttributeValueListsDiffer(&result1, &result2), + resource.TestCheckResourceAttr("random_shuffle.test", "keepers.%", "2"), + ), + }, + }, + }) +} + func TestAccResourceShuffle_Shorter(t *testing.T) { resource.UnitTest(t, resource.TestCase{ ProtoV5ProviderFactories: protoV5ProviderFactories(), @@ -180,3 +926,66 @@ func testAccResourceShuffleCheckLength(expectedLength string) func(input string) return nil } } + +//nolint:unparam +func testExtractResourceAttrList(resourceName string, attributeName string, attributeValue *[]string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + + if !ok { + return fmt.Errorf("resource name %s not found in state", resourceName) + } + + elementCountAttr := attributeName + ".#" + + elementCountValue, ok := rs.Primary.Attributes[elementCountAttr] + + if !ok { + return fmt.Errorf("attribute %s not found in resource %s state", elementCountAttr, resourceName) + } + + elementCount, err := strconv.Atoi(elementCountValue) + + if err != nil { + return fmt.Errorf("attribute %s not integer: %w", elementCountAttr, err) + } + + listValue := make([]string, elementCount) + + for i := 0; i < elementCount; i++ { + attr := attributeName + "." + strconv.Itoa(i) + + attrValue, ok := rs.Primary.Attributes[attr] + + if !ok { + return fmt.Errorf("attribute %s not found in resource %s state", attr, resourceName) + } + + listValue[i] = attrValue + } + + *attributeValue = listValue + + return nil + } +} + +func testCheckAttributeValueListsDiffer(i *[]string, j *[]string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if reflect.DeepEqual(i, j) { + return fmt.Errorf("attribute values are the same") + } + + return nil + } +} + +func testCheckAttributeValueListsEqual(i *[]string, j *[]string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if !reflect.DeepEqual(i, j) { + return fmt.Errorf("attribute values are different, got %v and %v", i, j) + } + + return nil + } +} diff --git a/internal/provider/resource_string.go b/internal/provider/resource_string.go index cf0d1566..7176626c 100644 --- a/internal/provider/resource_string.go +++ b/internal/provider/resource_string.go @@ -40,7 +40,7 @@ func (r stringResourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagn }, Optional: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - resource.RequiresReplace(), + planmodifiers.RequiresReplaceIfValuesNotNull(), }, }, @@ -170,6 +170,7 @@ func (r stringResourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagn Optional: true, Computed: true, PlanModifiers: []tfsdk.AttributePlanModifier{ + planmodifiers.DefaultValue(types.String{Value: ""}), resource.RequiresReplace(), }, }, @@ -178,12 +179,18 @@ func (r stringResourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagn Description: "The generated random string.", Type: types.StringType, Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + resource.UseStateForUnknown(), + }, }, "id": { Description: "The generated random string.", Computed: true, Type: types.StringType, + PlanModifiers: []tfsdk.AttributePlanModifier{ + resource.UseStateForUnknown(), + }, }, }, }, nil @@ -257,9 +264,17 @@ func (r *stringResource) Create(ctx context.Context, req resource.CreateRequest, func (r *stringResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { } -// Update is intentionally left blank as all required and optional attributes force replacement of the resource -// through the RequiresReplace AttributePlanModifier. +// Update ensures the plan value is copied to the state to complete the update. func (r *stringResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var model stringModelV2 + + resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &model)...) } // Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the diff --git a/internal/provider/resource_string_test.go b/internal/provider/resource_string_test.go index ec39fe55..614e812f 100644 --- a/internal/provider/resource_string_test.go +++ b/internal/provider/resource_string_test.go @@ -29,6 +29,749 @@ func TestAccResourceString(t *testing.T) { }) } +func TestAccResourceString_Keepers_Keep_EmptyMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id1), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceString_Keepers_Keep_EmptyMapToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id1), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceString_Keepers_Keep_NullMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id1), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceString_Keepers_Keep_NullMapToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id1), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceString_Keepers_Keep_NullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id1), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceString_Keepers_Keep_NullValues(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id1), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "2"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceString_Keepers_Keep_Value(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id1), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceString_Keepers_Keep_Values(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id1), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "2"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceString_Keepers_Replace_EmptyMapToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id1), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceString_Keepers_Replace_NullMapToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id1), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceString_Keepers_Replace_NullValueToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id1), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceString_Keepers_Replace_ValueToEmptyMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id1), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceString_Keepers_Replace_ValueToNullMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id1), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceString_Keepers_Replace_ValueToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id1), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceString_Keepers_Replace_ValueToNewValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id1), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceString_Keepers_FrameworkMigration_NullMapToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id1), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceString_Keepers_FrameworkMigration_NullMapToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id1), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceString_Keepers_FrameworkMigration_NullMapToMultipleNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id1), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceString_Keepers_FrameworkMigration_NullMapToMultipleValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id1), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceString_Keepers_FrameworkMigration_NullMapValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key1" = "123" + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id1), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key1" = "123" + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceString_Keepers_FrameworkMigration_NullMapValueToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key1" = "123" + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id1), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_string" "test" { + length = 12 + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_string.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_string.test", "keepers.%", "2"), + ), + }, + }, + }) +} + func TestAccResourceString_Override(t *testing.T) { resource.UnitTest(t, resource.TestCase{ ProtoV5ProviderFactories: protoV5ProviderFactories(), diff --git a/internal/provider/resource_uuid.go b/internal/provider/resource_uuid.go index dba22af2..0dafb841 100644 --- a/internal/provider/resource_uuid.go +++ b/internal/provider/resource_uuid.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/terraform-providers/terraform-provider-random/internal/diagnostics" + "github.com/terraform-providers/terraform-provider-random/internal/planmodifiers" ) var _ provider.ResourceType = (*uuidResourceType)(nil) @@ -34,18 +35,24 @@ func (r *uuidResourceType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagno }, Optional: true, PlanModifiers: []tfsdk.AttributePlanModifier{ - resource.RequiresReplace(), + planmodifiers.RequiresReplaceIfValuesNotNull(), }, }, "result": { Description: "The generated uuid presented in string format.", Type: types.StringType, Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + resource.UseStateForUnknown(), + }, }, "id": { Description: "The generated uuid presented in string format.", Type: types.StringType, Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + resource.UseStateForUnknown(), + }, }, }, }, nil @@ -100,9 +107,17 @@ func (r *uuidResource) Create(ctx context.Context, req resource.CreateRequest, r func (r *uuidResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { } -// Update is intentionally left blank as all required and optional attributes force replacement of the resource -// through the RequiresReplace AttributePlanModifier. +// Update ensures the plan value is copied to the state to complete the update. func (r *uuidResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var model uuidModelV0 + + resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &model)...) } // Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the diff --git a/internal/provider/resource_uuid_test.go b/internal/provider/resource_uuid_test.go index d365ca69..7c550416 100644 --- a/internal/provider/resource_uuid_test.go +++ b/internal/provider/resource_uuid_test.go @@ -27,6 +27,707 @@ func TestAccResourceUUID(t *testing.T) { }) } +func TestAccResourceUUID_Keepers_Keep_EmptyMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id1), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceUUID_Keepers_Keep_EmptyMapToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id1), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceUUID_Keepers_Keep_NullMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id1), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceUUID_Keepers_Keep_NullMapToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id1), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceUUID_Keepers_Keep_NullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id1), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceUUID_Keepers_Keep_NullValues(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id1), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "2"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceUUID_Keepers_Keep_Value(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id1), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceUUID_Keepers_Keep_Values(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id1), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "2"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceUUID_Keepers_Replace_EmptyMapToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id1), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceUUID_Keepers_Replace_NullMapToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id1), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceUUID_Keepers_Replace_NullValueToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id1), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceUUID_Keepers_Replace_ValueToEmptyMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id1), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceUUID_Keepers_Replace_ValueToNullMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id1), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceUUID_Keepers_Replace_ValueToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id1), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceUUID_Keepers_Replace_ValueToNewValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id1), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = { + "key" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceUUID_Keepers_FrameworkMigration_NullMapToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_uuid" "test" { + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id1), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceUUID_Keepers_FrameworkMigration_NullMapToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_uuid" "test" { + keepers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id1), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceUUID_Keepers_FrameworkMigration_NullMapToMultipleNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_uuid" "test" { + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id1), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceUUID_Keepers_FrameworkMigration_NullMapToMultipleValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_uuid" "test" { + keepers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id1), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceUUID_Keepers_FrameworkMigration_NullMapValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_uuid" "test" { + keepers = { + "key1" = "123" + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id1), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = { + "key1" = "123" + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceUUID_Keepers_FrameworkMigration_NullMapValueToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion332(), + Config: `resource "random_uuid" "test" { + keepers = { + "key1" = "123" + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id1), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid" "test" { + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_uuid.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("random_uuid.test", "keepers.%", "2"), + ), + }, + }, + }) +} + func TestAccResourceUUID_UpgradeFromVersion3_3_2(t *testing.T) { resource.Test(t, resource.TestCase{ Steps: []resource.TestStep{