Skip to content

stacks: move the RPCAPI and deferred actions out of experimental #37067

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

Merged
merged 2 commits into from
May 16, 2025
Merged
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
6 changes: 0 additions & 6 deletions .changes/footer-with-experiments.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
EXPERIMENTS:

Experiments are only enabled in alpha releases of Terraform CLI. The following features are not yet available in stable releases.

- The new command `terraform rpcapi` exposes some Terraform Core functionality through an RPC interface compatible with [`go-plugin`](https://github.com/hashicorp/go-plugin). The exact RPC API exposed here is currently subject to change at any time, because it's here primarily as a vehicle to support the [Terraform Stacks](https://www.hashicorp.com/blog/terraform-stacks-explained) private preview and so will be broken if necessary to respond to feedback from private preview participants, or possibly for other reasons. Do not use this mechanism yet outside of Terraform Stacks private preview.
- The experimental "deferred actions" feature, enabled by passing the `-allow-deferral` option to `terraform plan`, permits `count` and `for_each` arguments in `module`, `resource`, and `data` blocks to have unknown values and allows providers to react more flexibly to unknown values. This experiment is under active development, and so it's not yet useful to participate in this experiment

## Previous Releases

Expand Down
5 changes: 5 additions & 0 deletions .changes/v1.13/NEW FEATURES-20250515-151904.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: NEW FEATURES
body: 'Deferred actions: The `plan`, `apply`, and `refresh` commands now support the `-allow-deferral` flag. The flag enables Terraform and Terraform Providers to defer changes with unresolvable unknown values to future plans instead of failing the entire plan.'
time: 2025-05-15T15:19:04.603403+02:00
custom:
Issue: "37067"
5 changes: 5 additions & 0 deletions .changes/v1.13/NOTES-20250515-151922.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: NOTES
body: The command `terraform rpcapi` is now generally available. It is not intended for public consumption, but exposes certain Terraform operations through an RPC interface compatible with [go-plugin](https://github.com/hashicorp/go-plugin).
time: 2025-05-15T15:19:22.219798+02:00
custom:
Issue: "37067"
22 changes: 11 additions & 11 deletions commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,17 @@ func initCommands(
}, nil
},

// "rpcapi" is handled a bit differently because the whole point of
// this interface is to bypass the CLI layer so wrapping automation can
// get as-direct-as-possible access to Terraform Core functionality,
// without interference from behaviors that are intended for CLI
// end-user convenience. We bypass the "command" package entirely
// for this command in particular.
"rpcapi": rpcapi.CLICommandFactory(rpcapi.CommandFactoryOpts{
ExperimentsAllowed: meta.AllowExperimentalFeatures,
ShutdownCh: meta.ShutdownCh,
}),

"show": func() (cli.Command, error) {
return &command.ShowCommand{
Meta: meta,
Expand Down Expand Up @@ -439,17 +450,6 @@ func initCommands(
Meta: meta,
}, nil
}

// "rpcapi" is handled a bit differently because the whole point of
// this interface is to bypass the CLI layer so wrapping automation can
// get as-direct-as-possible access to Terraform Core functionality,
// without interference from behaviors that are intended for CLI
// end-user convenience. We bypass the "command" package entirely
// for this command in particular.
Commands["rpcapi"] = rpcapi.CLICommandFactory(rpcapi.CommandFactoryOpts{
ExperimentsAllowed: meta.AllowExperimentalFeatures,
ShutdownCh: meta.ShutdownCh,
})
}

PrimaryCommands = []string{
Expand Down
15 changes: 1 addition & 14 deletions internal/command/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,20 +274,7 @@ func (c *ApplyCommand) OperationRequest(
opReq.Type = backendrun.OperationTypeApply
opReq.View = view.Operation()
opReq.StatePersistInterval = c.Meta.StatePersistInterval()

// EXPERIMENTAL: maybe enable deferred actions
if c.AllowExperimentalFeatures {
opReq.DeferralAllowed = args.DeferralAllowed
} else if args.DeferralAllowed {
// Belated flag parse error, since we don't know about experiments
// support at actual parse time.
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"The -allow-deferral flag is only valid in experimental builds of Terraform.",
))
return nil, diags
}
opReq.DeferralAllowed = args.DeferralAllowed

var err error
opReq.ConfigLoader, err = c.initConfigLoader()
Expand Down
15 changes: 1 addition & 14 deletions internal/command/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,20 +164,7 @@ func (c *PlanCommand) OperationRequest(
opReq.ForceReplace = args.ForceReplace
opReq.Type = backendrun.OperationTypePlan
opReq.View = view.Operation()

// EXPERIMENTAL: maybe enable deferred actions
if c.AllowExperimentalFeatures {
opReq.DeferralAllowed = args.DeferralAllowed
} else if args.DeferralAllowed {
// Belated flag parse error, since we don't know about experiments
// support at actual parse time.
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"The -allow-deferral flag is only valid in experimental builds of Terraform.",
))
return nil, diags
}
opReq.DeferralAllowed = args.DeferralAllowed

var err error
opReq.ConfigLoader, err = c.initConfigLoader()
Expand Down
15 changes: 1 addition & 14 deletions internal/command/refresh.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,20 +146,7 @@ func (c *RefreshCommand) OperationRequest(be backendrun.OperationsBackend, view
opReq.Targets = args.Targets
opReq.Type = backendrun.OperationTypeRefresh
opReq.View = view.Operation()

// EXPERIMENTAL: maybe enable deferred actions
if c.AllowExperimentalFeatures {
opReq.DeferralAllowed = args.DeferralAllowed
} else if args.DeferralAllowed {
// Belated flag parse error, since we don't know about experiments
// support at actual parse time.
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"The -allow-deferral flag is only valid in experimental builds of Terraform.",
))
return nil, diags
}
opReq.DeferralAllowed = args.DeferralAllowed

var err error
opReq.ConfigLoader, err = c.initConfigLoader()
Expand Down
4 changes: 2 additions & 2 deletions internal/terraform/eval_for_each.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,8 @@ func (ev *forEachEvaluator) ensureKnownForImport(forEachVal cty.Value) tfdiags.D
func (ev *forEachEvaluator) ensureKnownForResource(forEachVal cty.Value) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
ty := forEachVal.Type()
const errInvalidUnknownDetailMap = "The \"for_each\" map includes keys derived from resource attributes that cannot be determined until apply, and so Terraform cannot determine the full set of keys that will identify the instances of this resource.\n\nWhen working with unknown values in for_each, it's better to define the map keys statically in your configuration and place apply-time results only in the map values.\n\nAlternatively, you could use the -target planning option to first apply only the resources that the for_each value depends on, and then apply a second time to fully converge."
const errInvalidUnknownDetailSet = "The \"for_each\" set includes values derived from resource attributes that cannot be determined until apply, and so Terraform cannot determine the full set of keys that will identify the instances of this resource.\n\nWhen working with unknown values in for_each, it's better to use a map value where the keys are defined statically in your configuration and where only the values contain apply-time results.\n\nAlternatively, you could use the -target planning option to first apply only the resources that the for_each value depends on, and then apply a second time to fully converge."
const errInvalidUnknownDetailMap = "The \"for_each\" map includes keys derived from resource attributes that cannot be determined until apply, and so Terraform cannot determine the full set of keys that will identify the instances of this resource.\n\nWhen working with unknown values in for_each, it's better to define the map keys statically in your configuration and place apply-time results only in the map values.\n\nAlternatively, you could either use the -target planning option or the -allow-deferrals planning option to first apply only the resources that the for_each value depends on, and then apply a second time to fully converge."
const errInvalidUnknownDetailSet = "The \"for_each\" set includes values derived from resource attributes that cannot be determined until apply, and so Terraform cannot determine the full set of keys that will identify the instances of this resource.\n\nWhen working with unknown values in for_each, it's better to use a map value where the keys are defined statically in your configuration and where only the values contain apply-time results.\n\nAlternatively, you could either use the -target planning option or the -allow-deferrals planning option to first apply only the resources that the for_each value depends on, and then apply a second time to fully converge."

if !forEachVal.IsKnown() {
var detailMsg string
Expand Down