Skip to content

Commit 60c2c5a

Browse files
SBGoodsaustinvallebflad
authored
all: Add automatic deferred action support for unknown provider configuration (#1002)
* Initial implementation of automatic deferrals for resource/datasource RPCs * Implement provider automatic deferral for `PlanResourceChange` RPC * Add default values for automatic deferrals * Update resource/metadata.go Co-authored-by: Austin Valle <[email protected]> * Add experimental note * Implement resource behavior in `proto6server` * Add changelog entries * Apply suggestions from code review Co-authored-by: Austin Valle <[email protected]> * Log deferred reason in debug logging * Add error diagnostics to automatic deferral tests * Refactor `PlanResourceChange` automatic deferred action implementation based on PR feedback. * Add a comment calling out intentional design of replacing configured values with `Unknown` * Return early in `PlanResourceChange` if `ProviderDeferredBehavior.EnablePlanModification` is false. * Update internal/fwserver/server_configureprovider.go Co-authored-by: Brian Flad <[email protected]> * Add separate unit test for overriding provider deferred reason with resource deferred reason. --------- Co-authored-by: Austin Valle <[email protected]> Co-authored-by: Brian Flad <[email protected]>
1 parent d46eab1 commit 60c2c5a

31 files changed

+849
-36
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
kind: FEATURES
2+
body: 'provider: Add `Deferred` field to `ConfigureResponse`
3+
which indicates a provider deferred action to the Terraform client'
4+
time: 2024-05-20T18:04:58.852448-04:00
5+
custom:
6+
Issue: "1002"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
kind: FEATURES
2+
body: 'provider: Add `ClientCapabilities` field to `ConfigureRequest` which specifies
3+
optionally supported protocol features for the Terraform client'
4+
time: 2024-05-20T18:07:35.862641-04:00
5+
custom:
6+
Issue: "1002"

internal/fromproto5/client_capabilities.go

+14
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,23 @@ import (
77
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
88

99
"github.com/hashicorp/terraform-plugin-framework/datasource"
10+
"github.com/hashicorp/terraform-plugin-framework/provider"
1011
"github.com/hashicorp/terraform-plugin-framework/resource"
1112
)
1213

14+
func ConfigureProviderClientCapabilities(in *tfprotov5.ConfigureProviderClientCapabilities) provider.ConfigureProviderClientCapabilities {
15+
if in == nil {
16+
// Client did not indicate any supported capabilities
17+
return provider.ConfigureProviderClientCapabilities{
18+
DeferralAllowed: false,
19+
}
20+
}
21+
22+
return provider.ConfigureProviderClientCapabilities{
23+
DeferralAllowed: in.DeferralAllowed,
24+
}
25+
}
26+
1327
func ReadDataSourceClientCapabilities(in *tfprotov5.ReadDataSourceClientCapabilities) datasource.ReadClientCapabilities {
1428
if in == nil {
1529
// Client did not indicate any supported capabilities

internal/fromproto5/configureprovider.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ package fromproto5
66
import (
77
"context"
88

9+
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
10+
911
"github.com/hashicorp/terraform-plugin-framework/diag"
1012
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
1113
"github.com/hashicorp/terraform-plugin-framework/provider"
12-
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
1314
)
1415

1516
// ConfigureProviderRequest returns the *fwserver.ConfigureProviderRequest
@@ -20,7 +21,8 @@ func ConfigureProviderRequest(ctx context.Context, proto5 *tfprotov5.ConfigurePr
2021
}
2122

2223
fw := &provider.ConfigureRequest{
23-
TerraformVersion: proto5.TerraformVersion,
24+
TerraformVersion: proto5.TerraformVersion,
25+
ClientCapabilities: ConfigureProviderClientCapabilities(proto5.ClientCapabilities),
2426
}
2527

2628
config, diags := Config(ctx, proto5.Config, providerSchema)

internal/fromproto5/configureprovider_test.go

+23-2
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ import (
88
"testing"
99

1010
"github.com/google/go-cmp/cmp"
11+
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
12+
"github.com/hashicorp/terraform-plugin-go/tftypes"
13+
1114
"github.com/hashicorp/terraform-plugin-framework/diag"
1215
"github.com/hashicorp/terraform-plugin-framework/internal/fromproto5"
1316
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
1417
"github.com/hashicorp/terraform-plugin-framework/provider"
1518
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
1619
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
17-
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
18-
"github.com/hashicorp/terraform-plugin-go/tftypes"
1920
)
2021

2122
func TestConfigureProviderRequest(t *testing.T) {
@@ -94,6 +95,26 @@ func TestConfigureProviderRequest(t *testing.T) {
9495
TerraformVersion: "99.99.99",
9596
},
9697
},
98+
"client-capabilities": {
99+
input: &tfprotov5.ConfigureProviderRequest{
100+
ClientCapabilities: &tfprotov5.ConfigureProviderClientCapabilities{
101+
DeferralAllowed: true,
102+
},
103+
},
104+
expected: &provider.ConfigureRequest{
105+
ClientCapabilities: provider.ConfigureProviderClientCapabilities{
106+
DeferralAllowed: true,
107+
},
108+
},
109+
},
110+
"client-capabilities-unset": {
111+
input: &tfprotov5.ConfigureProviderRequest{},
112+
expected: &provider.ConfigureRequest{
113+
ClientCapabilities: provider.ConfigureProviderClientCapabilities{
114+
DeferralAllowed: false,
115+
},
116+
},
117+
},
97118
}
98119

99120
for name, testCase := range testCases {

internal/fromproto5/planresourcechange.go

+2-1
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) (*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) (*fwserver.PlanResourceChangeRequest, diag.Diagnostics) {
2121
if proto5 == nil {
2222
return nil, nil
2323
}
@@ -39,6 +39,7 @@ func PlanResourceChangeRequest(ctx context.Context, proto5 *tfprotov5.PlanResour
3939
}
4040

4141
fw := &fwserver.PlanResourceChangeRequest{
42+
ResourceBehavior: resourceBehavior,
4243
ResourceSchema: resourceSchema,
4344
Resource: reqResource,
4445
ClientCapabilities: ModifyPlanClientCapabilities(proto5.ClientCapabilities),

internal/fromproto5/planresourcechange_test.go

+19-1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ func TestPlanResourceChangeRequest(t *testing.T) {
5656

5757
testCases := map[string]struct {
5858
input *tfprotov5.PlanResourceChangeRequest
59+
resourceBehavior resource.ResourceBehavior
5960
resourceSchema fwschema.Schema
6061
resource resource.Resource
6162
providerMetaSchema fwschema.Schema
@@ -241,6 +242,23 @@ func TestPlanResourceChangeRequest(t *testing.T) {
241242
ResourceSchema: testFwSchema,
242243
},
243244
},
245+
"resource-behavior": {
246+
input: &tfprotov5.PlanResourceChangeRequest{},
247+
resourceSchema: testFwSchema,
248+
resourceBehavior: resource.ResourceBehavior{
249+
ProviderDeferred: resource.ProviderDeferredBehavior{
250+
EnablePlanModification: true,
251+
},
252+
},
253+
expected: &fwserver.PlanResourceChangeRequest{
254+
ResourceBehavior: resource.ResourceBehavior{
255+
ProviderDeferred: resource.ProviderDeferredBehavior{
256+
EnablePlanModification: true,
257+
},
258+
},
259+
ResourceSchema: testFwSchema,
260+
},
261+
},
244262
}
245263

246264
for name, testCase := range testCases {
@@ -249,7 +267,7 @@ func TestPlanResourceChangeRequest(t *testing.T) {
249267
t.Run(name, func(t *testing.T) {
250268
t.Parallel()
251269

252-
got, diags := fromproto5.PlanResourceChangeRequest(context.Background(), testCase.input, testCase.resource, testCase.resourceSchema, testCase.providerMetaSchema)
270+
got, diags := fromproto5.PlanResourceChangeRequest(context.Background(), testCase.input, testCase.resource, testCase.resourceSchema, testCase.providerMetaSchema, testCase.resourceBehavior)
253271

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

internal/fromproto6/client_capabilities.go

+14
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,23 @@ import (
77
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
88

99
"github.com/hashicorp/terraform-plugin-framework/datasource"
10+
"github.com/hashicorp/terraform-plugin-framework/provider"
1011
"github.com/hashicorp/terraform-plugin-framework/resource"
1112
)
1213

14+
func ConfigureProviderClientCapabilities(in *tfprotov6.ConfigureProviderClientCapabilities) provider.ConfigureProviderClientCapabilities {
15+
if in == nil {
16+
// Client did not indicate any supported capabilities
17+
return provider.ConfigureProviderClientCapabilities{
18+
DeferralAllowed: false,
19+
}
20+
}
21+
22+
return provider.ConfigureProviderClientCapabilities{
23+
DeferralAllowed: in.DeferralAllowed,
24+
}
25+
}
26+
1327
func ReadDataSourceClientCapabilities(in *tfprotov6.ReadDataSourceClientCapabilities) datasource.ReadClientCapabilities {
1428
if in == nil {
1529
// Client did not indicate any supported capabilities

internal/fromproto6/configureprovider.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ package fromproto6
66
import (
77
"context"
88

9+
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
10+
911
"github.com/hashicorp/terraform-plugin-framework/diag"
1012
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
1113
"github.com/hashicorp/terraform-plugin-framework/provider"
12-
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
1314
)
1415

1516
// ConfigureProviderRequest returns the *fwserver.ConfigureProviderRequest
@@ -20,7 +21,8 @@ func ConfigureProviderRequest(ctx context.Context, proto6 *tfprotov6.ConfigurePr
2021
}
2122

2223
fw := &provider.ConfigureRequest{
23-
TerraformVersion: proto6.TerraformVersion,
24+
TerraformVersion: proto6.TerraformVersion,
25+
ClientCapabilities: ConfigureProviderClientCapabilities(proto6.ClientCapabilities),
2426
}
2527

2628
config, diags := Config(ctx, proto6.Config, providerSchema)

internal/fromproto6/configureprovider_test.go

+23-2
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ import (
88
"testing"
99

1010
"github.com/google/go-cmp/cmp"
11+
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
12+
"github.com/hashicorp/terraform-plugin-go/tftypes"
13+
1114
"github.com/hashicorp/terraform-plugin-framework/diag"
1215
"github.com/hashicorp/terraform-plugin-framework/internal/fromproto6"
1316
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
1417
"github.com/hashicorp/terraform-plugin-framework/provider"
1518
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
1619
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
17-
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
18-
"github.com/hashicorp/terraform-plugin-go/tftypes"
1920
)
2021

2122
func TestConfigureProviderRequest(t *testing.T) {
@@ -94,6 +95,26 @@ func TestConfigureProviderRequest(t *testing.T) {
9495
TerraformVersion: "99.99.99",
9596
},
9697
},
98+
"client-capabilities": {
99+
input: &tfprotov6.ConfigureProviderRequest{
100+
ClientCapabilities: &tfprotov6.ConfigureProviderClientCapabilities{
101+
DeferralAllowed: true,
102+
},
103+
},
104+
expected: &provider.ConfigureRequest{
105+
ClientCapabilities: provider.ConfigureProviderClientCapabilities{
106+
DeferralAllowed: true,
107+
},
108+
},
109+
},
110+
"client-capabilities-unset": {
111+
input: &tfprotov6.ConfigureProviderRequest{},
112+
expected: &provider.ConfigureRequest{
113+
ClientCapabilities: provider.ConfigureProviderClientCapabilities{
114+
DeferralAllowed: false,
115+
},
116+
},
117+
},
97118
}
98119

99120
for name, testCase := range testCases {

internal/fromproto6/planresourcechange.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import (
1717

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

4141
fw := &fwserver.PlanResourceChangeRequest{
42+
ResourceBehavior: resourceBehavior,
4243
ResourceSchema: resourceSchema,
4344
Resource: reqResource,
4445
ClientCapabilities: ModifyPlanClientCapabilities(proto6.ClientCapabilities),

internal/fromproto6/planresourcechange_test.go

+19-1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ func TestPlanResourceChangeRequest(t *testing.T) {
5656

5757
testCases := map[string]struct {
5858
input *tfprotov6.PlanResourceChangeRequest
59+
resourceBehavior resource.ResourceBehavior
5960
resourceSchema fwschema.Schema
6061
resource resource.Resource
6162
providerMetaSchema fwschema.Schema
@@ -241,6 +242,23 @@ func TestPlanResourceChangeRequest(t *testing.T) {
241242
ResourceSchema: testFwSchema,
242243
},
243244
},
245+
"resource-behavior": {
246+
input: &tfprotov6.PlanResourceChangeRequest{},
247+
resourceSchema: testFwSchema,
248+
resourceBehavior: resource.ResourceBehavior{
249+
ProviderDeferred: resource.ProviderDeferredBehavior{
250+
EnablePlanModification: true,
251+
},
252+
},
253+
expected: &fwserver.PlanResourceChangeRequest{
254+
ResourceBehavior: resource.ResourceBehavior{
255+
ProviderDeferred: resource.ProviderDeferredBehavior{
256+
EnablePlanModification: true,
257+
},
258+
},
259+
ResourceSchema: testFwSchema,
260+
},
261+
},
244262
}
245263

246264
for name, testCase := range testCases {
@@ -249,7 +267,7 @@ func TestPlanResourceChangeRequest(t *testing.T) {
249267
t.Run(name, func(t *testing.T) {
250268
t.Parallel()
251269

252-
got, diags := fromproto6.PlanResourceChangeRequest(context.Background(), testCase.input, testCase.resource, testCase.resourceSchema, testCase.providerMetaSchema)
270+
got, diags := fromproto6.PlanResourceChangeRequest(context.Background(), testCase.input, testCase.resource, testCase.resourceSchema, testCase.providerMetaSchema, testCase.resourceBehavior)
253271

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

0 commit comments

Comments
 (0)