Skip to content

Commit 8873227

Browse files
committed
protov5 + fwserver implementation for PlanResourceChange
1 parent e3dd2b6 commit 8873227

11 files changed

+676
-4
lines changed

internal/fromproto5/planresourcechange.go

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

1818
// PlanResourceChangeRequest returns the *fwserver.PlanResourceChangeRequest
1919
// equivalent of a *tfprotov5.PlanResourceChangeRequest.
20-
func PlanResourceChangeRequest(ctx context.Context, proto5 *tfprotov5.PlanResourceChangeRequest, reqResource resource.Resource, resourceSchema fwschema.Schema, providerMetaSchema fwschema.Schema, resourceBehavior resource.ResourceBehavior) (*fwserver.PlanResourceChangeRequest, diag.Diagnostics) {
20+
func PlanResourceChangeRequest(ctx context.Context, proto5 *tfprotov5.PlanResourceChangeRequest, reqResource resource.Resource, resourceSchema fwschema.Schema, providerMetaSchema fwschema.Schema, resourceBehavior resource.ResourceBehavior, identitySchema fwschema.Schema) (*fwserver.PlanResourceChangeRequest, diag.Diagnostics) {
2121
if proto5 == nil {
2222
return nil, nil
2323
}
@@ -41,6 +41,7 @@ func PlanResourceChangeRequest(ctx context.Context, proto5 *tfprotov5.PlanResour
4141
fw := &fwserver.PlanResourceChangeRequest{
4242
ResourceBehavior: resourceBehavior,
4343
ResourceSchema: resourceSchema,
44+
IdentitySchema: identitySchema,
4445
Resource: reqResource,
4546
ClientCapabilities: ModifyPlanClientCapabilities(proto5.ClientCapabilities),
4647
}
@@ -57,6 +58,12 @@ func PlanResourceChangeRequest(ctx context.Context, proto5 *tfprotov5.PlanResour
5758

5859
fw.PriorState = priorState
5960

61+
priorIdentity, priorIdentityDiags := ResourceIdentity(ctx, proto5.PriorIdentity, identitySchema)
62+
63+
diags.Append(priorIdentityDiags...)
64+
65+
fw.PriorIdentity = priorIdentity
66+
6067
proposedNewState, proposedNewStateDiags := Plan(ctx, proto5.ProposedNewState, resourceSchema)
6168

6269
diags.Append(proposedNewStateDiags...)

internal/fromproto5/planresourcechange_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 TestPlanResourceChangeRequest(t *testing.T) {
4849
},
4950
}
5051

52+
testIdentityProto5Type := tftypes.Object{
53+
AttributeTypes: map[string]tftypes.Type{
54+
"test_identity_attribute": tftypes.String,
55+
},
56+
}
57+
58+
testIdentityProto5Value := tftypes.NewValue(testIdentityProto5Type, map[string]tftypes.Value{
59+
"test_identity_attribute": tftypes.NewValue(tftypes.String, "id-123"),
60+
})
61+
62+
testIdentityProto5DynamicValue, err := tfprotov5.NewDynamicValue(testIdentityProto5Type, testIdentityProto5Value)
63+
64+
if err != nil {
65+
t.Fatalf("unexpected error calling tfprotov5.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
})
@@ -58,6 +83,7 @@ func TestPlanResourceChangeRequest(t *testing.T) {
5883
input *tfprotov5.PlanResourceChangeRequest
5984
resourceBehavior resource.ResourceBehavior
6085
resourceSchema fwschema.Schema
86+
identitySchema fwschema.Schema
6187
resource resource.Resource
6288
providerMetaSchema fwschema.Schema
6389
expected *fwserver.PlanResourceChangeRequest
@@ -182,6 +208,42 @@ func TestPlanResourceChangeRequest(t *testing.T) {
182208
ResourceSchema: testFwSchema,
183209
},
184210
},
211+
"prioridentity-missing-schema": {
212+
input: &tfprotov5.PlanResourceChangeRequest{
213+
PriorIdentity: &tfprotov5.ResourceIdentityData{
214+
IdentityData: &testIdentityProto5DynamicValue,
215+
},
216+
},
217+
resourceSchema: testFwSchema,
218+
expected: &fwserver.PlanResourceChangeRequest{
219+
ResourceSchema: testFwSchema,
220+
},
221+
expectedDiagnostics: diag.Diagnostics{
222+
diag.NewErrorDiagnostic(
223+
"Unable to Convert Resource Identity",
224+
"An unexpected error was encountered when converting the resource identity from the protocol type. "+
225+
"Identity data was sent in the protocol to a resource that doesn't support identity.\n\n"+
226+
"This is always a problem with Terraform or terraform-plugin-framework. Please report this to the provider developer.",
227+
),
228+
},
229+
},
230+
"prioridentity": {
231+
input: &tfprotov5.PlanResourceChangeRequest{
232+
PriorIdentity: &tfprotov5.ResourceIdentityData{
233+
IdentityData: &testIdentityProto5DynamicValue,
234+
},
235+
},
236+
identitySchema: testIdentitySchema,
237+
resourceSchema: testFwSchema,
238+
expected: &fwserver.PlanResourceChangeRequest{
239+
IdentitySchema: testIdentitySchema,
240+
PriorIdentity: &tfsdk.ResourceIdentity{
241+
Raw: testIdentityProto5Value,
242+
Schema: testIdentitySchema,
243+
},
244+
ResourceSchema: testFwSchema,
245+
},
246+
},
185247
"providermeta-missing-data": {
186248
input: &tfprotov5.PlanResourceChangeRequest{},
187249
resourceSchema: testFwSchema,
@@ -265,7 +327,7 @@ func TestPlanResourceChangeRequest(t *testing.T) {
265327
t.Run(name, func(t *testing.T) {
266328
t.Parallel()
267329

268-
got, diags := fromproto5.PlanResourceChangeRequest(context.Background(), testCase.input, testCase.resource, testCase.resourceSchema, testCase.providerMetaSchema, testCase.resourceBehavior)
330+
got, diags := fromproto5.PlanResourceChangeRequest(context.Background(), testCase.input, testCase.resource, testCase.resourceSchema, testCase.providerMetaSchema, testCase.resourceBehavior, testCase.identitySchema)
269331

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

internal/fwserver/server_planresourcechange.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@ type PlanResourceChangeRequest struct {
3030
Config *tfsdk.Config
3131
PriorPrivate *privatestate.Data
3232
PriorState *tfsdk.State
33+
PriorIdentity *tfsdk.ResourceIdentity
3334
ProposedNewState *tfsdk.Plan
3435
ProviderMeta *tfsdk.Config
3536
ResourceSchema fwschema.Schema
37+
IdentitySchema fwschema.Schema
3638
Resource resource.Resource
3739
ResourceBehavior resource.ResourceBehavior
3840
}
@@ -44,6 +46,7 @@ type PlanResourceChangeResponse struct {
4446
Diagnostics diag.Diagnostics
4547
PlannedPrivate *privatestate.Data
4648
PlannedState *tfsdk.State
49+
PlannedIdentity *tfsdk.ResourceIdentity
4750
RequiresReplace path.Paths
4851
}
4952

@@ -115,6 +118,26 @@ func (s *Server) PlanResourceChange(ctx context.Context, req *PlanResourceChange
115118
}
116119
}
117120

121+
// If the resource supports identity and there is no prior identity data, pre-populate with a null value.
122+
// TODO:ResourceIdentity: Is there any reason a provider WOULD NOT want to populate an identity when it supports one?
123+
// TODO:ResourceIdentity: Should this be set to all unknowns?
124+
if req.PriorIdentity == nil && req.IdentitySchema != nil {
125+
nullIdentityTfValue := tftypes.NewValue(req.IdentitySchema.Type().TerraformType(ctx), nil)
126+
127+
req.PriorIdentity = &tfsdk.ResourceIdentity{
128+
Schema: req.IdentitySchema,
129+
Raw: nullIdentityTfValue.Copy(),
130+
}
131+
}
132+
133+
// Set the planned identity to the prior identity by default (can be modified later).
134+
if req.PriorIdentity != nil {
135+
resp.PlannedIdentity = &tfsdk.ResourceIdentity{
136+
Schema: req.PriorIdentity.Schema,
137+
Raw: req.PriorIdentity.Raw.Copy(),
138+
}
139+
}
140+
118141
// Ensure that resp.PlannedPrivate is never nil.
119142
resp.PlannedPrivate = privatestate.EmptyData(ctx)
120143

@@ -304,9 +327,17 @@ func (s *Server) PlanResourceChange(ctx context.Context, req *PlanResourceChange
304327
modifyPlanReq.ProviderMeta = *req.ProviderMeta
305328
}
306329

330+
if resp.PlannedIdentity != nil {
331+
modifyPlanReq.Identity = &tfsdk.ResourceIdentity{
332+
Schema: resp.PlannedIdentity.Schema,
333+
Raw: resp.PlannedIdentity.Raw.Copy(),
334+
}
335+
}
336+
307337
modifyPlanResp := resource.ModifyPlanResponse{
308338
Diagnostics: resp.Diagnostics,
309339
Plan: modifyPlanReq.Plan,
340+
Identity: modifyPlanReq.Identity,
310341
RequiresReplace: path.Paths{},
311342
Private: modifyPlanReq.Private,
312343
}
@@ -317,6 +348,7 @@ func (s *Server) PlanResourceChange(ctx context.Context, req *PlanResourceChange
317348

318349
resp.Diagnostics = modifyPlanResp.Diagnostics
319350
resp.PlannedState = planToState(modifyPlanResp.Plan)
351+
resp.PlannedIdentity = modifyPlanResp.Identity
320352
resp.RequiresReplace = append(resp.RequiresReplace, modifyPlanResp.RequiresReplace...)
321353
resp.PlannedPrivate.Provider = modifyPlanResp.Private
322354
resp.Deferred = modifyPlanResp.Deferred
@@ -338,6 +370,16 @@ func (s *Server) PlanResourceChange(ctx context.Context, req *PlanResourceChange
338370
}
339371
}
340372

373+
if resp.PlannedIdentity != nil && req.IdentitySchema == nil {
374+
resp.Diagnostics.AddError(
375+
"Unexpected Plan Response",
376+
"An unexpected error was encountered when creating the plan response. New identity data was returned by the provider planning operation, but the resource does not indicate identity support.\n\n"+
377+
"This is always a problem with the provider and should be reported to the provider developer.",
378+
)
379+
380+
return
381+
}
382+
341383
// Ensure deterministic RequiresReplace by sorting and deduplicating
342384
resp.RequiresReplace = NormaliseRequiresReplace(ctx, resp.RequiresReplace)
343385

0 commit comments

Comments
 (0)