Skip to content

Commit

Permalink
all: Initial MoveResourceState implementation
Browse files Browse the repository at this point in the history
Reference: hashicorp/terraform-plugin-go#351
Reference: https://developer.hashicorp.com/terraform/plugin/framework/migrating

The next versions of the plugin protocol (5.5/6.5) include support for moving resource state across resource types. The terraform-plugin-sdk Go module will not be receiving this feature, however this Go module must be updated to handle the new RPC with errors.

Provider developers can implement move resource state in their terraform-plugin-sdk based providers by following the framework migration documentation to introduce terraform-plugin-mux and migrating the resource type to terraform-plugin-framework.
  • Loading branch information
bflad committed Jan 29, 2024
1 parent 9c3358e commit da34694
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 3 deletions.
8 changes: 8 additions & 0 deletions .changes/unreleased/NOTES-20240129-084840.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
kind: NOTES
body: 'helper/schema: While this Go module will not receive support for moving
resource state across resource types, the provider server is updated to handle
the new operation, which will be required to prevent errors when updating
terraform-plugin-framework or terraform-plugin-mux in the future.'
time: 2024-01-29T08:48:40.990566-05:00
custom:
Issue: "9999"
39 changes: 39 additions & 0 deletions helper/schema/grpc_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ const (
newExtraKey = "_new_extra_shim"
)

// Verify provider server interface implementation.
var _ tfprotov5.ProviderServer = (*GRPCProviderServer)(nil)

func NewGRPCProviderServer(p *Provider) *GRPCProviderServer {
return &GRPCProviderServer{
provider: p,
Expand Down Expand Up @@ -1208,6 +1211,42 @@ func (s *GRPCProviderServer) ImportResourceState(ctx context.Context, req *tfpro
return resp, nil
}

func (s *GRPCProviderServer) MoveResourceState(ctx context.Context, req *tfprotov5.MoveResourceStateRequest) (*tfprotov5.MoveResourceStateResponse, error) {
if req == nil {
return nil, fmt.Errorf("MoveResourceState request is nil")
}

ctx = logging.InitContext(ctx)

logging.HelperSchemaTrace(ctx, "Returning error for MoveResourceState")

resp := &tfprotov5.MoveResourceStateResponse{}

_, ok := s.provider.ResourcesMap[req.TargetTypeName]

if !ok {
resp.Diagnostics = []*tfprotov5.Diagnostic{
{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: "Unknown Resource Type",
Detail: fmt.Sprintf("The %q resource type is not supported by this provider.", req.TargetTypeName),
},
}

return resp, nil
}

resp.Diagnostics = []*tfprotov5.Diagnostic{
{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: "Move Resource State Not Supported",
Detail: fmt.Sprintf("The %q resource type does not support moving resource state across resource types.", req.TargetTypeName),
},
}

return resp, nil
}

func (s *GRPCProviderServer) ReadDataSource(ctx context.Context, req *tfprotov5.ReadDataSourceRequest) (*tfprotov5.ReadDataSourceResponse, error) {
ctx = logging.InitContext(ctx)
resp := &tfprotov5.ReadDataSourceResponse{}
Expand Down
71 changes: 68 additions & 3 deletions helper/schema/grpc_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

// The GRPCProviderServer will directly implement the go protobuf server
var _ tfprotov5.ProviderServer = (*GRPCProviderServer)(nil)

func TestGRPCProviderServerConfigureProvider(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -2209,6 +2206,74 @@ func TestGRPCProviderServerGetMetadata(t *testing.T) {
}
}

func TestGRPCProviderServerMoveResourceState(t *testing.T) {
t.Parallel()

testCases := map[string]struct {
server *GRPCProviderServer
request *tfprotov5.MoveResourceStateRequest
expected *tfprotov5.MoveResourceStateResponse
}{
"nil": {
server: NewGRPCProviderServer(&Provider{}),
request: nil,
expected: nil,
},
"request-TargetTypeName-missing": {
server: NewGRPCProviderServer(&Provider{}),
request: &tfprotov5.MoveResourceStateRequest{
TargetTypeName: "test_resource",
},
expected: &tfprotov5.MoveResourceStateResponse{
Diagnostics: []*tfprotov5.Diagnostic{
{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: "Unknown Resource Type",
Detail: "The \"test_resource\" resource type is not supported by this provider.",
},
},
},
},
"request-TargetTypeName": {
server: NewGRPCProviderServer(&Provider{
ResourcesMap: map[string]*Resource{
"test_resource": {},
},
}),
request: &tfprotov5.MoveResourceStateRequest{
TargetTypeName: "test_resource",
},
expected: &tfprotov5.MoveResourceStateResponse{
Diagnostics: []*tfprotov5.Diagnostic{
{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: "Move Resource State Not Supported",
Detail: "The \"test_resource\" resource type does not support moving resource state across resource types.",
},
},
},
},
}

for name, testCase := range testCases {
name, testCase := name, testCase

t.Run(name, func(t *testing.T) {
t.Parallel()

resp, err := testCase.server.MoveResourceState(context.Background(), testCase.request)

if testCase.request != nil && err != nil {
t.Fatalf("unexpected error: %s", err)
}

if diff := cmp.Diff(resp, testCase.expected); diff != "" {
t.Errorf("unexpected difference: %s", diff)
}
})
}
}

func TestUpgradeState_jsonState(t *testing.T) {
r := &Resource{
SchemaVersion: 2,
Expand Down

0 comments on commit da34694

Please sign in to comment.