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

[DRAFT] Add documentation for deferred actions #1007

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
34 changes: 34 additions & 0 deletions website/docs/plugin/framework/data-sources/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,40 @@ Implement the `Read` method by:

If the logic needs to return [warning or error diagnostics](/terraform/plugin/framework/diagnostics), they can added into the [`datasource.ReadResponse.Diagnostics` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#ReadResponse.Diagnostics).

#### Data Source Deferral
-> 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.

The `Read` method can indicate a deferral which will defer the data source and all of its dependencies.

To indicate a deferral,
first check the [`datasource.ReadRequest.ClientCapabilities.DeferralAllowed` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#ReadClientCapabilities.DeferralAllowed)
to verify that the calling Terraform client supports deferred action functionality.
Then indicate a data source deferral
by setting the [`datasource.ReadResponse.Deferral` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#ReadResponse.Deferred).

In the following example, the data source will defer if the `example_attribute` value is unknown:

```go
func (d *ThingDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data ThingDataSourceModel

// Read Terraform configuration data into the model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)

// Check that the Terraform client supports deferred actions and if so,
// defer the data source if example_attribute is unknown
if req.ClientCapabilities.DeferralAllowed && data.ExampleAttribute.IsUnknown() {
resp.Deferred = &datasource.Deferred{
Reason: datasource.DeferredReasonDataSourceConfigUnknown,
}
// Return early for deferral
return
}

// Set the Terraform state as appropriate
}
```

## Add Data Source to Provider

Data sources become available to practitioners when they are included in the [provider](/terraform/plugin/framework/providers) implementation via the [`provider.ProviderWithDataSources` interface `DataSources` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ProviderWithDataSources.DataSources).
Expand Down
48 changes: 48 additions & 0 deletions website/docs/plugin/framework/providers/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,54 @@ to use it. If resources and data sources can't provide any functionality
without knowing that value, it's often better to [return an
error](/terraform/plugin/framework/diagnostics), which will halt the apply.

#### Provider Deferral
-> 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.

A provider can indicate an automatic deferral in the `Configure` method,
which will automatically defer all resources and data sources for the provider.

To indicate a deferral,
first check the [`provider.ConfigureRequest.ClientCapabilities.DeferralAllowed` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ConfigureProviderClientCapabilities.DeferralAllowed)
to verify that the calling Terraform client supports deferred action functionality.
Then indicate a provider deferral
by setting the [`provider.ConfigureResponse.Deferral` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ConfigureResponse.Deferred).

In the following example, the provider will automatically defer if the API token value is unknown:

```go
func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
var data ExampleCloudProviderModel

// Read configuration data into model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)

// Check that the Terraform client supports deferred actions
if req.ClientCapabilities.DeferralAllowed && config.ApiToken.isUnknown() {
resp.Deferred = &provider.Deferred{
Reason: provider.DeferredReasonProviderConfigUnknown,
}
// Return early for deferral since config is not completely known
return
}

// Create data/clients and persist to resp.DataSourceData and
// resp.ResourceData as appropriate.
}
```

##### Opt-in for Plan Modification
By default,
automatically deferred resources will skip plan modification logic defined in a resource's `ModifyPlan` method,
but a resource can opt in for this logic to be called
by setting the [`ResourceBehavior.ProviderDeferred.EnablePlanModification` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ProviderDeferredBehavior.EnablePlanModification) in each resource’s `Metadata` method:

```go
func (r *ThingResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_thing"
resp.ResourceBehavior.ProviderDeferred.EnablePlanModification = true
}
```

### Resources

The [`provider.Provider` interface `Resources` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Resources) returns a slice of [resources](/terraform/plugin/framework/resources). Each element in the slice is a function to create a new `resource.Resource` so data is not inadvertently shared across multiple, disjointed resource instance operations unless explicitly coded. Information such as the resource type name is managed by the `resource.Resource` implementation.
Expand Down
26 changes: 26 additions & 0 deletions website/docs/plugin/framework/resources/import.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,32 @@ func (r *ThingResource) ImportState(ctx context.Context, req resource.ImportStat
}
```

### Resource Deferral
-> 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.

The `ImportState` method can indicate a deferral which will defer the resource and all of its dependencies.

To indicate a deferral,
first check the [`resource.ImportStateRequest.ClientCapabilities.DeferralAllowed` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ImportStateClientCapabilities.DeferralAllowed)
to verify that the calling Terraform client supports deferred action functionality.
Then indicate a resource deferral
by setting the [`resource.ImportStateResponse.Deferral` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ImportStateResponse.Deferred):

```go
func (r *ThingResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
// Check that the Terraform client supports deferred actions
if req.ClientCapabilities.DeferralAllowed {
resp.Deferred = &resource.Deferred{
Reason: resource.DeferredReasonResourceConfigUnknown,
}
// Return early for deferral
return
}

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

## Not Implemented

If the resource does not support `terraform import`, skip the `ImportState` method implementation.
Expand Down
38 changes: 38 additions & 0 deletions website/docs/plugin/framework/resources/plan-modification.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,41 @@ func (r ThingResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRe
```

Ensure the response plan remains entirely `null` when the request plan is entirely `null`.

### Resource Deferral
-> 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.

The `ModifyPlan` method can indicate a deferral which will defer the resource and all of its dependencies.

To indicate a deferral,
first check the [`resource.ModifyPlanRequest.ClientCapabilities.DeferralAllowed` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ModifyPlanClientCapabilities.DeferralAllowed)
to verify that the calling Terraform client supports deferred action functionality.
Then indicate a resource deferral
by setting the [`resource.ModifyPlanResponse.Deferral` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ModifyPlanResponse.Deferred).

In the following example, the resource will defer if `example_attribute` value is unknown:

```go
func (r ThingResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) {
var data ThingResourceModel

// Read Terraform plan into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)

// Check that the Terraform client supports deferred actions and if so,
// defer the resource if example_attribute is unknown
if req.ClientCapabilities.DeferralAllowed && data.ExampleAttribute.IsUnknown() {
resp.Deferred = &resource.Deferred{
Reason: resource.DeferredReasonResourceConfigUnknown,
}
// Return early for deferral
return
}

// Modify plan as appropriate
}
```

If a [provider deferral](/terraform/plugin/framework/providers/index#provider-deferral) is set in the provider's `Configure` method with plan modification behavior enabled
and a resource indicates a deferral in the `ModifyPlan` method,
then the `Deferred.Reason` set in the resource's `ModifyPlan` will take precedence over the provider's `Deferred.Reason` in the response to the Terraform client.
75 changes: 75 additions & 0 deletions website/docs/plugin/framework/resources/read.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,78 @@ Note these recommendations when implementing the `Read` method:
* Ignore returning errors that signify the resource is no longer existent, call the response state `RemoveResource()` method, and return early. The next Terraform plan will recreate the resource.
* Refresh all possible values. This will ensure Terraform shows configuration drift and reduces import logic.
* Preserve the prior state value if the updated value is semantically equal. For example, JSON strings that have inconsequential object property reordering or whitespace differences. This prevents Terraform from showing extraneous drift in plans.

## Resource Deferral
-> 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.

The `Read` method can indicate a deferral which will defer the resource and all of its dependencies.

To indicate a deferral,
first check the [`resource.ReadRequest.ClientCapabilities.DeferralAllowed` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ReadClientCapabilities.DeferralAllowed)
to verify that the calling Terraform client supports deferred action functionality.
Then indicate a resource deferral
by setting the [`resource.ReadResponse.Deferral` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ReadResponse.Deferred).

In the following example, the resource will defer if the HTTP call returns a 404 Not Found status:

```go
func (r *ThingResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data ThingResourceModel

// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)

// Convert from Terraform data model into API data model
readReq := ThingResourceAPIModel{
Id: data.Id.ValueString(),
Name: data.Name.ValueString(),
}

httpReqBody, err := json.Marshal(readReq)

if err != nil {
resp.Diagnostics.AddError(
"Unable to Refresh Resource",
"An unexpected error occurred while creating the resource read request. "+
"Please report this issue to the provider developers.\n\n"+
"JSON Error: "+err.Error(),
)

return
}

// Create HTTP request
httpReq := http.NewRequestWithContext(
ctx,
http.MethodPut,
"http://example.com/things",
bytes.NewBuffer(httpReqBody),
)

httpResp, err := d.client.Do(httpReq)
defer httpResp.Body.Close()

if err != nil {
resp.Diagnostics.AddError(
"Unable to Refresh Resource",
"An unexpected error occurred while attempting to refresh resource state. "+
"Please retry the operation or report this issue to the provider developers.\n\n"+
"HTTP Error: "+err.Error(),
)

return
}

// Check that the Terraform client supports deferred actions and if so,
// defer resource on a 404 response
if req.ClientCapabilities.DeferralAllowed && httpResp.StatusCode == http.StatusNotFound) {
resp.Deferred = &resource.Deferred{
Reason: resource.DeferredReasonAbsentPrereq,
}
// Return early for deferral
return
}

// Refresh state as appropriate
}
```