Skip to content

Commit

Permalink
all: Add automatic deferred action support for unknown provider confi…
Browse files Browse the repository at this point in the history
…guration (#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]>
  • Loading branch information
3 people authored May 30, 2024
1 parent d46eab1 commit 60c2c5a
Show file tree
Hide file tree
Showing 31 changed files with 849 additions and 36 deletions.
6 changes: 6 additions & 0 deletions .changes/unreleased/FEATURES-20240520-180458.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: FEATURES
body: 'provider: Add `Deferred` field to `ConfigureResponse`
which indicates a provider deferred action to the Terraform client'
time: 2024-05-20T18:04:58.852448-04:00
custom:
Issue: "1002"
6 changes: 6 additions & 0 deletions .changes/unreleased/FEATURES-20240520-180735.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: FEATURES
body: 'provider: Add `ClientCapabilities` field to `ConfigureRequest` which specifies
optionally supported protocol features for the Terraform client'
time: 2024-05-20T18:07:35.862641-04:00
custom:
Issue: "1002"
14 changes: 14 additions & 0 deletions internal/fromproto5/client_capabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,23 @@ import (
"github.com/hashicorp/terraform-plugin-go/tfprotov5"

"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/resource"
)

func ConfigureProviderClientCapabilities(in *tfprotov5.ConfigureProviderClientCapabilities) provider.ConfigureProviderClientCapabilities {
if in == nil {
// Client did not indicate any supported capabilities
return provider.ConfigureProviderClientCapabilities{
DeferralAllowed: false,
}
}

return provider.ConfigureProviderClientCapabilities{
DeferralAllowed: in.DeferralAllowed,
}
}

func ReadDataSourceClientCapabilities(in *tfprotov5.ReadDataSourceClientCapabilities) datasource.ReadClientCapabilities {
if in == nil {
// Client did not indicate any supported capabilities
Expand Down
6 changes: 4 additions & 2 deletions internal/fromproto5/configureprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ package fromproto5
import (
"context"

"github.com/hashicorp/terraform-plugin-go/tfprotov5"

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
)

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

fw := &provider.ConfigureRequest{
TerraformVersion: proto5.TerraformVersion,
TerraformVersion: proto5.TerraformVersion,
ClientCapabilities: ConfigureProviderClientCapabilities(proto5.ClientCapabilities),
}

config, diags := Config(ctx, proto5.Config, providerSchema)
Expand Down
25 changes: 23 additions & 2 deletions internal/fromproto5/configureprovider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import (
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tftypes"

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fromproto5"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

func TestConfigureProviderRequest(t *testing.T) {
Expand Down Expand Up @@ -94,6 +95,26 @@ func TestConfigureProviderRequest(t *testing.T) {
TerraformVersion: "99.99.99",
},
},
"client-capabilities": {
input: &tfprotov5.ConfigureProviderRequest{
ClientCapabilities: &tfprotov5.ConfigureProviderClientCapabilities{
DeferralAllowed: true,
},
},
expected: &provider.ConfigureRequest{
ClientCapabilities: provider.ConfigureProviderClientCapabilities{
DeferralAllowed: true,
},
},
},
"client-capabilities-unset": {
input: &tfprotov5.ConfigureProviderRequest{},
expected: &provider.ConfigureRequest{
ClientCapabilities: provider.ConfigureProviderClientCapabilities{
DeferralAllowed: false,
},
},
},
}

for name, testCase := range testCases {
Expand Down
3 changes: 2 additions & 1 deletion internal/fromproto5/planresourcechange.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (

// PlanResourceChangeRequest returns the *fwserver.PlanResourceChangeRequest
// equivalent of a *tfprotov5.PlanResourceChangeRequest.
func PlanResourceChangeRequest(ctx context.Context, proto5 *tfprotov5.PlanResourceChangeRequest, reqResource resource.Resource, resourceSchema fwschema.Schema, providerMetaSchema fwschema.Schema) (*fwserver.PlanResourceChangeRequest, diag.Diagnostics) {
func PlanResourceChangeRequest(ctx context.Context, proto5 *tfprotov5.PlanResourceChangeRequest, reqResource resource.Resource, resourceSchema fwschema.Schema, providerMetaSchema fwschema.Schema, resourceBehavior resource.ResourceBehavior) (*fwserver.PlanResourceChangeRequest, diag.Diagnostics) {
if proto5 == nil {
return nil, nil
}
Expand All @@ -39,6 +39,7 @@ func PlanResourceChangeRequest(ctx context.Context, proto5 *tfprotov5.PlanResour
}

fw := &fwserver.PlanResourceChangeRequest{
ResourceBehavior: resourceBehavior,
ResourceSchema: resourceSchema,
Resource: reqResource,
ClientCapabilities: ModifyPlanClientCapabilities(proto5.ClientCapabilities),
Expand Down
20 changes: 19 additions & 1 deletion internal/fromproto5/planresourcechange_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func TestPlanResourceChangeRequest(t *testing.T) {

testCases := map[string]struct {
input *tfprotov5.PlanResourceChangeRequest
resourceBehavior resource.ResourceBehavior
resourceSchema fwschema.Schema
resource resource.Resource
providerMetaSchema fwschema.Schema
Expand Down Expand Up @@ -241,6 +242,23 @@ func TestPlanResourceChangeRequest(t *testing.T) {
ResourceSchema: testFwSchema,
},
},
"resource-behavior": {
input: &tfprotov5.PlanResourceChangeRequest{},
resourceSchema: testFwSchema,
resourceBehavior: resource.ResourceBehavior{
ProviderDeferred: resource.ProviderDeferredBehavior{
EnablePlanModification: true,
},
},
expected: &fwserver.PlanResourceChangeRequest{
ResourceBehavior: resource.ResourceBehavior{
ProviderDeferred: resource.ProviderDeferredBehavior{
EnablePlanModification: true,
},
},
ResourceSchema: testFwSchema,
},
},
}

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

got, diags := fromproto5.PlanResourceChangeRequest(context.Background(), testCase.input, testCase.resource, testCase.resourceSchema, testCase.providerMetaSchema)
got, diags := fromproto5.PlanResourceChangeRequest(context.Background(), testCase.input, testCase.resource, testCase.resourceSchema, testCase.providerMetaSchema, testCase.resourceBehavior)

if diff := cmp.Diff(got, testCase.expected, cmp.AllowUnexported(privatestate.ProviderData{})); diff != "" {
t.Errorf("unexpected difference: %s", diff)
Expand Down
14 changes: 14 additions & 0 deletions internal/fromproto6/client_capabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,23 @@ import (
"github.com/hashicorp/terraform-plugin-go/tfprotov6"

"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/resource"
)

func ConfigureProviderClientCapabilities(in *tfprotov6.ConfigureProviderClientCapabilities) provider.ConfigureProviderClientCapabilities {
if in == nil {
// Client did not indicate any supported capabilities
return provider.ConfigureProviderClientCapabilities{
DeferralAllowed: false,
}
}

return provider.ConfigureProviderClientCapabilities{
DeferralAllowed: in.DeferralAllowed,
}
}

func ReadDataSourceClientCapabilities(in *tfprotov6.ReadDataSourceClientCapabilities) datasource.ReadClientCapabilities {
if in == nil {
// Client did not indicate any supported capabilities
Expand Down
6 changes: 4 additions & 2 deletions internal/fromproto6/configureprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ package fromproto6
import (
"context"

"github.com/hashicorp/terraform-plugin-go/tfprotov6"

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
)

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

fw := &provider.ConfigureRequest{
TerraformVersion: proto6.TerraformVersion,
TerraformVersion: proto6.TerraformVersion,
ClientCapabilities: ConfigureProviderClientCapabilities(proto6.ClientCapabilities),
}

config, diags := Config(ctx, proto6.Config, providerSchema)
Expand Down
25 changes: 23 additions & 2 deletions internal/fromproto6/configureprovider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import (
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-go/tftypes"

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fromproto6"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

func TestConfigureProviderRequest(t *testing.T) {
Expand Down Expand Up @@ -94,6 +95,26 @@ func TestConfigureProviderRequest(t *testing.T) {
TerraformVersion: "99.99.99",
},
},
"client-capabilities": {
input: &tfprotov6.ConfigureProviderRequest{
ClientCapabilities: &tfprotov6.ConfigureProviderClientCapabilities{
DeferralAllowed: true,
},
},
expected: &provider.ConfigureRequest{
ClientCapabilities: provider.ConfigureProviderClientCapabilities{
DeferralAllowed: true,
},
},
},
"client-capabilities-unset": {
input: &tfprotov6.ConfigureProviderRequest{},
expected: &provider.ConfigureRequest{
ClientCapabilities: provider.ConfigureProviderClientCapabilities{
DeferralAllowed: false,
},
},
},
}

for name, testCase := range testCases {
Expand Down
3 changes: 2 additions & 1 deletion internal/fromproto6/planresourcechange.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (

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

fw := &fwserver.PlanResourceChangeRequest{
ResourceBehavior: resourceBehavior,
ResourceSchema: resourceSchema,
Resource: reqResource,
ClientCapabilities: ModifyPlanClientCapabilities(proto6.ClientCapabilities),
Expand Down
20 changes: 19 additions & 1 deletion internal/fromproto6/planresourcechange_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func TestPlanResourceChangeRequest(t *testing.T) {

testCases := map[string]struct {
input *tfprotov6.PlanResourceChangeRequest
resourceBehavior resource.ResourceBehavior
resourceSchema fwschema.Schema
resource resource.Resource
providerMetaSchema fwschema.Schema
Expand Down Expand Up @@ -241,6 +242,23 @@ func TestPlanResourceChangeRequest(t *testing.T) {
ResourceSchema: testFwSchema,
},
},
"resource-behavior": {
input: &tfprotov6.PlanResourceChangeRequest{},
resourceSchema: testFwSchema,
resourceBehavior: resource.ResourceBehavior{
ProviderDeferred: resource.ProviderDeferredBehavior{
EnablePlanModification: true,
},
},
expected: &fwserver.PlanResourceChangeRequest{
ResourceBehavior: resource.ResourceBehavior{
ProviderDeferred: resource.ProviderDeferredBehavior{
EnablePlanModification: true,
},
},
ResourceSchema: testFwSchema,
},
},
}

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

got, diags := fromproto6.PlanResourceChangeRequest(context.Background(), testCase.input, testCase.resource, testCase.resourceSchema, testCase.providerMetaSchema)
got, diags := fromproto6.PlanResourceChangeRequest(context.Background(), testCase.input, testCase.resource, testCase.resourceSchema, testCase.providerMetaSchema, testCase.resourceBehavior)

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

0 comments on commit 60c2c5a

Please sign in to comment.