diff --git a/internal/framework5provider/move_state_resource.go b/internal/framework5provider/move_state_resource.go index 6102095..a84a3f5 100644 --- a/internal/framework5provider/move_state_resource.go +++ b/internal/framework5provider/move_state_resource.go @@ -9,14 +9,11 @@ import ( "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/hashicorp/terraform-plugin-framework/resource/identityschema" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) var _ resource.Resource = MoveStateResource{} -var _ resource.ResourceWithIdentity = MoveStateResource{} var _ resource.ResourceWithMoveState = MoveStateResource{} func NewMoveStateResource() resource.Resource { @@ -41,16 +38,6 @@ func (r MoveStateResource) Schema(_ context.Context, _ resource.SchemaRequest, r } } -func (r MoveStateResource) IdentitySchema(ctx context.Context, req resource.IdentitySchemaRequest, resp *resource.IdentitySchemaResponse) { - resp.IdentitySchema = identityschema.Schema{ - Attributes: map[string]identityschema.Attribute{ - "id": identityschema.StringAttribute{ - RequiredForImport: true, - }, - }, - } -} - func (r MoveStateResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var data MoveStateResourceModel @@ -121,46 +108,7 @@ func (r MoveStateResource) MoveState(ctx context.Context) []resource.StateMover } resp.Diagnostics.Append(resp.TargetState.SetAttribute(ctx, path.Root("moved_random_string"), oldState.Result)...) - resp.Diagnostics.Append(resp.TargetIdentity.SetAttribute(ctx, path.Root("id"), oldState.Result)...) - case "registry.terraform.io/hashicorp/framework": // Corner provider (testing identity moves) - if req.SourceTypeName != "framework_identity" { - resp.Diagnostics.AddError( - "Invalid Move State Request", - fmt.Sprintf("The \"framework_move_state\" resource can only be sourced from the \"random_string\" or \"framework_identity\" managed resources:\n\n"+ - "req.SourceProviderAddress: %q\n"+ - "req.SourceTypeName: %q\n", - req.SourceProviderAddress, - req.SourceTypeName, - ), - ) - return - } - - oldIdentityVal, err := req.SourceIdentity.Unmarshal( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "id": tftypes.String, - "name": tftypes.String, - }, - }, - ) - if err != nil { - resp.Diagnostics.AddError( - "Unexpected Move State Error", - fmt.Sprintf("Error decoding source identity: %s", err.Error()), - ) - return - } - - var sourceIdentityObj map[string]tftypes.Value - var sourceID, sourceName string - - oldIdentityVal.As(&sourceIdentityObj) //nolint:errcheck // This is just a quick test of grabbing raw identity data - sourceIdentityObj["id"].As(&sourceID) //nolint:errcheck // This is just a quick test of grabbing raw identity data - sourceIdentityObj["name"].As(&sourceName) //nolint:errcheck // This is just a quick test of grabbing raw identity data - resp.Diagnostics.Append(resp.TargetState.SetAttribute(ctx, path.Root("moved_random_string"), sourceName)...) - resp.Diagnostics.Append(resp.TargetIdentity.SetAttribute(ctx, path.Root("id"), sourceID)...) default: resp.Diagnostics.AddError( "Invalid Move State Request", diff --git a/internal/framework5provider/move_state_resource_test.go b/internal/framework5provider/move_state_resource_test.go index 4abc856..4258f93 100644 --- a/internal/framework5provider/move_state_resource_test.go +++ b/internal/framework5provider/move_state_resource_test.go @@ -10,7 +10,6 @@ import ( "github.com/hashicorp/terraform-plugin-go/tfprotov5" "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/plancheck" "github.com/hashicorp/terraform-plugin-testing/statecheck" "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" @@ -67,48 +66,3 @@ func TestMoveStateResource(t *testing.T) { }, }) } - -func TestMoveStateResource_identity(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.SkipBelow(tfversion.Version1_12_0), - }, - ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ - "framework": providerserver.NewProtocol5WithError(New()), - }, - Steps: []resource.TestStep{ - { - Config: `resource "framework_identity" "old" { - name = "tom" - }`, - ConfigStateChecks: []statecheck.StateCheck{ - statecheck.ExpectIdentity("framework_identity.old", map[string]knownvalue.Check{ - "id": knownvalue.StringExact("id-123"), - "name": knownvalue.StringExact("tom"), - }), - }, - }, - { - Config: ` - moved { - from = framework_identity.old - to = framework_move_state.new - } - resource "framework_move_state" "new" {} - `, - ConfigPlanChecks: resource.ConfigPlanChecks{ - PreApply: []plancheck.PlanCheck{ - plancheck.ExpectEmptyPlan(), - }, - }, - ConfigStateChecks: []statecheck.StateCheck{ - // The previous framework_identity.old identity should be moved to this new location, split into the new location identity and state. - statecheck.ExpectIdentity("framework_move_state.new", map[string]knownvalue.Check{ - "id": knownvalue.StringExact("id-123"), - }), - statecheck.ExpectKnownValue("framework_move_state.new", tfjsonpath.New("moved_random_string"), knownvalue.StringExact("tom")), - }, - }, - }, - }) -} diff --git a/internal/framework5provider/move_state_resource_with_identity.go b/internal/framework5provider/move_state_resource_with_identity.go new file mode 100644 index 0000000..9ea859e --- /dev/null +++ b/internal/framework5provider/move_state_resource_with_identity.go @@ -0,0 +1,160 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package framework + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/identityschema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +var _ resource.Resource = MoveStateResourceWithIdentity{} +var _ resource.ResourceWithIdentity = MoveStateResourceWithIdentity{} +var _ resource.ResourceWithMoveState = MoveStateResourceWithIdentity{} + +func NewMoveStateResourceWithIdentity() resource.Resource { + return &MoveStateResourceWithIdentity{} +} + +// MoveStateResourceWithIdentity is for testing the MoveResourceState RPC +// https://developer.hashicorp.com/terraform/plugin/framework/resources/state-move +type MoveStateResourceWithIdentity struct{} + +func (r MoveStateResourceWithIdentity) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_move_state_with_identity" +} + +func (r MoveStateResourceWithIdentity) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "moved_random_string": schema.StringAttribute{ + Computed: true, + }, + }, + } +} + +func (r MoveStateResourceWithIdentity) IdentitySchema(ctx context.Context, req resource.IdentitySchemaRequest, resp *resource.IdentitySchemaResponse) { + resp.IdentitySchema = identityschema.Schema{ + Attributes: map[string]identityschema.Attribute{ + "id": identityschema.StringAttribute{ + RequiredForImport: true, + }, + }, + } +} + +func (r MoveStateResourceWithIdentity) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data MoveStateResourceWithIdentityModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r MoveStateResourceWithIdentity) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data MoveStateResourceWithIdentityModel + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r MoveStateResourceWithIdentity) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data MoveStateResourceWithIdentityModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r MoveStateResourceWithIdentity) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +} + +func (r MoveStateResourceWithIdentity) MoveState(ctx context.Context) []resource.StateMover { + return []resource.StateMover{ + { + SourceSchema: &schema.Schema{ + Attributes: map[string]schema.Attribute{ + "result": schema.StringAttribute{}, + }, + }, + StateMover: func(ctx context.Context, req resource.MoveStateRequest, resp *resource.MoveStateResponse) { + switch req.SourceProviderAddress { + case "registry.terraform.io/hashicorp/framework": // Corner provider (testing identity moves) + if req.SourceTypeName != "framework_identity" { + resp.Diagnostics.AddError( + "Invalid Move State Request", + fmt.Sprintf("The \"framework_move_state\" resource can only be sourced from the \"random_string\" or \"framework_identity\" managed resources:\n\n"+ + "req.SourceProviderAddress: %q\n"+ + "req.SourceTypeName: %q\n", + req.SourceProviderAddress, + req.SourceTypeName, + ), + ) + return + } + + oldIdentityVal, err := req.SourceIdentity.Unmarshal( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + "name": tftypes.String, + }, + }, + ) + if err != nil { + resp.Diagnostics.AddError( + "Unexpected Move State Error", + fmt.Sprintf("Error decoding source identity: %s", err.Error()), + ) + return + } + + var sourceIdentityObj map[string]tftypes.Value + var sourceID, sourceName string + + oldIdentityVal.As(&sourceIdentityObj) //nolint:errcheck // This is just a quick test of grabbing raw identity data + sourceIdentityObj["id"].As(&sourceID) //nolint:errcheck // This is just a quick test of grabbing raw identity data + sourceIdentityObj["name"].As(&sourceName) //nolint:errcheck // This is just a quick test of grabbing raw identity data + + resp.Diagnostics.Append(resp.TargetState.SetAttribute(ctx, path.Root("moved_random_string"), sourceName)...) + resp.Diagnostics.Append(resp.TargetIdentity.SetAttribute(ctx, path.Root("id"), sourceID)...) + default: + resp.Diagnostics.AddError( + "Invalid Move State Request", + fmt.Sprintf("This test can only migrate resource state from hardcoded provider/resource types:\n\n"+ + "req.SourceProviderAddress: %q\n"+ + "req.SourceTypeName: %q\n", + req.SourceProviderAddress, + req.SourceTypeName, + ), + ) + } + }, + }, + } +} + +type MoveStateResourceWithIdentityModel struct { + MovedRandomString types.String `tfsdk:"moved_random_string"` +} diff --git a/internal/framework5provider/move_state_resource_with_identity_test.go b/internal/framework5provider/move_state_resource_with_identity_test.go new file mode 100644 index 0000000..06bdacb --- /dev/null +++ b/internal/framework5provider/move_state_resource_with_identity_test.go @@ -0,0 +1,62 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package framework + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func TestMoveStateResource_identity(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ + "framework": providerserver.NewProtocol5WithError(New()), + }, + Steps: []resource.TestStep{ + { + Config: `resource "framework_identity" "old" { + name = "tom" + }`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectIdentity("framework_identity.old", map[string]knownvalue.Check{ + "id": knownvalue.StringExact("id-123"), + "name": knownvalue.StringExact("tom"), + }), + }, + }, + { + Config: ` + moved { + from = framework_identity.old + to = framework_move_state_with_identity.new + } + resource "framework_move_state_with_identity" "new" {} + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + // The previous framework_identity.old identity should be moved to this new location, split into the new location identity and state. + statecheck.ExpectIdentity("framework_move_state_with_identity.new", map[string]knownvalue.Check{ + "id": knownvalue.StringExact("id-123"), + }), + statecheck.ExpectKnownValue("framework_move_state_with_identity.new", tfjsonpath.New("moved_random_string"), knownvalue.StringExact("tom")), + }, + }, + }, + }) +} diff --git a/internal/framework5provider/provider.go b/internal/framework5provider/provider.go index 71391bc..165f516 100644 --- a/internal/framework5provider/provider.go +++ b/internal/framework5provider/provider.go @@ -100,6 +100,7 @@ func (p *testProvider) Resources(_ context.Context) []func() resource.Resource { NewFloat64PrecisionResource, NewTFSDKReflectionResource, NewMoveStateResource, + NewMoveStateResourceWithIdentity, NewSetNestedBlockWithDefaultsResource, NewSetSemanticEqualityResource, NewCustomTypeResource, diff --git a/internal/framework6provider/move_state_resource.go b/internal/framework6provider/move_state_resource.go index 560c08d..5e10bd2 100644 --- a/internal/framework6provider/move_state_resource.go +++ b/internal/framework6provider/move_state_resource.go @@ -9,14 +9,11 @@ import ( "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/hashicorp/terraform-plugin-framework/resource/identityschema" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) var _ resource.Resource = MoveStateResource{} -var _ resource.ResourceWithIdentity = MoveStateResource{} var _ resource.ResourceWithMoveState = MoveStateResource{} func NewMoveStateResource() resource.Resource { @@ -41,16 +38,6 @@ func (r MoveStateResource) Schema(_ context.Context, _ resource.SchemaRequest, r } } -func (r MoveStateResource) IdentitySchema(ctx context.Context, req resource.IdentitySchemaRequest, resp *resource.IdentitySchemaResponse) { - resp.IdentitySchema = identityschema.Schema{ - Attributes: map[string]identityschema.Attribute{ - "id": identityschema.StringAttribute{ - RequiredForImport: true, - }, - }, - } -} - func (r MoveStateResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var data MoveStateResourceModel @@ -121,46 +108,7 @@ func (r MoveStateResource) MoveState(ctx context.Context) []resource.StateMover } resp.Diagnostics.Append(resp.TargetState.SetAttribute(ctx, path.Root("moved_random_string"), oldState.Result)...) - resp.Diagnostics.Append(resp.TargetIdentity.SetAttribute(ctx, path.Root("id"), oldState.Result)...) - case "registry.terraform.io/hashicorp/framework": // Corner provider (testing identity moves) - if req.SourceTypeName != "framework_identity" { - resp.Diagnostics.AddError( - "Invalid Move State Request", - fmt.Sprintf("This test can only migrate resource state from the \"framework_identity\" managed resource from the \"hashicorp/framework\" provider:\n\n"+ - "req.SourceProviderAddress: %q\n"+ - "req.SourceTypeName: %q\n", - req.SourceProviderAddress, - req.SourceTypeName, - ), - ) - return - } - - oldIdentityVal, err := req.SourceIdentity.Unmarshal( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "id": tftypes.String, - "name": tftypes.String, - }, - }, - ) - if err != nil { - resp.Diagnostics.AddError( - "Unexpected Move State Error", - fmt.Sprintf("Error decoding source identity: %s", err.Error()), - ) - return - } - - var sourceIdentityObj map[string]tftypes.Value - var sourceID, sourceName string - - oldIdentityVal.As(&sourceIdentityObj) //nolint:errcheck // This is just a quick test of grabbing raw identity data - sourceIdentityObj["id"].As(&sourceID) //nolint:errcheck // This is just a quick test of grabbing raw identity data - sourceIdentityObj["name"].As(&sourceName) //nolint:errcheck // This is just a quick test of grabbing raw identity data - resp.Diagnostics.Append(resp.TargetState.SetAttribute(ctx, path.Root("moved_random_string"), sourceName)...) - resp.Diagnostics.Append(resp.TargetIdentity.SetAttribute(ctx, path.Root("id"), sourceID)...) default: resp.Diagnostics.AddError( "Invalid Move State Request", diff --git a/internal/framework6provider/move_state_resource_test.go b/internal/framework6provider/move_state_resource_test.go index d834b9d..3404f12 100644 --- a/internal/framework6provider/move_state_resource_test.go +++ b/internal/framework6provider/move_state_resource_test.go @@ -10,7 +10,6 @@ import ( "github.com/hashicorp/terraform-plugin-go/tfprotov6" "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/plancheck" "github.com/hashicorp/terraform-plugin-testing/statecheck" "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" @@ -67,48 +66,3 @@ func TestMoveStateResource(t *testing.T) { }, }) } - -func TestMoveStateResource_identity(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.SkipBelow(tfversion.Version1_12_0), - }, - ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ - "framework": providerserver.NewProtocol6WithError(New()), - }, - Steps: []resource.TestStep{ - { - Config: `resource "framework_identity" "old" { - name = "tom" - }`, - ConfigStateChecks: []statecheck.StateCheck{ - statecheck.ExpectIdentity("framework_identity.old", map[string]knownvalue.Check{ - "id": knownvalue.StringExact("id-123"), - "name": knownvalue.StringExact("tom"), - }), - }, - }, - { - Config: ` - moved { - from = framework_identity.old - to = framework_move_state.new - } - resource "framework_move_state" "new" {} - `, - ConfigPlanChecks: resource.ConfigPlanChecks{ - PreApply: []plancheck.PlanCheck{ - plancheck.ExpectEmptyPlan(), - }, - }, - ConfigStateChecks: []statecheck.StateCheck{ - // The previous framework_identity.old identity should be moved to this new location, split into the new location identity and state. - statecheck.ExpectIdentity("framework_move_state.new", map[string]knownvalue.Check{ - "id": knownvalue.StringExact("id-123"), - }), - statecheck.ExpectKnownValue("framework_move_state.new", tfjsonpath.New("moved_random_string"), knownvalue.StringExact("tom")), - }, - }, - }, - }) -} diff --git a/internal/framework6provider/move_state_resource_with_identity.go b/internal/framework6provider/move_state_resource_with_identity.go new file mode 100644 index 0000000..7e38f0e --- /dev/null +++ b/internal/framework6provider/move_state_resource_with_identity.go @@ -0,0 +1,161 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package framework + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/identityschema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +var _ resource.Resource = MoveStateResourceWithIdentity{} +var _ resource.ResourceWithIdentity = MoveStateResourceWithIdentity{} +var _ resource.ResourceWithMoveState = MoveStateResourceWithIdentity{} + +func NewMoveStateResourceWithIdentity() resource.Resource { + return &MoveStateResourceWithIdentity{} +} + +// MoveStateResourceWithIdentity is for testing the MoveResourceState RPC +// https://developer.hashicorp.com/terraform/plugin/framework/resources/state-move +type MoveStateResourceWithIdentity struct{} + +func (r MoveStateResourceWithIdentity) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_move_state_with_identity" +} + +func (r MoveStateResourceWithIdentity) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "moved_random_string": schema.StringAttribute{ + Computed: true, + }, + }, + } +} + +func (r MoveStateResourceWithIdentity) IdentitySchema(ctx context.Context, req resource.IdentitySchemaRequest, resp *resource.IdentitySchemaResponse) { + resp.IdentitySchema = identityschema.Schema{ + Attributes: map[string]identityschema.Attribute{ + "id": identityschema.StringAttribute{ + RequiredForImport: true, + }, + }, + } +} + +func (r MoveStateResourceWithIdentity) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data MoveStateResourceWithIdentityModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r MoveStateResourceWithIdentity) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data MoveStateResourceWithIdentityModel + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r MoveStateResourceWithIdentity) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data MoveStateResourceWithIdentityModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r MoveStateResourceWithIdentity) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +} + +func (r MoveStateResourceWithIdentity) MoveState(ctx context.Context) []resource.StateMover { + return []resource.StateMover{ + { + SourceSchema: &schema.Schema{ + Attributes: map[string]schema.Attribute{ + "result": schema.StringAttribute{}, + }, + }, + StateMover: func(ctx context.Context, req resource.MoveStateRequest, resp *resource.MoveStateResponse) { + switch req.SourceProviderAddress { + case "registry.terraform.io/hashicorp/framework": // Corner provider (testing identity moves) + if req.SourceTypeName != "framework_identity" { + resp.Diagnostics.AddError( + "Invalid Move State Request", + fmt.Sprintf("This test can only migrate resource state from the \"framework_identity\" managed resource from the \"hashicorp/framework\" provider:\n\n"+ + "req.SourceProviderAddress: %q\n"+ + "req.SourceTypeName: %q\n", + req.SourceProviderAddress, + req.SourceTypeName, + ), + ) + return + } + + oldIdentityVal, err := req.SourceIdentity.Unmarshal( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + "name": tftypes.String, + }, + }, + ) + if err != nil { + resp.Diagnostics.AddError( + "Unexpected Move State Error", + fmt.Sprintf("Error decoding source identity: %s", err.Error()), + ) + return + } + + var sourceIdentityObj map[string]tftypes.Value + var sourceID, sourceName string + + oldIdentityVal.As(&sourceIdentityObj) //nolint:errcheck // This is just a quick test of grabbing raw identity data + sourceIdentityObj["id"].As(&sourceID) //nolint:errcheck // This is just a quick test of grabbing raw identity data + sourceIdentityObj["name"].As(&sourceName) //nolint:errcheck // This is just a quick test of grabbing raw identity data + + resp.Diagnostics.Append(resp.TargetState.SetAttribute(ctx, path.Root("moved_random_string"), sourceName)...) + resp.Diagnostics.Append(resp.TargetIdentity.SetAttribute(ctx, path.Root("id"), sourceID)...) + + default: + resp.Diagnostics.AddError( + "Invalid Move State Request", + fmt.Sprintf("This test can only migrate resource state from hardcoded provider/resource types:\n\n"+ + "req.SourceProviderAddress: %q\n"+ + "req.SourceTypeName: %q\n", + req.SourceProviderAddress, + req.SourceTypeName, + ), + ) + } + }, + }, + } +} + +type MoveStateResourceWithIdentityModel struct { + MovedRandomString types.String `tfsdk:"moved_random_string"` +} diff --git a/internal/framework6provider/move_state_resource_with_identity_test.go b/internal/framework6provider/move_state_resource_with_identity_test.go new file mode 100644 index 0000000..aa51026 --- /dev/null +++ b/internal/framework6provider/move_state_resource_with_identity_test.go @@ -0,0 +1,62 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package framework + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func TestMoveStateResource_identity(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "framework": providerserver.NewProtocol6WithError(New()), + }, + Steps: []resource.TestStep{ + { + Config: `resource "framework_identity" "old" { + name = "tom" + }`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectIdentity("framework_identity.old", map[string]knownvalue.Check{ + "id": knownvalue.StringExact("id-123"), + "name": knownvalue.StringExact("tom"), + }), + }, + }, + { + Config: ` + moved { + from = framework_identity.old + to = framework_move_state_with_identity.new + } + resource "framework_move_state_with_identity" "new" {} + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + // The previous framework_identity.old identity should be moved to this new location, split into the new location identity and state. + statecheck.ExpectIdentity("framework_move_state_with_identity.new", map[string]knownvalue.Check{ + "id": knownvalue.StringExact("id-123"), + }), + statecheck.ExpectKnownValue("framework_move_state_with_identity.new", tfjsonpath.New("moved_random_string"), knownvalue.StringExact("tom")), + }, + }, + }, + }) +} diff --git a/internal/framework6provider/provider.go b/internal/framework6provider/provider.go index 26b24d6..a937067 100644 --- a/internal/framework6provider/provider.go +++ b/internal/framework6provider/provider.go @@ -97,6 +97,7 @@ func (p *testProvider) Resources(_ context.Context) []func() resource.Resource { NewFloat64PrecisionResource, NewTFSDKReflectionResource, NewMoveStateResource, + NewMoveStateResourceWithIdentity, NewSetNestedBlockWithDefaultsResource, NewSetNestedAttributeWithDefaultsResource, NewSetSemanticEqualityResource,