Skip to content

Commit

Permalink
all: Add support for Manual Deferred Actions (#999)
Browse files Browse the repository at this point in the history
* Implement manual deferred action support for `resource.importResourceState`

* Implement manual deferred action support for `resource.readResource`

* Implement manual deferred action support for `resource.modifyPlan`

* Rename `DeferralReason` and `DeferralResponse` to `DeferredReason` and `DeferredResponse` respectively to match protobuf definition

* Update `terraform-plugin-go` dependency to `v0.23.0`

* Rename `deferral.go` to `deferred.go`

* Implement manual deferred action support for data sources

* Update documentation and diagnostic messages

* Add copyright headers

* Add changelog entries

* Apply suggestions from code review

Co-authored-by: Austin Valle <[email protected]>

* Add comment and changelog notes to indicate experimental nature of deferred actions.

* Rename constant to be specific to data sources

* Remove TODO comment

* Remove unnecessary nil check

* Add default values for `ClientCapabilities` request fields

* Rename `DeferredResponse` to `Deferred`

* Remove error handling for deferral response without deferral capability

* Remove variable indirection in tests

* Add copyright headers

* Apply suggestions from code review

Co-authored-by: Brian Flad <[email protected]>

* Add unit tests for client capabilities

* Move client capabilities defaulting behavior to `fromproto5/6` package

* Move `toproto5/6` `Deferred` conversion handling to its own files

* Use `ResourceDeferred()` and `DataSourceDeferred()` functions in `toproto6` package

---------

Co-authored-by: Austin Valle <[email protected]>
Co-authored-by: Brian Flad <[email protected]>
  • Loading branch information
3 people authored May 16, 2024
1 parent ec16ec0 commit 24d09fe
Show file tree
Hide file tree
Showing 54 changed files with 1,118 additions and 66 deletions.
6 changes: 6 additions & 0 deletions .changes/unreleased/FEATURES-20240508-105105.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: FEATURES
body: 'resource: Add `Deferred` 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 `Deferred` 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"
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"
7 changes: 7 additions & 0 deletions .changes/unreleased/NOTES-20240510-143136.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
kind: NOTES
body: This release contains support for deferred actions, which is an experimental
feature only available in prerelease builds of Terraform 1.9 and later. This functionality
is subject to change and is not protected by version compatibility guarantees.
time: 2024-05-10T14:31:36.644869-04:00
custom:
Issue: "999"
50 changes: 50 additions & 0 deletions datasource/deferred.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// 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

// DeferredReasonDataSourceConfigUnknown 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.
DeferredReasonDataSourceConfigUnknown DeferredReason = 1

// 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
)

// Deferred is used to indicate to Terraform that a change needs to be deferred for a reason.
//
// NOTE: This functionality is related to deferred action support, which is currently experimental and is subject
// to change or break without warning. It is not protected by version compatibility guarantees.
type Deferred struct {
// Reason is the reason for deferring the change.
Reason DeferredReason
}

// DeferredReason represents different reasons for deferring a change.
//
// NOTE: This functionality is related to deferred action support, which is currently experimental and is subject
// to change or break without warning. It is not protected by version compatibility guarantees.
type DeferredReason int32

func (d DeferredReason) String() string {
switch d {
case 0:
return "Unknown"
case 1:
return "Data Source Config Unknown"
case 2:
return "Provider Config Unknown"
case 3:
return "Absent Prerequisite"
}
return "Unknown"
}
26 changes: 26 additions & 0 deletions datasource/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ 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.
//
// NOTE: This functionality is related to deferred action support, which is currently experimental and is subject
// to change or break without warning. It is not protected by version compatibility guarantees.
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 +34,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
}

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

// Deferred indicates that Terraform should defer reading this
// data source until a followup apply operation.
//
// This field can only be set if
// `(datasource.ReadRequest).ClientCapabilities.DeferralAllowed` is true.
//
// NOTE: This functionality is related to deferred action support, which is currently experimental and is subject
// to change or break without warning. It is not protected by version compatibility guarantees.
Deferred *Deferred
}
63 changes: 63 additions & 0 deletions internal/fromproto5/client_capabilities.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package fromproto5

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

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

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

return datasource.ReadClientCapabilities{
DeferralAllowed: in.DeferralAllowed,
}
}

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

return resource.ReadClientCapabilities{
DeferralAllowed: in.DeferralAllowed,
}
}

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

return resource.ModifyPlanClientCapabilities{
DeferralAllowed: in.DeferralAllowed,
}
}

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

return resource.ImportStateClientCapabilities{
DeferralAllowed: in.DeferralAllowed,
}
}
14 changes: 8 additions & 6 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,9 +44,10 @@ func ImportResourceStateRequest(ctx context.Context, proto5 *tfprotov5.ImportRes
Raw: tftypes.NewValue(resourceSchema.Type().TerraformType(ctx), nil),
Schema: resourceSchema,
},
ID: proto5.ID,
Resource: resource,
TypeName: proto5.TypeName,
ID: proto5.ID,
Resource: reqResource,
TypeName: proto5.TypeName,
ClientCapabilities: ImportStateClientCapabilities(proto5.ClientCapabilities),
}

return fw, diags
Expand Down
34 changes: 32 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,35 @@ 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,
},
},
},
"client-capabilities-unset": {
input: &tfprotov5.ImportResourceStateRequest{
ID: "test-id",
},
resourceSchema: testFwSchema,
expected: &fwserver.ImportResourceStateRequest{
EmptyState: testFwEmptyState,
ID: "test-id",
ClientCapabilities: resource.ImportStateClientCapabilities{
DeferralAllowed: false,
},
},
},
}

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
7 changes: 4 additions & 3 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 @@ -39,8 +39,9 @@ func PlanResourceChangeRequest(ctx context.Context, proto5 *tfprotov5.PlanResour
}

fw := &fwserver.PlanResourceChangeRequest{
ResourceSchema: resourceSchema,
Resource: resource,
ResourceSchema: resourceSchema,
Resource: reqResource,
ClientCapabilities: ModifyPlanClientCapabilities(proto5.ClientCapabilities),
}

config, configDiags := Config(ctx, proto5.Config, resourceSchema)
Expand Down
24 changes: 24 additions & 0 deletions internal/fromproto5/planresourcechange_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,30 @@ func TestPlanResourceChangeRequest(t *testing.T) {
ResourceSchema: testFwSchema,
},
},
"client-capabilities": {
input: &tfprotov5.PlanResourceChangeRequest{
ClientCapabilities: &tfprotov5.PlanResourceChangeClientCapabilities{
DeferralAllowed: true,
},
},
resourceSchema: testFwSchema,
expected: &fwserver.PlanResourceChangeRequest{
ClientCapabilities: resource.ModifyPlanClientCapabilities{
DeferralAllowed: true,
},
ResourceSchema: testFwSchema,
},
},
"client-capabilities-unset": {
input: &tfprotov5.PlanResourceChangeRequest{},
resourceSchema: testFwSchema,
expected: &fwserver.PlanResourceChangeRequest{
ClientCapabilities: resource.ModifyPlanClientCapabilities{
DeferralAllowed: false,
},
ResourceSchema: testFwSchema,
},
},
}

for name, testCase := range testCases {
Expand Down
8 changes: 5 additions & 3 deletions 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 All @@ -37,8 +38,9 @@ func ReadDataSourceRequest(ctx context.Context, proto5 *tfprotov5.ReadDataSource
}

fw := &fwserver.ReadDataSourceRequest{
DataSource: dataSource,
DataSourceSchema: dataSourceSchema,
DataSource: dataSource,
DataSourceSchema: dataSourceSchema,
ClientCapabilities: ReadDataSourceClientCapabilities(proto5.ClientCapabilities),
}

config, configDiags := Config(ctx, proto5.Config, dataSourceSchema)
Expand Down
Loading

0 comments on commit 24d09fe

Please sign in to comment.