Skip to content

Commit 9e664c7

Browse files
bendbennettbflad
andauthored
Implement Resource Private State Management (#433)
* Add privatestate pkg and Data type (#399) Reference: #399 * Fixing map merging and tests. Adding doc (#399) Reference: #399 * Amend diagnostic messages and add logging (#399) Reference: #399 * Setting up private data for use in ReadResource RPC (#399) Reference: #399 * Refactoring and fixing tests (#399) Reference: #399 * Call privatestate.NewData directly from fromproto5/6 (#399) Reference: #399 * Adding tests for proto5/6server private (#399) Reference: #399 * Additional tests for privatestate.NewData (#399) Reference: #399 * Additional tests for privatestate (#399) Reference: #399 * Calling Data.Bytes directly from toproto5/6/readresource (#399) Reference: #399 * Adding test coverage to verify SetKey behaviour (#399) Reference: #399 * Adding test coverage for fwserver readresource behaviour (#399) Reference: #399 * Apply suggestions from code review Co-authored-by: Brian Flad <[email protected]> * Adding test coverage for proto5/6server readresource behaviour (#399) Reference: #399 * Update resource/read.go Co-authored-by: Brian Flad <[email protected]> * Configuring private state data for use with create, update and delete (applyresourcechange) operations (#399) Reference: #399 * Refactoring Data to use a pointer to a ProviderData struct which holds an unexported data field containing map[string][]byte to prevent direct manipulation of the data and inadvertent addition of values that are not invalid (i.e., invalid JSON and/or UTF-8) (#399) Reference: #399 * Expanding test coverage around privatestate.Data (#399) Reference: #399 * Ensuring that all instances of privatestate.Provider data that passed through to provider code have been initialised (#399) Reference: #399 * Fixing tests following rebase (#399) Reference: #399 * Implementing private state management for import resource state (#399) Reference: #399 * Implementing private state management for ModifyAttribute and ModifyResource (#399) Reference: #399 * Apply suggestions from code review Co-authored-by: Brian Flad <[email protected]> * Updating following code review (#399) Reference: #399 * Apply suggestions from code review Co-authored-by: Brian Flad <[email protected]> * Updating following code review (#399) Reference: #399 * Refactoring to use EmptyData to avoid Data.Provider from being nil (#399) Reference: #399 * Updating changelog entry (#399) Reference: #399 Co-authored-by: Brian Flad <[email protected]>
1 parent 0ba16ff commit 9e664c7

File tree

67 files changed

+6588
-174
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+6588
-174
lines changed

.changelog/433.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:note
2+
A new internal package has been introduced which enables provider developers to read/write framework-managed private state data.
3+
```

internal/fromproto5/applyresourcechange.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ package fromproto5
33
import (
44
"context"
55

6+
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
7+
68
"github.com/hashicorp/terraform-plugin-framework/diag"
79
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
810
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
11+
"github.com/hashicorp/terraform-plugin-framework/internal/privatestate"
912
"github.com/hashicorp/terraform-plugin-framework/provider"
10-
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
1113
)
1214

1315
// ApplyResourceChangeRequest returns the *fwserver.ApplyResourceChangeRequest
@@ -34,7 +36,6 @@ func ApplyResourceChangeRequest(ctx context.Context, proto5 *tfprotov5.ApplyReso
3436
}
3537

3638
fw := &fwserver.ApplyResourceChangeRequest{
37-
PlannedPrivate: proto5.PlannedPrivate,
3839
ResourceSchema: resourceSchema,
3940
ResourceType: resourceType,
4041
}
@@ -63,5 +64,11 @@ func ApplyResourceChangeRequest(ctx context.Context, proto5 *tfprotov5.ApplyReso
6364

6465
fw.ProviderMeta = providerMeta
6566

67+
privateData, privateDataDiags := privatestate.NewData(ctx, proto5.PlannedPrivate)
68+
69+
diags.Append(privateDataDiags...)
70+
71+
fw.PlannedPrivate = privateData
72+
6673
return fw, diags
6774
}

internal/fromproto5/applyresourcechange_test.go

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@ import (
55
"testing"
66

77
"github.com/google/go-cmp/cmp"
8+
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
9+
"github.com/hashicorp/terraform-plugin-go/tftypes"
10+
811
"github.com/hashicorp/terraform-plugin-framework/diag"
912
"github.com/hashicorp/terraform-plugin-framework/internal/fromproto5"
1013
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
1114
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
15+
"github.com/hashicorp/terraform-plugin-framework/internal/privatestate"
1216
"github.com/hashicorp/terraform-plugin-framework/provider"
1317
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
1418
"github.com/hashicorp/terraform-plugin-framework/types"
15-
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
16-
"github.com/hashicorp/terraform-plugin-go/tftypes"
1719
)
1820

1921
func TestApplyResourceChangeRequest(t *testing.T) {
@@ -44,6 +46,14 @@ func TestApplyResourceChangeRequest(t *testing.T) {
4446
},
4547
}
4648

49+
testProviderKeyValue := privatestate.MustMarshalToJson(map[string][]byte{
50+
"providerKeyOne": []byte(`{"pKeyOne": {"k0": "zero", "k1": 1}}`),
51+
})
52+
53+
testProviderData := privatestate.MustProviderData(context.Background(), testProviderKeyValue)
54+
55+
testEmptyProviderData := privatestate.EmptyProviderData(context.Background())
56+
4757
testCases := map[string]struct {
4858
input *tfprotov5.ApplyResourceChangeRequest
4959
resourceSchema fwschema.Schema
@@ -125,14 +135,50 @@ func TestApplyResourceChangeRequest(t *testing.T) {
125135
ResourceSchema: testFwSchema,
126136
},
127137
},
128-
"plannedprivate": {
138+
"plannedprivate-malformed-json": {
129139
input: &tfprotov5.ApplyResourceChangeRequest{
130-
PlannedPrivate: []byte("{}"),
140+
PlannedPrivate: []byte(`{`),
131141
},
132142
resourceSchema: testFwSchema,
133143
expected: &fwserver.ApplyResourceChangeRequest{
144+
ResourceSchema: testFwSchema,
145+
}, expectedDiagnostics: diag.Diagnostics{
146+
diag.NewErrorDiagnostic(
147+
"Error Decoding Private State",
148+
"An error was encountered when decoding private state: unexpected end of JSON input.\n\n"+
149+
"This is always a problem with Terraform or terraform-plugin-framework. Please report this to the provider developer.",
150+
),
151+
},
152+
},
153+
"plannedprivate-empty-json": {
154+
input: &tfprotov5.ApplyResourceChangeRequest{
134155
PlannedPrivate: []byte("{}"),
156+
},
157+
resourceSchema: testFwSchema,
158+
expected: &fwserver.ApplyResourceChangeRequest{
135159
ResourceSchema: testFwSchema,
160+
PlannedPrivate: &privatestate.Data{
161+
Framework: map[string][]byte{},
162+
Provider: testEmptyProviderData,
163+
},
164+
},
165+
},
166+
"plannedprivate": {
167+
input: &tfprotov5.ApplyResourceChangeRequest{
168+
PlannedPrivate: privatestate.MustMarshalToJson(map[string][]byte{
169+
".frameworkKey": []byte(`{"fKeyOne": {"k0": "zero", "k1": 1}}`),
170+
"providerKeyOne": []byte(`{"pKeyOne": {"k0": "zero", "k1": 1}}`),
171+
}),
172+
},
173+
resourceSchema: testFwSchema,
174+
expected: &fwserver.ApplyResourceChangeRequest{
175+
ResourceSchema: testFwSchema,
176+
PlannedPrivate: &privatestate.Data{
177+
Framework: map[string][]byte{
178+
".frameworkKey": []byte(`{"fKeyOne": {"k0": "zero", "k1": 1}}`),
179+
},
180+
Provider: testProviderData,
181+
},
136182
},
137183
},
138184
"priorstate-missing-schema": {
@@ -209,7 +255,7 @@ func TestApplyResourceChangeRequest(t *testing.T) {
209255

210256
got, diags := fromproto5.ApplyResourceChangeRequest(context.Background(), testCase.input, testCase.resourceType, testCase.resourceSchema, testCase.providerMetaSchema)
211257

212-
if diff := cmp.Diff(got, testCase.expected); diff != "" {
258+
if diff := cmp.Diff(got, testCase.expected, cmp.AllowUnexported(privatestate.ProviderData{})); diff != "" {
213259
t.Errorf("unexpected difference: %s", diff)
214260
}
215261

internal/fromproto5/planresourcechange.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ package fromproto5
33
import (
44
"context"
55

6+
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
7+
68
"github.com/hashicorp/terraform-plugin-framework/diag"
79
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
810
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
11+
"github.com/hashicorp/terraform-plugin-framework/internal/privatestate"
912
"github.com/hashicorp/terraform-plugin-framework/provider"
10-
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
1113
)
1214

1315
// PlanResourceChangeRequest returns the *fwserver.PlanResourceChangeRequest
@@ -34,7 +36,6 @@ func PlanResourceChangeRequest(ctx context.Context, proto5 *tfprotov5.PlanResour
3436
}
3537

3638
fw := &fwserver.PlanResourceChangeRequest{
37-
PriorPrivate: proto5.PriorPrivate,
3839
ResourceSchema: resourceSchema,
3940
ResourceType: resourceType,
4041
}
@@ -63,5 +64,11 @@ func PlanResourceChangeRequest(ctx context.Context, proto5 *tfprotov5.PlanResour
6364

6465
fw.ProviderMeta = providerMeta
6566

67+
privateData, privateDataDiags := privatestate.NewData(ctx, proto5.PriorPrivate)
68+
69+
diags.Append(privateDataDiags...)
70+
71+
fw.PriorPrivate = privateData
72+
6673
return fw, diags
6774
}

internal/fromproto5/planresourcechange_test.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@ import (
55
"testing"
66

77
"github.com/google/go-cmp/cmp"
8+
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
9+
"github.com/hashicorp/terraform-plugin-go/tftypes"
10+
811
"github.com/hashicorp/terraform-plugin-framework/diag"
912
"github.com/hashicorp/terraform-plugin-framework/internal/fromproto5"
1013
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
1114
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
15+
"github.com/hashicorp/terraform-plugin-framework/internal/privatestate"
1216
"github.com/hashicorp/terraform-plugin-framework/provider"
1317
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
1418
"github.com/hashicorp/terraform-plugin-framework/types"
15-
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
16-
"github.com/hashicorp/terraform-plugin-go/tftypes"
1719
)
1820

1921
func TestPlanResourceChangeRequest(t *testing.T) {
@@ -44,6 +46,12 @@ func TestPlanResourceChangeRequest(t *testing.T) {
4446
},
4547
}
4648

49+
testProviderKeyValue := privatestate.MustMarshalToJson(map[string][]byte{
50+
"providerKeyOne": []byte(`{"pKeyOne": {"k0": "zero", "k1": 1}}`),
51+
})
52+
53+
testProviderData := privatestate.MustProviderData(context.Background(), testProviderKeyValue)
54+
4755
testCases := map[string]struct {
4856
input *tfprotov5.PlanResourceChangeRequest
4957
resourceSchema fwschema.Schema
@@ -99,11 +107,19 @@ func TestPlanResourceChangeRequest(t *testing.T) {
99107
},
100108
"priorprivate": {
101109
input: &tfprotov5.PlanResourceChangeRequest{
102-
PriorPrivate: []byte("{}"),
110+
PriorPrivate: privatestate.MustMarshalToJson(map[string][]byte{
111+
".frameworkKey": []byte(`{"fKeyOne": {"k0": "zero", "k1": 1}}`),
112+
"providerKeyOne": []byte(`{"pKeyOne": {"k0": "zero", "k1": 1}}`),
113+
}),
103114
},
104115
resourceSchema: testFwSchema,
105116
expected: &fwserver.PlanResourceChangeRequest{
106-
PriorPrivate: []byte("{}"),
117+
PriorPrivate: &privatestate.Data{
118+
Framework: map[string][]byte{
119+
".frameworkKey": []byte(`{"fKeyOne": {"k0": "zero", "k1": 1}}`),
120+
},
121+
Provider: testProviderData,
122+
},
107123
ResourceSchema: testFwSchema,
108124
},
109125
},
@@ -209,7 +225,7 @@ func TestPlanResourceChangeRequest(t *testing.T) {
209225

210226
got, diags := fromproto5.PlanResourceChangeRequest(context.Background(), testCase.input, testCase.resourceType, testCase.resourceSchema, testCase.providerMetaSchema)
211227

212-
if diff := cmp.Diff(got, testCase.expected); diff != "" {
228+
if diff := cmp.Diff(got, testCase.expected, cmp.AllowUnexported(privatestate.ProviderData{})); diff != "" {
213229
t.Errorf("unexpected difference: %s", diff)
214230
}
215231

internal/fromproto5/readresource.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ package fromproto5
33
import (
44
"context"
55

6+
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
7+
68
"github.com/hashicorp/terraform-plugin-framework/diag"
79
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
810
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
11+
"github.com/hashicorp/terraform-plugin-framework/internal/privatestate"
912
"github.com/hashicorp/terraform-plugin-framework/provider"
10-
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
1113
)
1214

1315
// ReadResourceRequest returns the *fwserver.ReadResourceRequest
@@ -20,7 +22,6 @@ func ReadResourceRequest(ctx context.Context, proto5 *tfprotov5.ReadResourceRequ
2022
var diags diag.Diagnostics
2123

2224
fw := &fwserver.ReadResourceRequest{
23-
Private: proto5.Private,
2425
ResourceType: resourceType,
2526
}
2627

@@ -36,5 +37,11 @@ func ReadResourceRequest(ctx context.Context, proto5 *tfprotov5.ReadResourceRequ
3637

3738
fw.ProviderMeta = providerMeta
3839

40+
privateData, privateDataDiags := privatestate.NewData(ctx, proto5.Private)
41+
42+
diags.Append(privateDataDiags...)
43+
44+
fw.Private = privateData
45+
3946
return fw, diags
4047
}

internal/fromproto5/readresource_test.go

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@ import (
55
"testing"
66

77
"github.com/google/go-cmp/cmp"
8+
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
9+
"github.com/hashicorp/terraform-plugin-go/tftypes"
10+
811
"github.com/hashicorp/terraform-plugin-framework/diag"
912
"github.com/hashicorp/terraform-plugin-framework/internal/fromproto5"
1013
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
1114
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
15+
"github.com/hashicorp/terraform-plugin-framework/internal/privatestate"
1216
"github.com/hashicorp/terraform-plugin-framework/provider"
1317
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
1418
"github.com/hashicorp/terraform-plugin-framework/types"
15-
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
16-
"github.com/hashicorp/terraform-plugin-go/tftypes"
1719
)
1820

1921
func TestReadResourceRequest(t *testing.T) {
@@ -44,6 +46,14 @@ func TestReadResourceRequest(t *testing.T) {
4446
},
4547
}
4648

49+
testProviderKeyValue := privatestate.MustMarshalToJson(map[string][]byte{
50+
"providerKeyOne": []byte(`{"pKeyOne": {"k0": "zero", "k1": 1}}`),
51+
})
52+
53+
testProviderData := privatestate.MustProviderData(context.Background(), testProviderKeyValue)
54+
55+
testEmptyProviderData := privatestate.EmptyProviderData(context.Background())
56+
4757
testCases := map[string]struct {
4858
input *tfprotov5.ReadResourceRequest
4959
resourceSchema fwschema.Schema
@@ -87,13 +97,47 @@ func TestReadResourceRequest(t *testing.T) {
8797
},
8898
},
8999
},
90-
"private": {
100+
"private-malformed-json": {
101+
input: &tfprotov5.ReadResourceRequest{
102+
Private: []byte(`{`),
103+
},
104+
resourceSchema: testFwSchema,
105+
expected: &fwserver.ReadResourceRequest{},
106+
expectedDiagnostics: diag.Diagnostics{
107+
diag.NewErrorDiagnostic(
108+
"Error Decoding Private State",
109+
"An error was encountered when decoding private state: unexpected end of JSON input.\n\n"+
110+
"This is always a problem with Terraform or terraform-plugin-framework. Please report this to the provider developer.",
111+
),
112+
},
113+
},
114+
"private-empty-json": {
91115
input: &tfprotov5.ReadResourceRequest{
92116
Private: []byte("{}"),
93117
},
94118
resourceSchema: testFwSchema,
95119
expected: &fwserver.ReadResourceRequest{
96-
Private: []byte("{}"),
120+
Private: &privatestate.Data{
121+
Framework: map[string][]byte{},
122+
Provider: testEmptyProviderData,
123+
},
124+
},
125+
},
126+
"private": {
127+
input: &tfprotov5.ReadResourceRequest{
128+
Private: privatestate.MustMarshalToJson(map[string][]byte{
129+
".frameworkKey": []byte(`{"fKeyOne": {"k0": "zero", "k1": 1}}`),
130+
"providerKeyOne": []byte(`{"pKeyOne": {"k0": "zero", "k1": 1}}`),
131+
}),
132+
},
133+
resourceSchema: testFwSchema,
134+
expected: &fwserver.ReadResourceRequest{
135+
Private: &privatestate.Data{
136+
Framework: map[string][]byte{
137+
".frameworkKey": []byte(`{"fKeyOne": {"k0": "zero", "k1": 1}}`),
138+
},
139+
Provider: testProviderData,
140+
},
97141
},
98142
},
99143
"providermeta-missing-data": {
@@ -136,7 +180,7 @@ func TestReadResourceRequest(t *testing.T) {
136180

137181
got, diags := fromproto5.ReadResourceRequest(context.Background(), testCase.input, testCase.resourceType, testCase.resourceSchema, testCase.providerMetaSchema)
138182

139-
if diff := cmp.Diff(got, testCase.expected); diff != "" {
183+
if diff := cmp.Diff(got, testCase.expected, cmp.AllowUnexported(privatestate.ProviderData{})); diff != "" {
140184
t.Errorf("unexpected difference: %s", diff)
141185
}
142186

internal/fromproto6/applyresourcechange.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ package fromproto6
33
import (
44
"context"
55

6+
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
7+
68
"github.com/hashicorp/terraform-plugin-framework/diag"
79
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
810
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
11+
"github.com/hashicorp/terraform-plugin-framework/internal/privatestate"
912
"github.com/hashicorp/terraform-plugin-framework/provider"
10-
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
1113
)
1214

1315
// ApplyResourceChangeRequest returns the *fwserver.ApplyResourceChangeRequest
@@ -34,7 +36,6 @@ func ApplyResourceChangeRequest(ctx context.Context, proto6 *tfprotov6.ApplyReso
3436
}
3537

3638
fw := &fwserver.ApplyResourceChangeRequest{
37-
PlannedPrivate: proto6.PlannedPrivate,
3839
ResourceSchema: resourceSchema,
3940
ResourceType: resourceType,
4041
}
@@ -63,5 +64,11 @@ func ApplyResourceChangeRequest(ctx context.Context, proto6 *tfprotov6.ApplyReso
6364

6465
fw.ProviderMeta = providerMeta
6566

67+
privateData, privateDataDiags := privatestate.NewData(ctx, proto6.PlannedPrivate)
68+
69+
diags.Append(privateDataDiags...)
70+
71+
fw.PlannedPrivate = privateData
72+
6673
return fw, diags
6774
}

0 commit comments

Comments
 (0)