Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

all: Add automatic deferred action support for unknown provider configuration #1002

Merged
merged 51 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
df3f0cf
Implement manual deferred action support for `resource.importResource…
SBGoods May 1, 2024
0a27076
Implement manual deferred action support for `resource.readResource`
SBGoods May 3, 2024
bbf6c8d
Implement manual deferred action support for `resource.modifyPlan`
SBGoods May 7, 2024
29cfc74
Rename `DeferralReason` and `DeferralResponse` to `DeferredReason` an…
SBGoods May 7, 2024
67ff590
Update `terraform-plugin-go` dependency to `v0.23.0`
SBGoods May 7, 2024
4a11408
Rename `deferral.go` to `deferred.go`
SBGoods May 7, 2024
ce1d14b
Implement manual deferred action support for data sources
SBGoods May 7, 2024
39c4bb5
Update documentation and diagnostic messages
SBGoods May 7, 2024
e6a3700
Merge branch 'main' into SBGoods/deferred-action-support
SBGoods May 7, 2024
3a5a8c3
Add copyright headers
SBGoods May 7, 2024
04042c2
Add changelog entries
SBGoods May 8, 2024
ea64005
Apply suggestions from code review
SBGoods May 10, 2024
ac952fc
Add comment and changelog notes to indicate experimental nature of de…
SBGoods May 10, 2024
bcadaee
Rename constant to be specific to data sources
SBGoods May 13, 2024
3777d5d
Remove TODO comment
SBGoods May 13, 2024
0c48915
Remove unnecessary nil check
SBGoods May 13, 2024
95ef72e
Add default values for `ClientCapabilities` request fields
SBGoods May 13, 2024
c5156c5
Rename `DeferredResponse` to `Deferred`
SBGoods May 13, 2024
96166fb
Remove error handling for deferral response without deferral capability
SBGoods May 13, 2024
0a6b8a5
Remove variable indirection in tests
SBGoods May 13, 2024
f1112d5
Add copyright headers
SBGoods May 13, 2024
2433dca
Apply suggestions from code review
SBGoods May 14, 2024
50aaca9
Add unit tests for client capabilities
SBGoods May 14, 2024
f5d659d
Merge branch 'main' into SBGoods/deferred-action-support
SBGoods May 14, 2024
a705a12
Implement deferred action support for `provider.configureProvider`
SBGoods May 14, 2024
5ab0c24
Move client capabilities defaulting behavior to `fromproto5/6` package
SBGoods May 14, 2024
038d486
Move `toproto5/6` `Deferred` conversion handling to its own files
SBGoods May 14, 2024
8f0b6aa
Use `ResourceDeferred()` and `DataSourceDeferred()` functions in `top…
SBGoods May 15, 2024
720016f
Merge branch 'refs/heads/SBGoods/deferred-action-support' into SBGood…
SBGoods May 15, 2024
b92fc38
move configure provider client capabilities unset test to `fromproto5/6`
SBGoods May 15, 2024
c89dcdc
Add throw an error diagnostic in server_configureprovider.go if a def…
SBGoods May 15, 2024
188522e
Add `ResourceBehavior` field to `MetadataResponse`
SBGoods May 16, 2024
693e708
Initial implementation of automatic deferrals for resource/datasource…
SBGoods May 16, 2024
b29ca83
Merge branch 'refs/heads/main' into SBGoods/automatic-deferred-action…
SBGoods May 16, 2024
a5089d6
Implement provider automatic deferral for `PlanResourceChange` RPC
SBGoods May 17, 2024
9fd5971
Add default values for automatic deferrals
SBGoods May 20, 2024
a1e8983
Update resource/metadata.go
SBGoods May 20, 2024
299ac68
Add experimental note
SBGoods May 20, 2024
bc60806
Merge remote-tracking branch 'origin/SBGoods/automatic-deferred-actio…
SBGoods May 20, 2024
4669aaa
Implement resource behavior in `proto6server`
SBGoods May 20, 2024
fe78dbb
Add changelog entries
SBGoods May 20, 2024
1cb1aa4
Merge branch 'main' into SBGoods/automatic-deferred-action-support
SBGoods May 20, 2024
277ce82
Apply suggestions from code review
SBGoods May 21, 2024
8593412
Log deferred reason in debug logging
SBGoods May 21, 2024
904a87f
Add error diagnostics to automatic deferral tests
SBGoods May 21, 2024
31f33f8
Refactor `PlanResourceChange` automatic deferred action implementatio…
SBGoods May 23, 2024
a05621a
Add a comment calling out intentional design of replacing configured …
SBGoods May 23, 2024
a3dbd79
Return early in `PlanResourceChange` if `ProviderDeferredBehavior.Ena…
SBGoods May 29, 2024
b3f4d24
Update internal/fwserver/server_configureprovider.go
SBGoods May 30, 2024
a7d7292
Add separate unit test for overriding provider deferred reason with r…
SBGoods May 30, 2024
7a578b1
Merge branch 'main' into SBGoods/automatic-deferred-action-support
SBGoods May 30, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Implement manual deferred action support for `resource.importResource…
…State`
  • Loading branch information
SBGoods committed May 1, 2024
commit df3f0cf2b664ac40b94515bfdf8c05d0fc19967c
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ toolchain go1.21.6

require (
github.com/google/go-cmp v0.6.0
github.com/hashicorp/terraform-plugin-go v0.22.2
github.com/hashicorp/terraform-plugin-go v0.22.3-0.20240424182422-dba624001d4f
github.com/hashicorp/terraform-plugin-log v0.9.0
)

Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDm
github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/terraform-plugin-go v0.22.2 h1:5o8uveu6eZUf5J7xGPV0eY0TPXg3qpmwX9sce03Bxnc=
github.com/hashicorp/terraform-plugin-go v0.22.2/go.mod h1:drq8Snexp9HsbFZddvyLHN6LuWHHndSQg+gV+FPkcIM=
github.com/hashicorp/terraform-plugin-go v0.22.3-0.20240419152848-9a1607db1cab h1:Q86RQOyr+03Z+MbEi1hhlDADMwc0k24XYqASQAYPE0A=
github.com/hashicorp/terraform-plugin-go v0.22.3-0.20240419152848-9a1607db1cab/go.mod h1:drq8Snexp9HsbFZddvyLHN6LuWHHndSQg+gV+FPkcIM=
github.com/hashicorp/terraform-plugin-go v0.22.3-0.20240424182422-dba624001d4f h1:r6EtFgZbSf+VeNrWqFwTFl5mKBdlFl+bpufwgaNBeQA=
github.com/hashicorp/terraform-plugin-go v0.22.3-0.20240424182422-dba624001d4f/go.mod h1:DbW1zoh21fsPD35whjnDA5aR7bXQV+k+lnkZrn8ZrFM=
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow=
github.com/hashicorp/terraform-registry-address v0.2.3 h1:2TAiKJ1A3MAkZlH1YI/aTVcLZRu7JseiXNRHbOAyoTI=
Expand Down
14 changes: 9 additions & 5 deletions internal/fromproto5/importresourcestate.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@ package fromproto5
import (
"context"

"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/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

// ImportResourceStateRequest returns the *fwserver.ImportResourceStateRequest
// equivalent of a *tfprotov5.ImportResourceStateRequest.
func ImportResourceStateRequest(ctx context.Context, proto5 *tfprotov5.ImportResourceStateRequest, resource resource.Resource, resourceSchema fwschema.Schema) (*fwserver.ImportResourceStateRequest, diag.Diagnostics) {
func ImportResourceStateRequest(ctx context.Context, proto5 *tfprotov5.ImportResourceStateRequest, reqResource resource.Resource, resourceSchema fwschema.Schema) (*fwserver.ImportResourceStateRequest, diag.Diagnostics) {
if proto5 == nil {
return nil, nil
}
Expand All @@ -43,8 +44,11 @@ func ImportResourceStateRequest(ctx context.Context, proto5 *tfprotov5.ImportRes
Raw: tftypes.NewValue(resourceSchema.Type().TerraformType(ctx), nil),
Schema: resourceSchema,
},
ID: proto5.ID,
Resource: resource,
ID: proto5.ID,
ClientCapabilities: &resource.ImportStateClientCapabilities{
DeferralAllowed: proto5.ClientCapabilities.DeferralAllowed,
},
Resource: reqResource,
TypeName: proto5.TypeName,
}

Expand Down
21 changes: 19 additions & 2 deletions internal/fromproto5/importresourcestate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ 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/internal/fwserver"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

func TestImportResourceStateRequest(t *testing.T) {
Expand Down Expand Up @@ -86,6 +87,22 @@ func TestImportResourceStateRequest(t *testing.T) {
TypeName: "test_resource",
},
},
"client-capabilities": {
input: &tfprotov5.ImportResourceStateRequest{
ID: "test-id",
ClientCapabilities: &tfprotov5.ClientCapabilities{
DeferralAllowed: true,
},
},
resourceSchema: testFwSchema,
expected: &fwserver.ImportResourceStateRequest{
EmptyState: testFwEmptyState,
ID: "test-id",
ClientCapabilities: &resource.ImportStateClientCapabilities{
DeferralAllowed: true,
},
},
},
}

for name, testCase := range testCases {
Expand Down
14 changes: 9 additions & 5 deletions internal/fromproto6/importresourcestate.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@ package fromproto6
import (
"context"

"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/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

// ImportResourceStateRequest returns the *fwserver.ImportResourceStateRequest
// equivalent of a *tfprotov6.ImportResourceStateRequest.
func ImportResourceStateRequest(ctx context.Context, proto6 *tfprotov6.ImportResourceStateRequest, resource resource.Resource, resourceSchema fwschema.Schema) (*fwserver.ImportResourceStateRequest, diag.Diagnostics) {
func ImportResourceStateRequest(ctx context.Context, proto6 *tfprotov6.ImportResourceStateRequest, reqResource resource.Resource, resourceSchema fwschema.Schema) (*fwserver.ImportResourceStateRequest, diag.Diagnostics) {
if proto6 == nil {
return nil, nil
}
Expand All @@ -43,8 +44,11 @@ func ImportResourceStateRequest(ctx context.Context, proto6 *tfprotov6.ImportRes
Raw: tftypes.NewValue(resourceSchema.Type().TerraformType(ctx), nil),
Schema: resourceSchema,
},
ID: proto6.ID,
Resource: resource,
ID: proto6.ID,
ClientCapabilities: &resource.ImportStateClientCapabilities{
DeferralAllowed: proto6.ClientCapabilities.DeferralAllowed,
},
Resource: reqResource,
TypeName: proto6.TypeName,
}

Expand Down
21 changes: 19 additions & 2 deletions internal/fromproto6/importresourcestate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ 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/internal/fwserver"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

func TestImportResourceStateRequest(t *testing.T) {
Expand Down Expand Up @@ -86,6 +87,22 @@ func TestImportResourceStateRequest(t *testing.T) {
TypeName: "test_resource",
},
},
"client-capabilities": {
input: &tfprotov6.ImportResourceStateRequest{
ID: "test-id",
ClientCapabilities: &tfprotov6.ClientCapabilities{
DeferralAllowed: true,
},
},
resourceSchema: testFwSchema,
expected: &fwserver.ImportResourceStateRequest{
EmptyState: testFwEmptyState,
ID: "test-id",
ClientCapabilities: &resource.ImportStateClientCapabilities{
DeferralAllowed: true,
},
},
},
}

for name, testCase := range testCases {
Expand Down
21 changes: 20 additions & 1 deletion internal/fwserver/server_importresourcestate.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,17 @@ type ImportResourceStateRequest struct {
// TypeName is the resource type name, which is necessary for populating
// the ImportedResource TypeName of the ImportResourceStateResponse.
TypeName string

//TODO: doc
ClientCapabilities *resource.ImportStateClientCapabilities
}

// ImportResourceStateResponse is the framework server response for the
// ImportResourceState RPC.
type ImportResourceStateResponse struct {
Diagnostics diag.Diagnostics
ImportedResources []ImportedResource
Deferral *resource.DeferralResponse
}

// ImportResourceState implements the framework server ImportResourceState RPC.
Expand Down Expand Up @@ -90,7 +94,8 @@ func (s *Server) ImportResourceState(ctx context.Context, req *ImportResourceSta
}

importReq := resource.ImportStateRequest{
ID: req.ID,
ID: req.ID,
ClientCapabilities: req.ClientCapabilities,
}

privateProviderData := privatestate.EmptyProviderData(ctx)
Expand All @@ -113,6 +118,16 @@ func (s *Server) ImportResourceState(ctx context.Context, req *ImportResourceSta
return
}

if (importReq.ClientCapabilities == nil || !importReq.ClientCapabilities.DeferralAllowed) && importResp.DeferralResponse != nil {
resp.Diagnostics.AddError(
"Resource Import Deferral Not Allowed",
"An unexpected error was encountered when importing the resource. This is always a problem with the provider. Please give the following information to the provider developer:\n\n"+
"The resource requested a deferral but the Terraform client does not support deferrals, "+
"resource.DeferralResponse can only be set if resource.ImportStateRequest.ImportStateClientCapabilities.DeferralAllowed is true.",
)
return
}

if importResp.State.Raw.Equal(req.EmptyState.Raw) {
resp.Diagnostics.AddError(
"Missing Resource Import State",
Expand All @@ -128,6 +143,10 @@ func (s *Server) ImportResourceState(ctx context.Context, req *ImportResourceSta
private.Provider = importResp.Private
}

if importResp.DeferralResponse != nil {
resp.Deferral = importResp.DeferralResponse
}

resp.ImportedResources = []ImportedResource{
{
State: importResp.State,
Expand Down
75 changes: 75 additions & 0 deletions internal/fwserver/server_importresourcestate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ func TestServerImportResourceState(t *testing.T) {
Provider: testEmptyProviderData,
}

testDeferral := &resource.ImportStateClientCapabilities{
DeferralAllowed: true,
}

testCases := map[string]struct {
server *fwserver.Server
request *fwserver.ImportResourceStateRequest
Expand Down Expand Up @@ -299,6 +303,77 @@ func TestServerImportResourceState(t *testing.T) {
},
},
},
"request-deferral-allowed-response-deferral": {
server: &fwserver.Server{
Provider: &testprovider.Provider{},
},
request: &fwserver.ImportResourceStateRequest{
EmptyState: *testEmptyState,
ID: "test-id",
Resource: &testprovider.ResourceWithImportState{
Resource: &testprovider.Resource{},
ImportStateMethod: func(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
if req.ID != "test-id" {
resp.Diagnostics.AddError("unexpected req.ID value: %s", req.ID)
}

resp.DeferralResponse = &resource.DeferralResponse{
Reason: resource.DeferralReasonAbsentPrereq,
}

resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)

},
},
TypeName: "test_resource",
ClientCapabilities: testDeferral,
},
expectedResponse: &fwserver.ImportResourceStateResponse{
ImportedResources: []fwserver.ImportedResource{
{
State: *testState,
TypeName: "test_resource",
Private: testEmptyPrivate,
},
},
Deferral: &resource.DeferralResponse{Reason: resource.DeferralReasonAbsentPrereq},
},
},
"request-deferral-not-allowed-response-deferral": {
server: &fwserver.Server{
Provider: &testprovider.Provider{},
},
request: &fwserver.ImportResourceStateRequest{
EmptyState: *testEmptyState,
ID: "test-id",
Resource: &testprovider.ResourceWithImportState{
Resource: &testprovider.Resource{},
ImportStateMethod: func(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
if req.ID != "test-id" {
resp.Diagnostics.AddError("unexpected req.ID value: %s", req.ID)
}

resp.DeferralResponse = &resource.DeferralResponse{
Reason: resource.DeferralReasonAbsentPrereq,
}

resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)

},
},
TypeName: "test_resource",
},
expectedResponse: &fwserver.ImportResourceStateResponse{
Diagnostics: diag.Diagnostics{
diag.NewErrorDiagnostic(
"Resource Import Deferral Not Allowed",
"An unexpected error was encountered when importing the resource. This is always a problem with the provider. Please give the following information to the provider developer:\n\n"+
"The resource requested a deferral but the Terraform client does not support deferrals, "+
"resource.DeferralResponse can only be set if resource.ImportStateRequest.ImportStateClientCapabilities.DeferralAllowed is true.",
),
},
},
},
}

for name, testCase := range testCases {
Expand Down
7 changes: 6 additions & 1 deletion internal/toproto5/importresourcestate.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ package toproto5
import (
"context"

"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"

"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
)

// ImportResourceStateResponse returns the *tfprotov5.ImportResourceStateResponse
Expand All @@ -33,5 +34,9 @@ func ImportResourceStateResponse(ctx context.Context, fw *fwserver.ImportResourc
proto5.ImportedResources = append(proto5.ImportedResources, proto5ImportedResource)
}

if fw.Deferral != nil {
proto5.Deferred.Reason = tfprotov5.DeferredReason(fw.Deferral.Reason)
}

return proto5
}
7 changes: 6 additions & 1 deletion internal/toproto6/importresourcestate.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ package toproto6
import (
"context"

"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"

"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
)

// ImportResourceStateResponse returns the *tfprotov6.ImportResourceStateResponse
Expand All @@ -33,5 +34,9 @@ func ImportResourceStateResponse(ctx context.Context, fw *fwserver.ImportResourc
proto6.ImportedResources = append(proto6.ImportedResources, proto6ImportedResource)
}

if fw.Deferral != nil {
proto6.Deferred.Reason = tfprotov6.DeferredReason(fw.Deferral.Reason)
}

return proto6
}
28 changes: 28 additions & 0 deletions resource/deferral.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package resource

const (
DeferralReasonUnknown DeferralReason = 0
DeferralReasonResourceConfigUnknown DeferralReason = 1
DeferralReasonProviderConfigUnknown DeferralReason = 2
DeferralReasonAbsentPrereq DeferralReason = 3
)

type DeferralResponse struct {
Reason DeferralReason
}

type DeferralReason int32

func (d DeferralReason) String() string {
switch d {
case 0:
return "Unknown"
case 1:
return "Resource Config Unknown"
case 2:
return "Provider Config Unknown"
case 3:
return "Absent Prerequisite"
}
return "Unknown"
}
Loading