From aa4c3e990cb975654773dead1acc287444528d7f Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Mon, 29 Jan 2024 17:09:30 +0100 Subject: [PATCH] all: Initial MoveResourceState implementation (#1307) Reference: https://github.com/hashicorp/terraform-plugin-go/pull/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. --- .../unreleased/NOTES-20240129-084840.yaml | 8 +++ helper/schema/grpc_provider.go | 39 ++++++++++ helper/schema/grpc_provider_test.go | 71 ++++++++++++++++++- 3 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 .changes/unreleased/NOTES-20240129-084840.yaml diff --git a/.changes/unreleased/NOTES-20240129-084840.yaml b/.changes/unreleased/NOTES-20240129-084840.yaml new file mode 100644 index 0000000000..ed8deb16f8 --- /dev/null +++ b/.changes/unreleased/NOTES-20240129-084840.yaml @@ -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: "1307" diff --git a/helper/schema/grpc_provider.go b/helper/schema/grpc_provider.go index 4ba774a1f2..52661c9b16 100644 --- a/helper/schema/grpc_provider.go +++ b/helper/schema/grpc_provider.go @@ -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, @@ -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{} diff --git a/helper/schema/grpc_provider_test.go b/helper/schema/grpc_provider_test.go index 537d8fa1c8..39b4b078a1 100644 --- a/helper/schema/grpc_provider_test.go +++ b/helper/schema/grpc_provider_test.go @@ -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() @@ -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,