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 support for Manual Deferred Actions #999

Merged
merged 27 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
27 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
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
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
6 changes: 6 additions & 0 deletions .changes/unreleased/FEATURES-20240508-105105.yaml
SBGoods marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: FEATURES
body: 'resource: Add `DeferredResponse` field to `ReadResponse`, `ModifyPlanResponse`, and `ImportStateResponse`
which indicates a resource deferred action to the Terraform client'
time: 2024-05-08T10:51:05.90518-04:00
custom:
Issue: "999"
6 changes: 6 additions & 0 deletions .changes/unreleased/FEATURES-20240508-105141.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: FEATURES
body: 'datasource: Add `DeferredResponse` field to `ReadResponse` which indicates a data source deferred action
to the Terraform client'
time: 2024-05-08T10:51:41.663935-04:00
custom:
Issue: "999"
austinvalle marked this conversation as resolved.
Show resolved Hide resolved
6 changes: 6 additions & 0 deletions .changes/unreleased/FEATURES-20240508-110715.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: FEATURES
body: 'resource: Add `ClientCapabilities` field to `ReadRequest`, `ModifyPlanRequest`, and `ImportStateRequest`
which specifies optionally supported protocol features for the Terraform client'
time: 2024-05-08T11:07:15.955126-04:00
custom:
Issue: "999"
6 changes: 6 additions & 0 deletions .changes/unreleased/FEATURES-20240508-111023.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: FEATURES
body: 'datasource: Add `ClientCapabilities` field to `ReadRequest` which specifies
optionally supported protocol features for the Terraform client'
time: 2024-05-08T11:10:23.66584-04:00
custom:
Issue: "999"
44 changes: 44 additions & 0 deletions datasource/deferred.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package datasource

const (
// DeferredReasonUnknown is used to indicate an invalid `DeferredReason`.
// Provider developers should not use it.
DeferredReasonUnknown DeferredReason = 0

// DeferredReasonResourceConfigUnknown is used to indicate that the resource configuration
// is partially unknown and the real values need to be known before the change can be planned.
DeferredReasonResourceConfigUnknown DeferredReason = 1
SBGoods marked this conversation as resolved.
Show resolved Hide resolved

// DeferredReasonProviderConfigUnknown is used to indicate that the provider configuration
// is partially unknown and the real values need to be known before the change can be planned.
DeferredReasonProviderConfigUnknown DeferredReason = 2

// DeferredReasonAbsentPrereq is used to indicate that a hard dependency has not been satisfied.
DeferredReasonAbsentPrereq DeferredReason = 3
)

// DeferredResponse is used to indicate to Terraform that a change needs to be deferred for a reason.
type DeferredResponse struct {
// Reason is the reason for deferring the change.
Reason DeferredReason
}

// DeferredReason represents different reasons for deferring a change.
type DeferredReason int32

func (d DeferredReason) String() string {
switch d {
case 0:
return "Unknown"
case 1:
return "Resource Config Unknown"
SBGoods marked this conversation as resolved.
Show resolved Hide resolved
case 2:
return "Provider Config Unknown"
case 3:
return "Absent Prerequisite"
}
return "Unknown"
}
20 changes: 20 additions & 0 deletions datasource/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ import (
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
)

// ReadClientCapabilities allows Terraform to publish information
// regarding optionally supported protocol features for the ReadDataSource RPC,
// such as forward-compatible Terraform behavior changes.
type ReadClientCapabilities struct {
// DeferralAllowed indicates whether the Terraform client initiating
// the request allows a deferral response.
DeferralAllowed bool
}

// ReadRequest represents a request for the provider to read a data
// source, i.e., update values in state according to the real state of the
// data source. An instance of this request struct is supplied as an argument
Expand All @@ -22,6 +31,10 @@ type ReadRequest struct {

// ProviderMeta is metadata from the provider_meta block of the module.
ProviderMeta tfsdk.Config

// ClientCapabilities defines optionally supported protocol features for the
// ReadDataSource RPC, such as forward-compatible Terraform behavior changes.
ClientCapabilities *ReadClientCapabilities
SBGoods marked this conversation as resolved.
Show resolved Hide resolved
}

// ReadResponse represents a response to a ReadRequest. An
Expand All @@ -37,4 +50,11 @@ type ReadResponse struct {
// source. An empty slice indicates a successful operation with no
// warnings or errors generated.
Diagnostics diag.Diagnostics

// DeferredResponse indicates that Terraform should defer
// importing this resource.
SBGoods marked this conversation as resolved.
Show resolved Hide resolved
//
// This field can only be set if
// `(datasource.ReadRequest.ReadClientCapabilities).DeferralAllowed` is true.
SBGoods marked this conversation as resolved.
Show resolved Hide resolved
DeferredResponse *DeferredResponse
SBGoods marked this conversation as resolved.
Show resolved Hide resolved
}
15 changes: 11 additions & 4 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 @@ -44,9 +45,15 @@ func ImportResourceStateRequest(ctx context.Context, proto5 *tfprotov5.ImportRes
Schema: resourceSchema,
},
ID: proto5.ID,
Resource: resource,
Resource: reqResource,
TypeName: proto5.TypeName,
}

if proto5.ClientCapabilities != nil {
fw.ClientCapabilities = &resource.ImportStateClientCapabilities{
DeferralAllowed: proto5.ClientCapabilities.DeferralAllowed,
}
}
SBGoods marked this conversation as resolved.
Show resolved Hide resolved

return fw, diags
}
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.ImportResourceStateClientCapabilities{
DeferralAllowed: true,
},
},
resourceSchema: testFwSchema,
expected: &fwserver.ImportResourceStateRequest{
EmptyState: testFwEmptyState,
ID: "test-id",
ClientCapabilities: &resource.ImportStateClientCapabilities{
DeferralAllowed: true,
},
},
},
}

for name, testCase := range testCases {
Expand Down
3 changes: 2 additions & 1 deletion internal/fromproto5/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ 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/internal/fwschemadata"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
)

// Plan returns the *tfsdk.Plan for a *tfprotov5.DynamicValue and
Expand Down
10 changes: 8 additions & 2 deletions 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, resource 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) (*fwserver.PlanResourceChangeRequest, diag.Diagnostics) {
if proto5 == nil {
return nil, nil
}
Expand All @@ -40,7 +40,7 @@ func PlanResourceChangeRequest(ctx context.Context, proto5 *tfprotov5.PlanResour

fw := &fwserver.PlanResourceChangeRequest{
ResourceSchema: resourceSchema,
Resource: resource,
Resource: reqResource,
}

config, configDiags := Config(ctx, proto5.Config, resourceSchema)
Expand Down Expand Up @@ -73,5 +73,11 @@ func PlanResourceChangeRequest(ctx context.Context, proto5 *tfprotov5.PlanResour

fw.PriorPrivate = privateData

if proto5.ClientCapabilities != nil {
fw.ClientCapabilities = &resource.ModifyPlanClientCapabilities{
DeferralAllowed: proto5.ClientCapabilities.DeferralAllowed,
}
}

return fw, diags
}
14 changes: 14 additions & 0 deletions internal/fromproto5/planresourcechange_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ func TestPlanResourceChangeRequest(t *testing.T) {

testProviderData := privatestate.MustProviderData(context.Background(), testProviderKeyValue)

testClientCapabilities := tfprotov5.PlanResourceChangeClientCapabilities{DeferralAllowed: true}

testCases := map[string]struct {
input *tfprotov5.PlanResourceChangeRequest
resourceSchema fwschema.Schema
Expand Down Expand Up @@ -217,6 +219,18 @@ func TestPlanResourceChangeRequest(t *testing.T) {
ResourceSchema: testFwSchema,
},
},
"client-capabilities": {
input: &tfprotov5.PlanResourceChangeRequest{
ClientCapabilities: &testClientCapabilities,
},
resourceSchema: testFwSchema,
expected: &fwserver.PlanResourceChangeRequest{
ClientCapabilities: &resource.ModifyPlanClientCapabilities{
DeferralAllowed: true,
},
ResourceSchema: testFwSchema,
},
},
}

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

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

"github.com/hashicorp/terraform-plugin-framework/datasource"
"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-go/tfprotov5"
)

// ReadDataSourceRequest returns the *fwserver.ReadDataSourceRequest
Expand Down Expand Up @@ -53,5 +54,11 @@ func ReadDataSourceRequest(ctx context.Context, proto5 *tfprotov5.ReadDataSource

fw.ProviderMeta = providerMeta

if proto5.ClientCapabilities != nil {
fw.ClientCapabilities = &datasource.ReadClientCapabilities{
DeferralAllowed: proto5.ClientCapabilities.DeferralAllowed,
}
}

return fw, diags
}
20 changes: 18 additions & 2 deletions internal/fromproto5/readdatasource_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright (c) HashiCorp, Inc.

SBGoods marked this conversation as resolved.
Show resolved Hide resolved
// SPDX-License-Identifier: MPL-2.0

package fromproto5_test
Expand All @@ -8,15 +9,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/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"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/tfsdk"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

func TestReadDataSourceRequest(t *testing.T) {
Expand Down Expand Up @@ -46,6 +48,8 @@ func TestReadDataSourceRequest(t *testing.T) {
},
}

testClientCapabilities := tfprotov5.ReadDataSourceClientCapabilities{DeferralAllowed: true}

testCases := map[string]struct {
input *tfprotov5.ReadDataSourceRequest
dataSourceSchema fwschema.Schema
Expand Down Expand Up @@ -135,6 +139,18 @@ func TestReadDataSourceRequest(t *testing.T) {
},
},
},
"client-capabilities": {
input: &tfprotov5.ReadDataSourceRequest{
ClientCapabilities: &testClientCapabilities,
},
dataSourceSchema: testFwSchema,
expected: &fwserver.ReadDataSourceRequest{
DataSourceSchema: testFwSchema,
ClientCapabilities: &datasource.ReadClientCapabilities{
DeferralAllowed: true,
},
},
},
}

for name, testCase := range testCases {
Expand Down
10 changes: 8 additions & 2 deletions internal/fromproto5/readresource.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ import (

// ReadResourceRequest returns the *fwserver.ReadResourceRequest
// equivalent of a *tfprotov5.ReadResourceRequest.
func ReadResourceRequest(ctx context.Context, proto5 *tfprotov5.ReadResourceRequest, resource resource.Resource, resourceSchema fwschema.Schema, providerMetaSchema fwschema.Schema) (*fwserver.ReadResourceRequest, diag.Diagnostics) {
func ReadResourceRequest(ctx context.Context, proto5 *tfprotov5.ReadResourceRequest, reqResource resource.Resource, resourceSchema fwschema.Schema, providerMetaSchema fwschema.Schema) (*fwserver.ReadResourceRequest, diag.Diagnostics) {
if proto5 == nil {
return nil, nil
}

var diags diag.Diagnostics

fw := &fwserver.ReadResourceRequest{
Resource: resource,
Resource: reqResource,
}

currentState, currentStateDiags := State(ctx, proto5.CurrentState, resourceSchema)
Expand All @@ -46,5 +46,11 @@ func ReadResourceRequest(ctx context.Context, proto5 *tfprotov5.ReadResourceRequ

fw.Private = privateData

if proto5.ClientCapabilities != nil {
fw.ClientCapabilities = &resource.ReadClientCapabilities{
DeferralAllowed: proto5.ClientCapabilities.DeferralAllowed,
}
}

return fw, diags
}
Loading
Loading