Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/hashicorp/go-memdb v1.3.5
github.com/hashicorp/go-version v1.7.0
github.com/hashicorp/terraform-json v0.24.1-0.20250314103308-f86d5e36f4ab
github.com/hashicorp/terraform-plugin-framework v1.15.0-beta.1
github.com/hashicorp/terraform-plugin-framework v1.15.0-beta.1.0.20250502130454-17f1faf8787c
github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0
github.com/hashicorp/terraform-plugin-framework-nettypes v0.2.0
github.com/hashicorp/terraform-plugin-framework-timeouts v0.5.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ github.com/hashicorp/terraform-exec v0.23.0 h1:MUiBM1s0CNlRFsCLJuM5wXZrzA3MnPYEs
github.com/hashicorp/terraform-exec v0.23.0/go.mod h1:mA+qnx1R8eePycfwKkCRk3Wy65mwInvlpAeOwmA7vlY=
github.com/hashicorp/terraform-json v0.24.1-0.20250314103308-f86d5e36f4ab h1:5Qpuprk76zkVEdTCtfoPjUc+1AeUxlgkF6sWTr7qLDs=
github.com/hashicorp/terraform-json v0.24.1-0.20250314103308-f86d5e36f4ab/go.mod h1:sMKS8fiRDX4rVlR6EJUMudg1WcanxCMoWwTLkgZP/vc=
github.com/hashicorp/terraform-plugin-framework v1.15.0-beta.1 h1:lX4qacaJc8dqUzEaOALeUW0Gvv0ACs9myvN1WQ4rRgU=
github.com/hashicorp/terraform-plugin-framework v1.15.0-beta.1/go.mod h1:SNnBQzWTh3ydNHBJF8eLVHlm/2gu+RBG508LCfCSVwI=
github.com/hashicorp/terraform-plugin-framework v1.15.0-beta.1.0.20250502130454-17f1faf8787c h1:3yQ23nFnxkj6D6ca4wHr13jV6+USVgak6+pj6x4c6dM=
github.com/hashicorp/terraform-plugin-framework v1.15.0-beta.1.0.20250502130454-17f1faf8787c/go.mod h1:ZlVO3Sv2z0tnLP+f3/30hOFwaiM/BNye0H8HFRN1nDo=
github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0 h1:SJXL5FfJJm17554Kpt9jFXngdM6fXbnUnZ6iT2IeiYA=
github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0/go.mod h1:p0phD0IYhsu9bR4+6OetVvvH59I6LwjXGnTVEr8ox6E=
github.com/hashicorp/terraform-plugin-framework-nettypes v0.2.0 h1:Zap24rkky7SvNGGNYHMKFhAriP6+6riI21BMYOYgLRE=
Expand Down
35 changes: 33 additions & 2 deletions internal/framework5provider/identity_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@ 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/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
)

var _ resource.Resource = IdentityResource{}
var _ resource.ResourceWithIdentity = IdentityResource{}
var _ resource.ResourceWithImportState = IdentityResource{}

func NewIdentityResource() resource.Resource {
return &IdentityResource{}
Expand Down Expand Up @@ -42,13 +46,23 @@ func (r IdentityResource) Metadata(_ context.Context, req resource.MetadataReque
func (r IdentityResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"name": schema.StringAttribute{
Required: true,
},
},
}
}

func (r IdentityResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughWithIdentity(ctx, path.Root("id"), path.Root("id"), req, resp)
}

func (r IdentityResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data IdentityResourceModel

Expand All @@ -59,20 +73,36 @@ func (r IdentityResource) Create(ctx context.Context, req resource.CreateRequest

resp.Diagnostics.Append(resp.Identity.Set(ctx, IdentityResourceIdentityModel{
ID: types.StringValue("id-123"),
Name: types.StringValue(fmt.Sprintf("my name is %s", data.Name.ValueString())),
Name: types.StringValue(data.Name.ValueString()),
})...)

data.ID = types.StringValue("id-123")
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (r IdentityResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data IdentityResourceModel

resp.Diagnostics.Append(req.State.Get(ctx, &data)...)

if resp.Diagnostics.HasError() {
return
}

if data.ID.ValueString() != "id-123" {
resp.Diagnostics.AddAttributeError(
path.Root("id"),
"Unexpected ID value",
fmt.Sprintf("Expected ID to be \"id-123\", got: %s", data.ID.String()),
)
return
}

data.Name = types.StringValue("john")
resp.Diagnostics.Append(resp.Identity.Set(ctx, IdentityResourceIdentityModel{
ID: types.StringValue("id-123"),
Name: types.StringValue("john"),
})...)

resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

Expand All @@ -92,6 +122,7 @@ func (r IdentityResource) Delete(ctx context.Context, req resource.DeleteRequest
}

type IdentityResourceModel struct {
ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
}

Expand Down
55 changes: 40 additions & 15 deletions internal/framework5provider/identity_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@
package framework

import (
"regexp"
"testing"

"github.com/hashicorp/go-version"
"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 TestIdentityResource(t *testing.T) {
resource.UnitTest(t, resource.TestCase{
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.SkipBelow(version.Must(version.NewVersion("1.12.0-beta1"))),
tfversion.SkipBelow(tfversion.Version1_12_0),
},
ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){
"framework": providerserver.NewProtocol5WithError(New()),
Expand All @@ -32,25 +32,50 @@ func TestIdentityResource(t *testing.T) {
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectIdentity("framework_identity.test", map[string]knownvalue.Check{
"id": knownvalue.StringExact("id-123"),
"name": knownvalue.StringExact("my name is john"),
"name": knownvalue.StringExact("john"),
}),
statecheck.ExpectKnownValue("framework_identity.test", tfjsonpath.New("id"), knownvalue.StringExact("id-123")),
statecheck.ExpectKnownValue("framework_identity.test", tfjsonpath.New("name"), knownvalue.StringExact("john")),
},
},
// Typically you don't need to test all of these different import methods,
// but this just a smoke test for passing state + identity data through.
{
ImportState: true,
ResourceName: "framework_identity.test",
ImportStateKind: resource.ImportCommandWithID,
},
{
ImportState: true,
ResourceName: "framework_identity.test",
ImportStateKind: resource.ImportBlockWithID,
},
{
ImportState: true,
ResourceName: "framework_identity.test",
ImportStateKind: resource.ImportBlockWithResourceIdentity,
},
},
})
}

func TestIdentityResource_identity_changes(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" "test" {
name = "jerry"
}`,
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectResourceAction("framework_identity.test", plancheck.ResourceActionUpdate),
},
},
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectIdentity("framework_identity.test", map[string]knownvalue.Check{
"id": knownvalue.StringExact("id-123"),
"name": knownvalue.StringExact("my name is john"), // doesn't get updated, since identity should not change.
}),
},
// The resource is hardcoded to refresh with the same identity, based off the name attribute during create.
// Resources are currently not allowed to change identities at any time, so core will return an error message
// after the post-apply refresh.
ExpectError: regexp.MustCompile(`Error: Provider produced different identity`),
},
},
})
Expand Down
87 changes: 76 additions & 11 deletions internal/framework5provider/move_state_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ package framework
import (
"context"
"fmt"
"strings"

"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 {
Expand All @@ -39,6 +41,16 @@ 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

Expand Down Expand Up @@ -87,25 +99,78 @@ func (r MoveStateResource) MoveState(ctx context.Context) []resource.StateMover
},
},
StateMover: func(ctx context.Context, req resource.MoveStateRequest, resp *resource.MoveStateResponse) {
if !strings.HasSuffix(req.SourceProviderAddress, "hashicorp/random") || req.SourceTypeName != "random_string" {
switch req.SourceProviderAddress {
case "registry.terraform.io/hashicorp/random": // Random provider (testing state moves)
if req.SourceTypeName != "random_string" {
resp.Diagnostics.AddError(
"Invalid Move State Request",
fmt.Sprintf("This test can only migrate resource state from the \"random_string\" managed resource from the \"hashicorp/random\" provider:\n\n"+
"req.SourceProviderAddress: %q\n"+
"req.SourceTypeName: %q\n",
req.SourceProviderAddress,
req.SourceTypeName,
),
)
return
}

var oldState RandomStringResourceModel
resp.Diagnostics.Append(req.SourceState.Get(ctx, &oldState)...)
if resp.Diagnostics.HasError() {
return
}

resp.Diagnostics.Append(resp.TargetState.SetAttribute(ctx, path.Root("moved_random_string"), 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",
fmt.Sprintf("This test can only migrate resource state from the \"random_string\" managed resource from the \"hashicorp/random\" provider:\n\n"+
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,
),
)
}

var oldState RandomStringResourceModel
resp.Diagnostics.Append(req.SourceState.Get(ctx, &oldState)...)
if resp.Diagnostics.HasError() {
return
}

resp.Diagnostics.Append(resp.TargetState.SetAttribute(ctx, path.Root("moved_random_string"), oldState.Result)...)
},
},
}
Expand Down
46 changes: 46 additions & 0 deletions internal/framework5provider/move_state_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ 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"
Expand Down Expand Up @@ -66,3 +67,48 @@ 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 = "john"
}`,
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectIdentity("framework_identity.old", map[string]knownvalue.Check{
"id": knownvalue.StringExact("id-123"),
"name": knownvalue.StringExact("john"),
}),
},
},
{
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("john")),
},
},
},
})
}
Loading
Loading