Skip to content

Commit fe2a56b

Browse files
committed
internal: Migrate ImportResourceState RPC to fromproto6, fwserver, and toproto6
Reference: #299
1 parent cf2868d commit fe2a56b

10 files changed

+538
-137
lines changed
+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package fromproto6
2+
3+
import (
4+
"context"
5+
6+
"github.com/hashicorp/terraform-plugin-framework/diag"
7+
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
8+
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
9+
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
10+
"github.com/hashicorp/terraform-plugin-go/tftypes"
11+
)
12+
13+
// ImportResourceStateRequest returns the *fwserver.ImportResourceStateRequest
14+
// equivalent of a *tfprotov6.ImportResourceStateRequest.
15+
func ImportResourceStateRequest(ctx context.Context, proto6 *tfprotov6.ImportResourceStateRequest, resourceType tfsdk.ResourceType, resourceSchema *tfsdk.Schema) (*fwserver.ImportResourceStateRequest, diag.Diagnostics) {
16+
if proto6 == nil {
17+
return nil, nil
18+
}
19+
20+
var diags diag.Diagnostics
21+
22+
// Panic prevention here to simplify the calling implementations.
23+
// This should not happen, but just in case.
24+
if resourceSchema == nil {
25+
diags.AddError(
26+
"Unable to Create Empty State",
27+
"An unexpected error was encountered when creating the empty state. "+
28+
"This is always an issue in the Terraform Provider SDK used to implement the provider and should be reported to the provider developers.\n\n"+
29+
"Please report this to the provider developer:\n\n"+
30+
"Missing schema.",
31+
)
32+
33+
return nil, diags
34+
}
35+
36+
fw := &fwserver.ImportResourceStateRequest{
37+
EmptyState: tfsdk.State{
38+
Raw: tftypes.NewValue(resourceSchema.TerraformType(ctx), nil),
39+
Schema: *resourceSchema,
40+
},
41+
ID: proto6.ID,
42+
ResourceType: resourceType,
43+
TypeName: proto6.TypeName,
44+
}
45+
46+
return fw, diags
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package fromproto6_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/google/go-cmp/cmp"
8+
"github.com/hashicorp/terraform-plugin-framework/diag"
9+
"github.com/hashicorp/terraform-plugin-framework/internal/fromproto6"
10+
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
11+
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
12+
"github.com/hashicorp/terraform-plugin-framework/types"
13+
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
14+
"github.com/hashicorp/terraform-plugin-go/tftypes"
15+
)
16+
17+
func TestImportResourceStateRequest(t *testing.T) {
18+
t.Parallel()
19+
20+
testFwSchema := &tfsdk.Schema{
21+
Attributes: map[string]tfsdk.Attribute{
22+
"test_attribute": {
23+
Required: true,
24+
Type: types.StringType,
25+
},
26+
},
27+
}
28+
29+
testFwEmptyState := tfsdk.State{
30+
Raw: tftypes.NewValue(testFwSchema.TerraformType(context.Background()), nil),
31+
Schema: *testFwSchema,
32+
}
33+
34+
testCases := map[string]struct {
35+
input *tfprotov6.ImportResourceStateRequest
36+
resourceSchema *tfsdk.Schema
37+
resourceType tfsdk.ResourceType
38+
expected *fwserver.ImportResourceStateRequest
39+
expectedDiagnostics diag.Diagnostics
40+
}{
41+
"nil": {
42+
input: nil,
43+
expected: nil,
44+
},
45+
"emptystate": {
46+
input: &tfprotov6.ImportResourceStateRequest{},
47+
resourceSchema: testFwSchema,
48+
expected: &fwserver.ImportResourceStateRequest{
49+
EmptyState: testFwEmptyState,
50+
},
51+
},
52+
"emptystate-missing-schema": {
53+
input: &tfprotov6.ImportResourceStateRequest{},
54+
expected: nil,
55+
expectedDiagnostics: diag.Diagnostics{
56+
diag.NewErrorDiagnostic(
57+
"Unable to Create Empty State",
58+
"An unexpected error was encountered when creating the empty state. "+
59+
"This is always an issue in the Terraform Provider SDK used to implement the provider and should be reported to the provider developers.\n\n"+
60+
"Please report this to the provider developer:\n\n"+
61+
"Missing schema.",
62+
),
63+
},
64+
},
65+
"id": {
66+
input: &tfprotov6.ImportResourceStateRequest{
67+
ID: "test-id",
68+
},
69+
resourceSchema: testFwSchema,
70+
expected: &fwserver.ImportResourceStateRequest{
71+
EmptyState: testFwEmptyState,
72+
ID: "test-id",
73+
},
74+
},
75+
"typename": {
76+
input: &tfprotov6.ImportResourceStateRequest{
77+
TypeName: "test_resource",
78+
},
79+
resourceSchema: testFwSchema,
80+
expected: &fwserver.ImportResourceStateRequest{
81+
EmptyState: testFwEmptyState,
82+
TypeName: "test_resource",
83+
},
84+
},
85+
}
86+
87+
for name, testCase := range testCases {
88+
name, testCase := name, testCase
89+
90+
t.Run(name, func(t *testing.T) {
91+
t.Parallel()
92+
93+
got, diags := fromproto6.ImportResourceStateRequest(context.Background(), testCase.input, testCase.resourceType, testCase.resourceSchema)
94+
95+
if diff := cmp.Diff(got, testCase.expected); diff != "" {
96+
t.Errorf("unexpected difference: %s", diff)
97+
}
98+
99+
if diff := cmp.Diff(diags, testCase.expectedDiagnostics); diff != "" {
100+
t.Errorf("unexpected diagnostics difference: %s", diff)
101+
}
102+
})
103+
}
104+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package fwserver
2+
3+
import (
4+
"context"
5+
6+
"github.com/hashicorp/terraform-plugin-framework/diag"
7+
"github.com/hashicorp/terraform-plugin-framework/internal/logging"
8+
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
9+
)
10+
11+
// ImportedResource represents a resource that was imported.
12+
type ImportedResource struct {
13+
Private []byte
14+
State tfsdk.State
15+
TypeName string
16+
}
17+
18+
// ImportResourceStateRequest is the framework server request for the
19+
// ImportResourceState RPC.
20+
type ImportResourceStateRequest struct {
21+
ID string
22+
ResourceType tfsdk.ResourceType
23+
24+
// EmptyState is an empty State for the resource schema. This is used to
25+
// initialize the ImportedResource State of the ImportResourceStateResponse
26+
// and allow the framework server to verify that the provider updated the
27+
// state after the provider defined logic.
28+
EmptyState tfsdk.State
29+
30+
// TypeName is the resource type name, which is necessary for populating
31+
// the ImportedResource TypeName of the ImportResourceStateResponse.
32+
TypeName string
33+
}
34+
35+
// ImportResourceStateResponse is the framework server response for the
36+
// ImportResourceState RPC.
37+
type ImportResourceStateResponse struct {
38+
Diagnostics diag.Diagnostics
39+
ImportedResources []ImportedResource
40+
}
41+
42+
// ImportResourceState implements the framework server ImportResourceState RPC.
43+
func (s *Server) ImportResourceState(ctx context.Context, req *ImportResourceStateRequest, resp *ImportResourceStateResponse) {
44+
if req == nil {
45+
return
46+
}
47+
48+
// Always instantiate new Resource instances.
49+
logging.FrameworkDebug(ctx, "Calling provider defined ResourceType NewResource")
50+
resource, diags := req.ResourceType.NewResource(ctx, s.Provider)
51+
logging.FrameworkDebug(ctx, "Called provider defined ResourceType NewResource")
52+
53+
resp.Diagnostics.Append(diags...)
54+
55+
if resp.Diagnostics.HasError() {
56+
return
57+
}
58+
59+
resourceWithImportState, ok := resource.(tfsdk.ResourceWithImportState)
60+
61+
if !ok {
62+
// If there is a feature request for customizing this messaging,
63+
// provider developers can implement a ImportState method that
64+
// immediately returns a custom error diagnostic.
65+
//
66+
// However, implementing the ImportState method could cause issues
67+
// with automated documentation generation, which likely would check
68+
// if the resource implements the ResourceWithImportState interface.
69+
// Instead, a separate "ResourceWithoutImportState" interface could be
70+
// created with a method such as:
71+
// ImportNotImplementedMessage(context.Context) string.
72+
resp.Diagnostics.AddError(
73+
"Resource Import Not Implemented",
74+
"This resource does not support import. Please contact the provider developer for additional information.",
75+
)
76+
return
77+
}
78+
79+
importReq := tfsdk.ImportResourceStateRequest{
80+
ID: req.ID,
81+
}
82+
importResp := tfsdk.ImportResourceStateResponse{
83+
State: tfsdk.State{
84+
Raw: req.EmptyState.Raw.Copy(),
85+
Schema: req.EmptyState.Schema,
86+
},
87+
}
88+
89+
logging.FrameworkDebug(ctx, "Calling provider defined Resource ImportState")
90+
resourceWithImportState.ImportState(ctx, importReq, &importResp)
91+
logging.FrameworkDebug(ctx, "Called provider defined Resource ImportState")
92+
93+
resp.Diagnostics.Append(importResp.Diagnostics...)
94+
95+
if resp.Diagnostics.HasError() {
96+
return
97+
}
98+
99+
if importResp.State.Raw.Equal(req.EmptyState.Raw) {
100+
resp.Diagnostics.AddError(
101+
"Missing Resource Import State",
102+
"An unexpected error was encountered when importing the resource. This is always a problem with the provider. Please give the following information to the provider developer:\n\n"+
103+
"Resource ImportState method returned no State in response. If import is intentionally not supported, remove the Resource type ImportState method or return an error.",
104+
)
105+
return
106+
}
107+
108+
resp.ImportedResources = []ImportedResource{
109+
{
110+
State: importResp.State,
111+
TypeName: req.TypeName,
112+
},
113+
}
114+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package fwserver_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/google/go-cmp/cmp"
8+
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
9+
"github.com/hashicorp/terraform-plugin-framework/internal/testing/emptyprovider"
10+
)
11+
12+
// TODO: Migrate tfsdk.Provider bits of proto6server.testProviderServer to
13+
// new internal/testing/provider.Provider that allows customization of all
14+
// method implementations via struct fields. Then, create additional test
15+
// cases in this unit test.
16+
//
17+
// For now this testing is covered by proto6server.ImportResourceState.
18+
//
19+
// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/215
20+
func TestServerImportResourceState(t *testing.T) {
21+
t.Parallel()
22+
23+
testCases := map[string]struct {
24+
server *fwserver.Server
25+
request *fwserver.ImportResourceStateRequest
26+
expectedResponse *fwserver.ImportResourceStateResponse
27+
}{
28+
"empty-provider": {
29+
server: &fwserver.Server{
30+
Provider: &emptyprovider.Provider{},
31+
},
32+
expectedResponse: &fwserver.ImportResourceStateResponse{},
33+
},
34+
}
35+
36+
for name, testCase := range testCases {
37+
name, testCase := name, testCase
38+
39+
t.Run(name, func(t *testing.T) {
40+
t.Parallel()
41+
42+
response := &fwserver.ImportResourceStateResponse{}
43+
testCase.server.ImportResourceState(context.Background(), testCase.request, response)
44+
45+
if diff := cmp.Diff(response, testCase.expectedResponse); diff != "" {
46+
t.Errorf("unexpected difference: %s", diff)
47+
}
48+
})
49+
}
50+
}

0 commit comments

Comments
 (0)