Skip to content

Commit bb07ce6

Browse files
committed
protov6 implementation of apply resource
1 parent 8a90ecb commit bb07ce6

File tree

6 files changed

+328
-3
lines changed

6 files changed

+328
-3
lines changed

internal/fromproto6/applyresourcechange.go

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

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

4141
fw := &fwserver.ApplyResourceChangeRequest{
4242
ResourceSchema: resourceSchema,
43+
IdentitySchema: identitySchema,
4344
Resource: resource,
4445
}
4546

@@ -55,6 +56,12 @@ func ApplyResourceChangeRequest(ctx context.Context, proto6 *tfprotov6.ApplyReso
5556

5657
fw.PlannedState = plannedState
5758

59+
plannedIdentity, plannedIdentityDiags := ResourceIdentity(ctx, proto6.PlannedIdentity, identitySchema)
60+
61+
diags.Append(plannedIdentityDiags...)
62+
63+
fw.PlannedIdentity = plannedIdentity
64+
5865
priorState, priorStateDiags := State(ctx, proto6.PriorState, resourceSchema)
5966

6067
diags.Append(priorStateDiags...)

internal/fromproto6/applyresourcechange_test.go

Lines changed: 63 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 TestApplyResourceChangeRequest(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
})
@@ -61,6 +86,7 @@ func TestApplyResourceChangeRequest(t *testing.T) {
6186
resourceSchema fwschema.Schema
6287
resource resource.Resource
6388
providerMetaSchema fwschema.Schema
89+
identitySchema fwschema.Schema
6490
expected *fwserver.ApplyResourceChangeRequest
6591
expectedDiagnostics diag.Diagnostics
6692
}{
@@ -137,6 +163,42 @@ func TestApplyResourceChangeRequest(t *testing.T) {
137163
ResourceSchema: testFwSchema,
138164
},
139165
},
166+
"plannedidentity-missing-schema": {
167+
input: &tfprotov6.ApplyResourceChangeRequest{
168+
PlannedIdentity: &tfprotov6.ResourceIdentityData{
169+
IdentityData: &testIdentityProto6DynamicValue,
170+
},
171+
},
172+
resourceSchema: testFwSchema,
173+
expected: &fwserver.ApplyResourceChangeRequest{
174+
ResourceSchema: testFwSchema,
175+
},
176+
expectedDiagnostics: diag.Diagnostics{
177+
diag.NewErrorDiagnostic(
178+
"Unable to Convert Resource Identity",
179+
"An unexpected error was encountered when converting the resource identity from the protocol type. "+
180+
"Identity data was sent in the protocol to a resource that doesn't support identity.\n\n"+
181+
"This is always a problem with Terraform or terraform-plugin-framework. Please report this to the provider developer.",
182+
),
183+
},
184+
},
185+
"plannedidentity": {
186+
input: &tfprotov6.ApplyResourceChangeRequest{
187+
PlannedIdentity: &tfprotov6.ResourceIdentityData{
188+
IdentityData: &testIdentityProto6DynamicValue,
189+
},
190+
},
191+
identitySchema: testIdentitySchema,
192+
resourceSchema: testFwSchema,
193+
expected: &fwserver.ApplyResourceChangeRequest{
194+
IdentitySchema: testIdentitySchema,
195+
PlannedIdentity: &tfsdk.ResourceIdentity{
196+
Raw: testIdentityProto6Value,
197+
Schema: testIdentitySchema,
198+
},
199+
ResourceSchema: testFwSchema,
200+
},
201+
},
140202
"plannedprivate-malformed-json": {
141203
input: &tfprotov6.ApplyResourceChangeRequest{
142204
PlannedPrivate: []byte(`{`),
@@ -253,7 +315,7 @@ func TestApplyResourceChangeRequest(t *testing.T) {
253315
t.Run(name, func(t *testing.T) {
254316
t.Parallel()
255317

256-
got, diags := fromproto6.ApplyResourceChangeRequest(context.Background(), testCase.input, testCase.resource, testCase.resourceSchema, testCase.providerMetaSchema)
318+
got, diags := fromproto6.ApplyResourceChangeRequest(context.Background(), testCase.input, testCase.resource, testCase.resourceSchema, testCase.providerMetaSchema, testCase.identitySchema)
257319

258320
if diff := cmp.Diff(got, testCase.expected, cmp.AllowUnexported(privatestate.ProviderData{})); diff != "" {
259321
t.Errorf("unexpected difference: %s", diff)

internal/proto6server/server_applyresourcechange.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ func (s *Server) ApplyResourceChange(ctx context.Context, proto6Req *tfprotov6.A
3636
return toproto6.ApplyResourceChangeResponse(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.ApplyResourceChangeResponse(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) ApplyResourceChange(ctx context.Context, proto6Req *tfprotov6.A
4452
return toproto6.ApplyResourceChangeResponse(ctx, fwResp), nil
4553
}
4654

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

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

internal/proto6server/server_applyresourcechange_test.go

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/hashicorp/terraform-plugin-framework/provider"
1919
"github.com/hashicorp/terraform-plugin-framework/provider/metaschema"
2020
"github.com/hashicorp/terraform-plugin-framework/resource"
21+
"github.com/hashicorp/terraform-plugin-framework/resource/identityschema"
2122
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
2223
"github.com/hashicorp/terraform-plugin-framework/types"
2324
)
@@ -34,6 +35,20 @@ func TestServerApplyResourceChange(t *testing.T) {
3435

3536
testEmptyDynamicValue, _ := tfprotov6.NewDynamicValue(testSchemaType, tftypes.NewValue(testSchemaType, nil))
3637

38+
testIdentityType := tftypes.Object{
39+
AttributeTypes: map[string]tftypes.Type{
40+
"test_id": tftypes.String,
41+
},
42+
}
43+
44+
testPlannedIdentityValue := testNewDynamicValue(t, testIdentityType, map[string]tftypes.Value{
45+
"test_id": tftypes.NewValue(tftypes.String, "id-123"),
46+
})
47+
48+
testNewIdentityDynamicValue := testNewDynamicValue(t, testIdentityType, map[string]tftypes.Value{
49+
"test_id": tftypes.NewValue(tftypes.String, "new-id-123"),
50+
})
51+
3752
testSchema := schema.Schema{
3853
Attributes: map[string]schema.Attribute{
3954
"test_computed": schema.StringAttribute{
@@ -45,6 +60,14 @@ func TestServerApplyResourceChange(t *testing.T) {
4560
},
4661
}
4762

63+
testIdentitySchema := identityschema.Schema{
64+
Attributes: map[string]identityschema.Attribute{
65+
"test_id": identityschema.StringAttribute{
66+
RequiredForImport: true,
67+
},
68+
},
69+
}
70+
4871
type testSchemaData struct {
4972
TestComputed types.String `tfsdk:"test_computed"`
5073
TestRequired types.String `tfsdk:"test_required"`
@@ -194,6 +217,79 @@ func TestServerApplyResourceChange(t *testing.T) {
194217
}),
195218
},
196219
},
220+
"create-request-plannedidentity": {
221+
server: &Server{
222+
FrameworkServer: fwserver.Server{
223+
Provider: &testprovider.Provider{
224+
ResourcesMethod: func(_ context.Context) []func() resource.Resource {
225+
return []func() resource.Resource{
226+
func() resource.Resource {
227+
return &testprovider.ResourceWithIdentity{
228+
Resource: &testprovider.Resource{
229+
SchemaMethod: func(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
230+
resp.Schema = testSchema
231+
},
232+
MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) {
233+
resp.TypeName = "test_resource"
234+
},
235+
CreateMethod: func(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
236+
var identityData struct {
237+
TestID types.String `tfsdk:"test_id"`
238+
}
239+
240+
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
241+
242+
if identityData.TestID.ValueString() != "id-123" {
243+
resp.Diagnostics.AddError("Unexpected req.Identity", identityData.TestID.ValueString())
244+
}
245+
246+
// Prevent missing resource state error diagnostic
247+
var data testSchemaData
248+
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
249+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
250+
},
251+
DeleteMethod: func(_ context.Context, _ resource.DeleteRequest, resp *resource.DeleteResponse) {
252+
resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Create, Got: Delete")
253+
},
254+
UpdateMethod: func(_ context.Context, _ resource.UpdateRequest, resp *resource.UpdateResponse) {
255+
resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Create, Got: Update")
256+
},
257+
},
258+
IdentitySchemaMethod: func(ctx context.Context, req resource.IdentitySchemaRequest, resp *resource.IdentitySchemaResponse) {
259+
resp.IdentitySchema = testIdentitySchema
260+
},
261+
}
262+
},
263+
}
264+
},
265+
},
266+
},
267+
},
268+
request: &tfprotov6.ApplyResourceChangeRequest{
269+
Config: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{
270+
"test_computed": tftypes.NewValue(tftypes.String, nil),
271+
"test_required": tftypes.NewValue(tftypes.String, "test-config-value"),
272+
}),
273+
PlannedState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{
274+
"test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"),
275+
"test_required": tftypes.NewValue(tftypes.String, "test-config-value"),
276+
}),
277+
PlannedIdentity: &tfprotov6.ResourceIdentityData{
278+
IdentityData: testPlannedIdentityValue,
279+
},
280+
PriorState: &testEmptyDynamicValue,
281+
TypeName: "test_resource",
282+
},
283+
expectedResponse: &tfprotov6.ApplyResourceChangeResponse{
284+
NewState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{
285+
"test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"),
286+
"test_required": tftypes.NewValue(tftypes.String, "test-config-value"),
287+
}),
288+
NewIdentity: &tfprotov6.ResourceIdentityData{
289+
IdentityData: testPlannedIdentityValue,
290+
},
291+
},
292+
},
197293
"create-request-providermeta": {
198294
server: &Server{
199295
FrameworkServer: fwserver.Server{
@@ -372,6 +468,73 @@ func TestServerApplyResourceChange(t *testing.T) {
372468
}),
373469
},
374470
},
471+
"create-response-newidentity": {
472+
server: &Server{
473+
FrameworkServer: fwserver.Server{
474+
Provider: &testprovider.Provider{
475+
ResourcesMethod: func(_ context.Context) []func() resource.Resource {
476+
return []func() resource.Resource{
477+
func() resource.Resource {
478+
return &testprovider.ResourceWithIdentity{
479+
Resource: &testprovider.Resource{
480+
SchemaMethod: func(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
481+
resp.Schema = testSchema
482+
},
483+
MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) {
484+
resp.TypeName = "test_resource"
485+
},
486+
CreateMethod: func(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
487+
identityData := struct {
488+
TestID types.String `tfsdk:"test_id"`
489+
}{
490+
TestID: types.StringValue("new-id-123"),
491+
}
492+
resp.Diagnostics.Append(resp.Identity.Set(ctx, identityData)...)
493+
494+
var data testSchemaData
495+
496+
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
497+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
498+
},
499+
DeleteMethod: func(_ context.Context, _ resource.DeleteRequest, resp *resource.DeleteResponse) {
500+
resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Create, Got: Delete")
501+
},
502+
UpdateMethod: func(_ context.Context, _ resource.UpdateRequest, resp *resource.UpdateResponse) {
503+
resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Create, Got: Update")
504+
},
505+
},
506+
IdentitySchemaMethod: func(ctx context.Context, req resource.IdentitySchemaRequest, resp *resource.IdentitySchemaResponse) {
507+
resp.IdentitySchema = testIdentitySchema
508+
},
509+
}
510+
},
511+
}
512+
},
513+
},
514+
},
515+
},
516+
request: &tfprotov6.ApplyResourceChangeRequest{
517+
Config: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{
518+
"test_computed": tftypes.NewValue(tftypes.String, nil),
519+
"test_required": tftypes.NewValue(tftypes.String, "test-config-value"),
520+
}),
521+
PlannedState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{
522+
"test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"),
523+
"test_required": tftypes.NewValue(tftypes.String, "test-config-value"),
524+
}),
525+
PriorState: &testEmptyDynamicValue,
526+
TypeName: "test_resource",
527+
},
528+
expectedResponse: &tfprotov6.ApplyResourceChangeResponse{
529+
NewIdentity: &tfprotov6.ResourceIdentityData{
530+
IdentityData: testNewIdentityDynamicValue,
531+
},
532+
NewState: testNewDynamicValue(t, testSchemaType, map[string]tftypes.Value{
533+
"test_computed": tftypes.NewValue(tftypes.String, "test-plannedstate-value"),
534+
"test_required": tftypes.NewValue(tftypes.String, "test-config-value"),
535+
}),
536+
},
537+
},
375538
"create-response-newstate-null": {
376539
server: &Server{
377540
FrameworkServer: fwserver.Server{

internal/toproto6/applyresourcechange.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ func ApplyResourceChangeResponse(ctx context.Context, fw *fwserver.ApplyResource
2727
proto6.Diagnostics = append(proto6.Diagnostics, Diagnostics(ctx, diags)...)
2828
proto6.NewState = newState
2929

30+
newIdentity, diags := ResourceIdentity(ctx, fw.NewIdentity)
31+
32+
proto6.Diagnostics = append(proto6.Diagnostics, Diagnostics(ctx, diags)...)
33+
proto6.NewIdentity = newIdentity
34+
3035
newPrivate, diags := fw.Private.Bytes(ctx)
3136

3237
proto6.Diagnostics = append(proto6.Diagnostics, Diagnostics(ctx, diags)...)

0 commit comments

Comments
 (0)