diff --git a/docs/resources/uuid4.md b/docs/resources/uuid4.md new file mode 100644 index 00000000..e7220a0d --- /dev/null +++ b/docs/resources/uuid4.md @@ -0,0 +1,52 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "random_uuid4 Resource - terraform-provider-random" +subcategory: "" +description: |- + The resource random_uuid4 generates a random version 4 uuid string that is intended to be used as a unique identifier for other resources. + This resource uses google/uuid https://github.com/google/uuid to generate a valid V4 UUID for use with services needing a unique string identifier. +--- + +# random_uuid4 (Resource) + +The resource `random_uuid4` generates a random version 4 uuid string that is intended to be used as a unique identifier for other resources. + +This resource uses [google/uuid](https://github.com/google/uuid) to generate a valid V4 UUID for use with services needing a unique string identifier. + +## Example Usage + +```terraform +# The following example shows how to generate a unique name for an Azure Resource Group. + +resource "random_uuid4" "test" { +} + +resource "azurerm_resource_group" "test" { + name = "${random_uuid4.test.result}-rg" + location = "Central US" +} +``` + + +## Schema + +### Optional + +- `keepers` (Map of String) Arbitrary map of values that, when changed, will trigger recreation of resource. See [the main provider documentation](../index.html) for more information. + +### Read-Only + +- `id` (String) The generated uuid presented in string format. +- `result` (String) The generated uuid presented in string format. + +## Import + +Import is supported using the following syntax: + +```shell +# Random UUID's can be imported. This can be used to replace a config +# value with a value interpolated from the random provider without +# experiencing diffs. + +terraform import random_uuid4.main 7e4436da-7c71-486e-a57c-830b25fff7bd +``` diff --git a/docs/resources/uuid7.md b/docs/resources/uuid7.md new file mode 100644 index 00000000..52acd27c --- /dev/null +++ b/docs/resources/uuid7.md @@ -0,0 +1,52 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "random_uuid7 Resource - terraform-provider-random" +subcategory: "" +description: |- + The resource random_uuid7 generates a random version 7 uuid string that is intended to be used as a unique identifier for other resources. + This resource uses google/uuid https://github.com/google/uuid to generate a valid V7 UUID for use with services needing a unique string identifier. +--- + +# random_uuid7 (Resource) + +The resource `random_uuid7` generates a random version 7 uuid string that is intended to be used as a unique identifier for other resources. + +This resource uses [google/uuid](https://github.com/google/uuid) to generate a valid V7 UUID for use with services needing a unique string identifier. + +## Example Usage + +```terraform +# The following example shows how to generate a unique name for an Azure Resource Group. + +resource "random_uuid7" "test" { +} + +resource "azurerm_resource_group" "test" { + name = "${random_uuid7.test.result}-rg" + location = "Central US" +} +``` + + +## Schema + +### Optional + +- `keepers` (Map of String) Arbitrary map of values that, when changed, will trigger recreation of resource. See [the main provider documentation](../index.html) for more information. + +### Read-Only + +- `id` (String) The generated uuid presented in string format. +- `result` (String) The generated uuid presented in string format. + +## Import + +Import is supported using the following syntax: + +```shell +# Random UUID's can be imported. This can be used to replace a config +# value with a value interpolated from the random provider without +# experiencing diffs. + +terraform import random_uuid7.main 0197ad85-fe6e-7e92-a2f5-7550daa83030 +``` diff --git a/examples/resources/random_uuid4/import.sh b/examples/resources/random_uuid4/import.sh new file mode 100644 index 00000000..8b6dc07e --- /dev/null +++ b/examples/resources/random_uuid4/import.sh @@ -0,0 +1,5 @@ +# Random UUID's can be imported. This can be used to replace a config +# value with a value interpolated from the random provider without +# experiencing diffs. + +terraform import random_uuid4.main 7e4436da-7c71-486e-a57c-830b25fff7bd diff --git a/examples/resources/random_uuid4/resource.tf b/examples/resources/random_uuid4/resource.tf new file mode 100644 index 00000000..895a15b3 --- /dev/null +++ b/examples/resources/random_uuid4/resource.tf @@ -0,0 +1,9 @@ +# The following example shows how to generate a unique name for an Azure Resource Group. + +resource "random_uuid4" "test" { +} + +resource "azurerm_resource_group" "test" { + name = "${random_uuid4.test.result}-rg" + location = "Central US" +} diff --git a/examples/resources/random_uuid7/import.sh b/examples/resources/random_uuid7/import.sh new file mode 100644 index 00000000..0611a1bc --- /dev/null +++ b/examples/resources/random_uuid7/import.sh @@ -0,0 +1,5 @@ +# Random UUID's can be imported. This can be used to replace a config +# value with a value interpolated from the random provider without +# experiencing diffs. + +terraform import random_uuid7.main 0197ad85-fe6e-7e92-a2f5-7550daa83030 diff --git a/examples/resources/random_uuid7/resource.tf b/examples/resources/random_uuid7/resource.tf new file mode 100644 index 00000000..1c29894c --- /dev/null +++ b/examples/resources/random_uuid7/resource.tf @@ -0,0 +1,9 @@ +# The following example shows how to generate a unique name for an Azure Resource Group. + +resource "random_uuid7" "test" { +} + +resource "azurerm_resource_group" "test" { + name = "${random_uuid7.test.result}-rg" + location = "Central US" +} diff --git a/go.mod b/go.mod index a4a3392d..dcfd476c 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.23.7 require ( github.com/dustinkirkland/golang-petname v0.0.0-20240428194347-eebcea082ee0 github.com/google/go-cmp v0.7.0 + github.com/google/uuid v1.6.0 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/terraform-json v0.25.0 github.com/hashicorp/terraform-plugin-framework v1.15.0 diff --git a/internal/provider/provider.go b/internal/provider/provider.go index fd1c340d..228322f0 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -41,6 +41,8 @@ func (p *randomProvider) Resources(context.Context) []func() resource.Resource { NewShuffleResource, NewStringResource, NewUuidResource, + NewUuidV4Resource, + NewUuidV7Resource, } } diff --git a/internal/provider/resource_uuid4.go b/internal/provider/resource_uuid4.go new file mode 100644 index 00000000..22adcdf4 --- /dev/null +++ b/internal/provider/resource_uuid4.go @@ -0,0 +1,155 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "context" + "fmt" + + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/terraform-providers/terraform-provider-random/internal/diagnostics" + mapplanmodifiers "github.com/terraform-providers/terraform-provider-random/internal/planmodifiers/map" +) + +var ( + _ resource.Resource = (*uuidV4Resource)(nil) + _ resource.ResourceWithImportState = (*uuidV4Resource)(nil) +) + +func NewUuidV4Resource() resource.Resource { + return &uuidV4Resource{} +} + +type uuidV4Resource struct{} + +func (r *uuidV4Resource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_uuid4" +} + +func (r *uuidV4Resource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "The resource `random_uuid4` generates a random version 4 uuid string that is intended " + + "to be used as a unique identifier for other resources.\n" + + "\n" + + "This resource uses [google/uuid](https://github.com/google/uuid) to generate a " + + "valid V4 UUID for use with services needing a unique string identifier.", + Attributes: map[string]schema.Attribute{ + "keepers": schema.MapAttribute{ + Description: "Arbitrary map of values that, when changed, will trigger recreation of " + + "resource. See [the main provider documentation](../index.html) for more information.", + ElementType: types.StringType, + Optional: true, + PlanModifiers: []planmodifier.Map{ + mapplanmodifiers.RequiresReplaceIfValuesNotNull(), + }, + }, + "result": schema.StringAttribute{ + Description: "The generated uuid presented in string format.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "id": schema.StringAttribute{ + Description: "The generated uuid presented in string format.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + } +} + +func (r *uuidV4Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + result, err := uuid.NewRandom() + if err != nil { + resp.Diagnostics.AddError( + "Create Random UUID v4 error", + "There was an error during generation of a UUID.\n\n"+ + diagnostics.RetryMsg+ + fmt.Sprintf("Original Error: %s", err), + ) + return + } + + var plan uuidModelV4 + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + u := &uuidModelV4{ + ID: types.StringValue(result.String()), + Result: types.StringValue(result.String()), + Keepers: plan.Keepers, + } + + diags = resp.State.Set(ctx, u) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Read does not need to perform any operations as the state in ReadResourceResponse is already populated. +func (r *uuidV4Resource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { +} + +// Update ensures the plan value is copied to the state to complete the update. +func (r *uuidV4Resource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var model uuidModelV4 + + 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 +// [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301). +func (r *uuidV4Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +} + +func (r *uuidV4Resource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + parsedUuid, err := uuid.Parse(req.ID) + if err != nil { + resp.Diagnostics.AddError( + "Import Random UUID Error", + "There was an error during the parsing of the UUID.\n\n"+ + diagnostics.RetryMsg+ + fmt.Sprintf("Original Error: %s", err), + ) + return + } + + var state uuidModelV4 + + state.ID = types.StringValue(parsedUuid.String()) + state.Result = types.StringValue(parsedUuid.String()) + state.Keepers = types.MapNull(types.StringType) + + diags := resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +type uuidModelV4 struct { + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + Result types.String `tfsdk:"result"` +} diff --git a/internal/provider/resource_uuid4_test.go b/internal/provider/resource_uuid4_test.go new file mode 100644 index 00000000..ef870cd3 --- /dev/null +++ b/internal/provider/resource_uuid4_test.go @@ -0,0 +1,545 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestAccResourceUUIDV4(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: `resource "random_uuid4" "basic" { + }`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("random_uuid4.basic", tfjsonpath.New("result"), knownvalue.StringRegexp(regexp.MustCompile(`[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}`))), + }, + }, + { + ResourceName: "random_uuid4.basic", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccResourceUUIDV4_ImportWithoutKeepersProducesNoPlannedChanges(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: `resource "random_uuid4" "basic" { + }`, + ResourceName: "random_uuid4.basic", + ImportStateId: "6b0f8e7c-3ea6-4523-88a2-5a70419ee954", + ImportState: true, + ImportStatePersist: true, + }, + { + Config: `resource "random_uuid4" "basic" { + }`, + PlanOnly: true, + }, + }, + }) +} + +func TestAccResourceUUIDV4_Keepers_Keep_EmptyMap(t *testing.T) { + // The id attribute values should be the same between test steps + assertIdSame := statecheck.CompareValue(compare.ValuesSame()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + keepers = {} + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(0)), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + keepers = {} + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(0)), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV4_Keepers_Keep_EmptyMapToNullValue(t *testing.T) { + // The id attribute values should be the same between test steps + assertIdSame := statecheck.CompareValue(compare.ValuesSame()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + keepers = {} + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(0)), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + keepers = { + "key" = null + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV4_Keepers_Keep_NullMap(t *testing.T) { + // The id attribute values should be the same between test steps + assertIdSame := statecheck.CompareValue(compare.ValuesSame()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.Null()), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.Null()), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV4_Keepers_Keep_NullMapToNullValue(t *testing.T) { + // The id attribute values should be the same between test steps + assertIdSame := statecheck.CompareValue(compare.ValuesSame()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.Null()), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + keepers = { + "key" = null + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV4_Keepers_Keep_NullValue(t *testing.T) { + // The id attribute values should be the same between test steps + assertIdSame := statecheck.CompareValue(compare.ValuesSame()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + keepers = { + "key" = null + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + keepers = { + "key" = null + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV4_Keepers_Keep_NullValues(t *testing.T) { + // The id attribute values should be the same between test steps + assertIdSame := statecheck.CompareValue(compare.ValuesSame()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + keepers = { + "key1" = null + "key2" = null + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(2)), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + keepers = { + "key1" = null + "key2" = null + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(2)), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV4_Keepers_Keep_Value(t *testing.T) { + // The id attribute values should be the same between test steps + assertIdSame := statecheck.CompareValue(compare.ValuesSame()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + keepers = { + "key" = "123" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + keepers = { + "key" = "123" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV4_Keepers_Keep_Values(t *testing.T) { + // The id attribute values should be the same between test steps + assertIdSame := statecheck.CompareValue(compare.ValuesSame()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(2)), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + keepers = { + "key1" = "123" + "key2" = "456" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(2)), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV4_Keepers_Replace_EmptyMapToValue(t *testing.T) { + // The id attribute values should differ between test steps + assertIdDiffer := statecheck.CompareValue(compare.ValuesDiffer()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + keepers = {} + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(0)), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + keepers = { + "key" = "123" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV4_Keepers_Replace_NullMapToValue(t *testing.T) { + // The id attribute values should differ between test steps + assertIdDiffer := statecheck.CompareValue(compare.ValuesDiffer()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.Null()), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + keepers = { + "key" = "123" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV4_Keepers_Replace_NullValueToValue(t *testing.T) { + // The id attribute values should differ between test steps + assertIdDiffer := statecheck.CompareValue(compare.ValuesDiffer()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + keepers = { + "key" = null + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + keepers = { + "key" = "123" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV4_Keepers_Replace_ValueToEmptyMap(t *testing.T) { + // The id attribute values should differ between test steps + assertIdDiffer := statecheck.CompareValue(compare.ValuesDiffer()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + keepers = { + "key" = "123" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + keepers = {} + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(0)), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV4_Keepers_Replace_ValueToNullMap(t *testing.T) { + // The id attribute values should differ between test steps + assertIdDiffer := statecheck.CompareValue(compare.ValuesDiffer()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + keepers = { + "key" = "123" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.Null()), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV4_Keepers_Replace_ValueToNullValue(t *testing.T) { + // The id attribute values should differ between test steps + assertIdDiffer := statecheck.CompareValue(compare.ValuesDiffer()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + keepers = { + "key" = "123" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + keepers = { + "key" = null + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV4_Keepers_Replace_ValueToNewValue(t *testing.T) { + // The id attribute values should differ between test steps + assertIdDiffer := statecheck.CompareValue(compare.ValuesDiffer()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + keepers = { + "key" = "123" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid4" "test" { + keepers = { + "key" = "456" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid4.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid4.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + }, + }) +} diff --git a/internal/provider/resource_uuid7.go b/internal/provider/resource_uuid7.go new file mode 100644 index 00000000..ea0d825c --- /dev/null +++ b/internal/provider/resource_uuid7.go @@ -0,0 +1,155 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "context" + "fmt" + + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/terraform-providers/terraform-provider-random/internal/diagnostics" + mapplanmodifiers "github.com/terraform-providers/terraform-provider-random/internal/planmodifiers/map" +) + +var ( + _ resource.Resource = (*uuidV7Resource)(nil) + _ resource.ResourceWithImportState = (*uuidV7Resource)(nil) +) + +func NewUuidV7Resource() resource.Resource { + return &uuidV7Resource{} +} + +type uuidV7Resource struct{} + +func (r *uuidV7Resource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_uuid7" +} + +func (r *uuidV7Resource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "The resource `random_uuid7` generates a random version 7 uuid string that is intended " + + "to be used as a unique identifier for other resources.\n" + + "\n" + + "This resource uses [google/uuid](https://github.com/google/uuid) to generate a " + + "valid V7 UUID for use with services needing a unique string identifier.", + Attributes: map[string]schema.Attribute{ + "keepers": schema.MapAttribute{ + Description: "Arbitrary map of values that, when changed, will trigger recreation of " + + "resource. See [the main provider documentation](../index.html) for more information.", + ElementType: types.StringType, + Optional: true, + PlanModifiers: []planmodifier.Map{ + mapplanmodifiers.RequiresReplaceIfValuesNotNull(), + }, + }, + "result": schema.StringAttribute{ + Description: "The generated uuid presented in string format.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "id": schema.StringAttribute{ + Description: "The generated uuid presented in string format.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + } +} + +func (r *uuidV7Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + result, err := uuid.NewV7() + if err != nil { + resp.Diagnostics.AddError( + "Create Random UUID v7 error", + "There was an error during generation of a UUID.\n\n"+ + diagnostics.RetryMsg+ + fmt.Sprintf("Original Error: %s", err), + ) + return + } + + var plan uuidModelV7 + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + u := &uuidModelV7{ + ID: types.StringValue(result.String()), + Result: types.StringValue(result.String()), + Keepers: plan.Keepers, + } + + diags = resp.State.Set(ctx, u) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Read does not need to perform any operations as the state in ReadResourceResponse is already populated. +func (r *uuidV7Resource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { +} + +// Update ensures the plan value is copied to the state to complete the update. +func (r *uuidV7Resource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var model uuidModelV7 + + 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 +// [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301). +func (r *uuidV7Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +} + +func (r *uuidV7Resource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + parsedUuid, err := uuid.Parse(req.ID) + if err != nil { + resp.Diagnostics.AddError( + "Import Random UUID Error", + "There was an error during the parsing of the UUID.\n\n"+ + diagnostics.RetryMsg+ + fmt.Sprintf("Original Error: %s", err), + ) + return + } + + var state uuidModelV7 + + state.ID = types.StringValue(parsedUuid.String()) + state.Result = types.StringValue(parsedUuid.String()) + state.Keepers = types.MapNull(types.StringType) + + diags := resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +type uuidModelV7 struct { + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + Result types.String `tfsdk:"result"` +} diff --git a/internal/provider/resource_uuid7_test.go b/internal/provider/resource_uuid7_test.go new file mode 100644 index 00000000..28031b99 --- /dev/null +++ b/internal/provider/resource_uuid7_test.go @@ -0,0 +1,545 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestAccResourceUUIDV7(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: `resource "random_uuid7" "basic" { + }`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("random_uuid7.basic", tfjsonpath.New("result"), knownvalue.StringRegexp(regexp.MustCompile(`[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}`))), + }, + }, + { + ResourceName: "random_uuid7.basic", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccResourceUUIDV7_ImportWithoutKeepersProducesNoPlannedChanges(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: `resource "random_uuid7" "basic" { + }`, + ResourceName: "random_uuid7.basic", + ImportStateId: "6b0f8e7c-3ea6-7523-88a2-5a70719ee957", + ImportState: true, + ImportStatePersist: true, + }, + { + Config: `resource "random_uuid7" "basic" { + }`, + PlanOnly: true, + }, + }, + }) +} + +func TestAccResourceUUIDV7_Keepers_Keep_EmptyMap(t *testing.T) { + // The id attribute values should be the same between test steps + assertIdSame := statecheck.CompareValue(compare.ValuesSame()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + keepers = {} + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(0)), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + keepers = {} + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(0)), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV7_Keepers_Keep_EmptyMapToNullValue(t *testing.T) { + // The id attribute values should be the same between test steps + assertIdSame := statecheck.CompareValue(compare.ValuesSame()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + keepers = {} + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(0)), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + keepers = { + "key" = null + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV7_Keepers_Keep_NullMap(t *testing.T) { + // The id attribute values should be the same between test steps + assertIdSame := statecheck.CompareValue(compare.ValuesSame()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.Null()), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.Null()), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV7_Keepers_Keep_NullMapToNullValue(t *testing.T) { + // The id attribute values should be the same between test steps + assertIdSame := statecheck.CompareValue(compare.ValuesSame()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.Null()), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + keepers = { + "key" = null + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV7_Keepers_Keep_NullValue(t *testing.T) { + // The id attribute values should be the same between test steps + assertIdSame := statecheck.CompareValue(compare.ValuesSame()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + keepers = { + "key" = null + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + keepers = { + "key" = null + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV7_Keepers_Keep_NullValues(t *testing.T) { + // The id attribute values should be the same between test steps + assertIdSame := statecheck.CompareValue(compare.ValuesSame()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + keepers = { + "key1" = null + "key2" = null + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(2)), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + keepers = { + "key1" = null + "key2" = null + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(2)), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV7_Keepers_Keep_Value(t *testing.T) { + // The id attribute values should be the same between test steps + assertIdSame := statecheck.CompareValue(compare.ValuesSame()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + keepers = { + "key" = "123" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + keepers = { + "key" = "123" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV7_Keepers_Keep_Values(t *testing.T) { + // The id attribute values should be the same between test steps + assertIdSame := statecheck.CompareValue(compare.ValuesSame()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + keepers = { + "key1" = "123" + "key2" = "756" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(2)), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + keepers = { + "key1" = "123" + "key2" = "756" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdSame.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(2)), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV7_Keepers_Replace_EmptyMapToValue(t *testing.T) { + // The id attribute values should differ between test steps + assertIdDiffer := statecheck.CompareValue(compare.ValuesDiffer()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + keepers = {} + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(0)), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + keepers = { + "key" = "123" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV7_Keepers_Replace_NullMapToValue(t *testing.T) { + // The id attribute values should differ between test steps + assertIdDiffer := statecheck.CompareValue(compare.ValuesDiffer()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.Null()), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + keepers = { + "key" = "123" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV7_Keepers_Replace_NullValueToValue(t *testing.T) { + // The id attribute values should differ between test steps + assertIdDiffer := statecheck.CompareValue(compare.ValuesDiffer()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + keepers = { + "key" = null + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + keepers = { + "key" = "123" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV7_Keepers_Replace_ValueToEmptyMap(t *testing.T) { + // The id attribute values should differ between test steps + assertIdDiffer := statecheck.CompareValue(compare.ValuesDiffer()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + keepers = { + "key" = "123" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + keepers = {} + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(0)), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV7_Keepers_Replace_ValueToNullMap(t *testing.T) { + // The id attribute values should differ between test steps + assertIdDiffer := statecheck.CompareValue(compare.ValuesDiffer()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + keepers = { + "key" = "123" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.Null()), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV7_Keepers_Replace_ValueToNullValue(t *testing.T) { + // The id attribute values should differ between test steps + assertIdDiffer := statecheck.CompareValue(compare.ValuesDiffer()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + keepers = { + "key" = "123" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + keepers = { + "key" = null + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + }, + }) +} + +func TestAccResourceUUIDV7_Keepers_Replace_ValueToNewValue(t *testing.T) { + // The id attribute values should differ between test steps + assertIdDiffer := statecheck.CompareValue(compare.ValuesDiffer()) + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + keepers = { + "key" = "123" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_uuid7" "test" { + keepers = { + "key" = "756" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + assertIdDiffer.AddStateValue("random_uuid7.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("random_uuid7.test", tfjsonpath.New("keepers"), knownvalue.MapSizeExact(1)), + }, + }, + }, + }) +}