Skip to content

Commit e3dd2b6

Browse files
committed
ReadResource RPC implementation for v6
1 parent d07f756 commit e3dd2b6

File tree

10 files changed

+630
-3
lines changed

10 files changed

+630
-3
lines changed

internal/fromproto6/readresource.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import (
1717

1818
// ReadResourceRequest returns the *fwserver.ReadResourceRequest
1919
// equivalent of a *tfprotov6.ReadResourceRequest.
20-
func ReadResourceRequest(ctx context.Context, proto6 *tfprotov6.ReadResourceRequest, reqResource resource.Resource, resourceSchema fwschema.Schema, providerMetaSchema fwschema.Schema) (*fwserver.ReadResourceRequest, diag.Diagnostics) {
20+
func ReadResourceRequest(ctx context.Context, proto6 *tfprotov6.ReadResourceRequest, reqResource resource.Resource, resourceSchema fwschema.Schema, providerMetaSchema fwschema.Schema, identitySchema fwschema.Schema) (*fwserver.ReadResourceRequest, diag.Diagnostics) {
2121
if proto6 == nil {
2222
return nil, nil
2323
}
@@ -26,6 +26,7 @@ func ReadResourceRequest(ctx context.Context, proto6 *tfprotov6.ReadResourceRequ
2626

2727
fw := &fwserver.ReadResourceRequest{
2828
Resource: reqResource,
29+
IdentitySchema: identitySchema,
2930
ClientCapabilities: ReadResourceClientCapabilities(proto6.ClientCapabilities),
3031
}
3132

@@ -35,6 +36,12 @@ func ReadResourceRequest(ctx context.Context, proto6 *tfprotov6.ReadResourceRequ
3536

3637
fw.CurrentState = currentState
3738

39+
currentIdentity, currentIdentityDiags := ResourceIdentity(ctx, proto6.CurrentIdentity, identitySchema)
40+
41+
diags.Append(currentIdentityDiags...)
42+
43+
fw.CurrentIdentity = currentIdentity
44+
3845
providerMeta, providerMetaDiags := ProviderMeta(ctx, proto6.ProviderMeta, providerMetaSchema)
3946

4047
diags.Append(providerMetaDiags...)

internal/fromproto6/readresource_test.go

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
1818
"github.com/hashicorp/terraform-plugin-framework/internal/privatestate"
1919
"github.com/hashicorp/terraform-plugin-framework/resource"
20+
"github.com/hashicorp/terraform-plugin-framework/resource/identityschema"
2021
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
2122
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
2223
)
@@ -48,6 +49,30 @@ func TestReadResourceRequest(t *testing.T) {
4849
},
4950
}
5051

52+
testIdentityProto6Type := tftypes.Object{
53+
AttributeTypes: map[string]tftypes.Type{
54+
"test_identity_attribute": tftypes.String,
55+
},
56+
}
57+
58+
testIdentityProto6Value := tftypes.NewValue(testIdentityProto6Type, map[string]tftypes.Value{
59+
"test_identity_attribute": tftypes.NewValue(tftypes.String, "id-123"),
60+
})
61+
62+
testIdentityProto6DynamicValue, err := tfprotov6.NewDynamicValue(testIdentityProto6Type, testIdentityProto6Value)
63+
64+
if err != nil {
65+
t.Fatalf("unexpected error calling tfprotov6.NewDynamicValue(): %s", err)
66+
}
67+
68+
testIdentitySchema := identityschema.Schema{
69+
Attributes: map[string]identityschema.Attribute{
70+
"test_identity_attribute": identityschema.StringAttribute{
71+
RequiredForImport: true,
72+
},
73+
},
74+
}
75+
5176
testProviderKeyValue := privatestate.MustMarshalToJson(map[string][]byte{
5277
"providerKeyOne": []byte(`{"pKeyOne": {"k0": "zero", "k1": 1}}`),
5378
})
@@ -59,6 +84,7 @@ func TestReadResourceRequest(t *testing.T) {
5984
testCases := map[string]struct {
6085
input *tfprotov6.ReadResourceRequest
6186
resourceSchema fwschema.Schema
87+
identitySchema fwschema.Schema
6288
resource resource.Resource
6389
providerMetaSchema fwschema.Schema
6490
expected *fwserver.ReadResourceRequest
@@ -99,6 +125,37 @@ func TestReadResourceRequest(t *testing.T) {
99125
},
100126
},
101127
},
128+
"currentidentity-missing-schema": {
129+
input: &tfprotov6.ReadResourceRequest{
130+
CurrentIdentity: &tfprotov6.ResourceIdentityData{
131+
IdentityData: &testIdentityProto6DynamicValue,
132+
},
133+
},
134+
expected: &fwserver.ReadResourceRequest{},
135+
expectedDiagnostics: diag.Diagnostics{
136+
diag.NewErrorDiagnostic(
137+
"Unable to Convert Resource Identity",
138+
"An unexpected error was encountered when converting the resource identity from the protocol type. "+
139+
"Identity data was sent in the protocol to a resource that doesn't support identity.\n\n"+
140+
"This is always a problem with Terraform or terraform-plugin-framework. Please report this to the provider developer.",
141+
),
142+
},
143+
},
144+
"currentidentity": {
145+
input: &tfprotov6.ReadResourceRequest{
146+
CurrentIdentity: &tfprotov6.ResourceIdentityData{
147+
IdentityData: &testIdentityProto6DynamicValue,
148+
},
149+
},
150+
identitySchema: testIdentitySchema,
151+
expected: &fwserver.ReadResourceRequest{
152+
IdentitySchema: testIdentitySchema,
153+
CurrentIdentity: &tfsdk.ResourceIdentity{
154+
Raw: testIdentityProto6Value,
155+
Schema: testIdentitySchema,
156+
},
157+
},
158+
},
102159
"private-malformed-json": {
103160
input: &tfprotov6.ReadResourceRequest{
104161
Private: []byte(`{`),
@@ -200,7 +257,7 @@ func TestReadResourceRequest(t *testing.T) {
200257
t.Run(name, func(t *testing.T) {
201258
t.Parallel()
202259

203-
got, diags := fromproto6.ReadResourceRequest(context.Background(), testCase.input, testCase.resource, testCase.resourceSchema, testCase.providerMetaSchema)
260+
got, diags := fromproto6.ReadResourceRequest(context.Background(), testCase.input, testCase.resource, testCase.resourceSchema, testCase.providerMetaSchema, testCase.identitySchema)
204261

205262
if diff := cmp.Diff(got, testCase.expected, cmp.AllowUnexported(privatestate.ProviderData{})); diff != "" {
206263
t.Errorf("unexpected difference: %s", diff)
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package fromproto6
5+
6+
import (
7+
"context"
8+
9+
"github.com/hashicorp/terraform-plugin-framework/diag"
10+
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
11+
"github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata"
12+
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
13+
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
14+
)
15+
16+
// TODO:ResourceIdentity: Should we create a wrapping struct to contain the identity data? To match the protocol (in-case we want to introduce other identity things)
17+
// - Need to think more on this (like what if we want to introduce display-only attributes)
18+
// - If we introduce one, add a test as well.
19+
func ResourceIdentity(ctx context.Context, in *tfprotov6.ResourceIdentityData, schema fwschema.Schema) (*tfsdk.ResourceIdentity, diag.Diagnostics) {
20+
if in == nil {
21+
return nil, nil
22+
}
23+
24+
return IdentityData(ctx, in.IdentityData, schema)
25+
}
26+
27+
// IdentityData returns the *tfsdk.ResourceIdentity for a *tfprotov6.DynamicValue and fwschema.Schema.
28+
func IdentityData(ctx context.Context, proto6DynamicValue *tfprotov6.DynamicValue, schema fwschema.Schema) (*tfsdk.ResourceIdentity, diag.Diagnostics) {
29+
if proto6DynamicValue == nil {
30+
return nil, nil
31+
}
32+
33+
var diags diag.Diagnostics
34+
35+
// Panic prevention here to simplify the calling implementations.
36+
// This should not happen, but just in case.
37+
if schema == nil {
38+
diags.AddError(
39+
"Unable to Convert Resource Identity",
40+
"An unexpected error was encountered when converting the resource identity from the protocol type. "+
41+
"Identity data was sent in the protocol to a resource that doesn't support identity.\n\n"+
42+
"This is always a problem with Terraform or terraform-plugin-framework. Please report this to the provider developer.",
43+
)
44+
45+
return nil, diags
46+
}
47+
48+
data, dynamicValueDiags := DynamicValue(ctx, proto6DynamicValue, schema, fwschemadata.DataDescriptionResourceIdentity)
49+
50+
diags.Append(dynamicValueDiags...)
51+
52+
if diags.HasError() {
53+
return nil, diags
54+
}
55+
56+
fw := &tfsdk.ResourceIdentity{
57+
Raw: data.TerraformValue,
58+
Schema: schema,
59+
}
60+
61+
return fw, diags
62+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package fromproto6_test
5+
6+
import (
7+
"context"
8+
"testing"
9+
10+
"github.com/google/go-cmp/cmp"
11+
"github.com/hashicorp/terraform-plugin-framework/diag"
12+
"github.com/hashicorp/terraform-plugin-framework/internal/fromproto6"
13+
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
14+
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema"
15+
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
16+
"github.com/hashicorp/terraform-plugin-framework/types"
17+
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
18+
"github.com/hashicorp/terraform-plugin-go/tftypes"
19+
)
20+
21+
func TestResourceIdentity(t *testing.T) {
22+
t.Parallel()
23+
24+
testProto6Type := tftypes.Object{
25+
AttributeTypes: map[string]tftypes.Type{
26+
"test_attribute": tftypes.String,
27+
},
28+
}
29+
30+
testProto6Value := tftypes.NewValue(testProto6Type, map[string]tftypes.Value{
31+
"test_attribute": tftypes.NewValue(tftypes.String, "test-value"),
32+
})
33+
34+
testProto6DynamicValue, err := tfprotov6.NewDynamicValue(testProto6Type, testProto6Value)
35+
36+
if err != nil {
37+
t.Fatalf("unexpected error calling tfprotov6.NewDynamicValue(): %s", err)
38+
}
39+
40+
testFwSchema := testschema.Schema{
41+
Attributes: map[string]fwschema.Attribute{
42+
"test_attribute": testschema.Attribute{
43+
RequiredForImport: true,
44+
Type: types.StringType,
45+
},
46+
},
47+
}
48+
49+
testFwSchemaInvalid := testschema.Schema{
50+
Attributes: map[string]fwschema.Attribute{
51+
"test_attribute": testschema.Attribute{
52+
RequiredForImport: true,
53+
Type: types.BoolType,
54+
},
55+
},
56+
}
57+
58+
testCases := map[string]struct {
59+
input *tfprotov6.ResourceIdentityData
60+
schema fwschema.Schema
61+
expected *tfsdk.ResourceIdentity
62+
expectedDiagnostics diag.Diagnostics
63+
}{
64+
"nil": {
65+
input: nil,
66+
expected: nil,
67+
},
68+
"empty": {
69+
input: &tfprotov6.ResourceIdentityData{},
70+
expected: nil,
71+
},
72+
"missing-schema": {
73+
input: &tfprotov6.ResourceIdentityData{
74+
IdentityData: &testProto6DynamicValue,
75+
},
76+
expected: nil,
77+
expectedDiagnostics: diag.Diagnostics{
78+
diag.NewErrorDiagnostic(
79+
"Unable to Convert Resource Identity",
80+
"An unexpected error was encountered when converting the resource identity from the protocol type. "+
81+
"Identity data was sent in the protocol to a resource that doesn't support identity.\n\n"+
82+
"This is always a problem with Terraform or terraform-plugin-framework. Please report this to the provider developer.",
83+
),
84+
},
85+
},
86+
"invalid-schema": {
87+
input: &tfprotov6.ResourceIdentityData{
88+
IdentityData: &testProto6DynamicValue,
89+
},
90+
schema: testFwSchemaInvalid,
91+
expected: nil,
92+
expectedDiagnostics: diag.Diagnostics{
93+
diag.NewErrorDiagnostic(
94+
"Unable to Convert Resource Identity",
95+
"An unexpected error was encountered when converting the resource identity from the protocol type. "+
96+
"This is always an issue in terraform-plugin-framework used to implement the provider and should be reported to the provider developers.\n\n"+
97+
"Please report this to the provider developer:\n\n"+
98+
"Unable to unmarshal DynamicValue: AttributeName(\"test_attribute\"): couldn't decode bool: msgpack: invalid code=aa decoding bool",
99+
),
100+
},
101+
},
102+
"valid": {
103+
input: &tfprotov6.ResourceIdentityData{
104+
IdentityData: &testProto6DynamicValue,
105+
},
106+
schema: testFwSchema,
107+
expected: &tfsdk.ResourceIdentity{
108+
Raw: testProto6Value,
109+
Schema: testFwSchema,
110+
},
111+
},
112+
}
113+
114+
for name, testCase := range testCases {
115+
t.Run(name, func(t *testing.T) {
116+
t.Parallel()
117+
118+
got, diags := fromproto6.ResourceIdentity(context.Background(), testCase.input, testCase.schema)
119+
120+
if diff := cmp.Diff(got, testCase.expected); diff != "" {
121+
t.Errorf("unexpected difference: %s", diff)
122+
}
123+
124+
if diff := cmp.Diff(diags, testCase.expectedDiagnostics); diff != "" {
125+
t.Errorf("unexpected diagnostics difference: %s", diff)
126+
}
127+
})
128+
}
129+
}

internal/proto6server/server_readresource.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ func (s *Server) ReadResource(ctx context.Context, proto6Req *tfprotov6.ReadReso
3636
return toproto6.ReadResourceResponse(ctx, fwResp), nil
3737
}
3838

39+
identitySchema, diags := s.FrameworkServer.ResourceIdentitySchema(ctx, proto6Req.TypeName)
40+
41+
fwResp.Diagnostics.Append(diags...)
42+
43+
if fwResp.Diagnostics.HasError() {
44+
return toproto6.ReadResourceResponse(ctx, fwResp), nil
45+
}
46+
3947
providerMetaSchema, diags := s.FrameworkServer.ProviderMetaSchema(ctx)
4048

4149
fwResp.Diagnostics.Append(diags...)
@@ -44,7 +52,7 @@ func (s *Server) ReadResource(ctx context.Context, proto6Req *tfprotov6.ReadReso
4452
return toproto6.ReadResourceResponse(ctx, fwResp), nil
4553
}
4654

47-
fwReq, diags := fromproto6.ReadResourceRequest(ctx, proto6Req, resource, resourceSchema, providerMetaSchema)
55+
fwReq, diags := fromproto6.ReadResourceRequest(ctx, proto6Req, resource, resourceSchema, providerMetaSchema, identitySchema)
4856

4957
fwResp.Diagnostics.Append(diags...)
5058

0 commit comments

Comments
 (0)