Skip to content

Actions enhancements rollup#38668

Draft
jbardin wants to merge 35 commits into
mainfrom
jbardin/reaction
Draft

Actions enhancements rollup#38668
jbardin wants to merge 35 commits into
mainfrom
jbardin/reaction

Conversation

@jbardin
Copy link
Copy Markdown
Member

@jbardin jbardin commented Jun 2, 2026

This merges the actions work feature branch, which consists of the previous PRs:
#38547
#38572
#38618
#38644
#38654
#38657
#38658
#38664

Most of this was internal refactoring, but there are a few notable external changes:

  • before_ actions can now be used
  • Actions can access a new caller symbol when called from a resource action_trigger. This new symbol contains the object value from the calling resource to avoid dependency cycle problems inherent with indirect references.
  • Action can handle deferrals, so they can be used safely in stacks. if an action is deferred, resources requiring that action will also be deferred.
  • More accurate validation and diagnostics

Fixes: #37975
Fixes: #37927

jbardin added 27 commits June 2, 2026 08:31
Actions need to execute from the resource instance. To represent that
more directly, we're embedding the triggers in the resource, and then
referencing the action nodes from those triggers.

Actions nodes are only left in the graph as a representation of the
configuration. This allows for those nodes to be used to resolve their
own providers using the same methods as other nodes, while then be used
from within the resource later.

squashed merge of #38547
Because actions are written outside of resource, but are effectively
evaluated like providers, we create a "caller" object which is a
pseudo-alias for "self". The word "self" would be confusing since it
typically refers to the surrounding object, but there is no surrounding
object in this context. The term "caller" makes it more clear that there
is a special object coming from outside of the context of the action
config block.
The "self" and "caller" object are evaluated the same internally, but we
need different diagnostic text for each.
We need to thread "caller" through all action evaluation. We do this by
using the same mechanism as "self", since this is effectively just "self
at a distance". That means we can leverage all the same mechanisms
built around "self" and only need to change some diagnostic output to
match the context.

In order to validate action blocks however, it now means there needs to
be a `caller` supplied, so we can't validate them before we get to the
triggering resource. In order to do that we manually validate actions
from the triggering node, and only insert a `NodeValidateAction` for
standalone actions that are not referenced otherwise.
Check that caller is valid for trigger resource
Check that caller is invalid for standalone action
Rename which was incorrectly named, and use to to verify that old direct
references still work as expected.
Yes it is true that the order here would matter if the resource is
otherwise being evaluated, but that happens in many cases throughout the
codebase, and we already rely on having a correct graph to ensure correct
ordering of operations. Calling this out here with a FIXME is needlessly
specific when it's actually a question about the overall architecture,
and likely to produce a change which doesn't have any better global
guarantees.
Remove unnecessary err checks.
Check yield returns to break from loop.
While action nodes are not being evaluated directly during the graph
walk, it's useful to record their expansion information ahead of time.
This will also allow resource instances to know if they are being
deferred by an unknown action expansion before the resource is planned.
We still may not make use of all the deferral code around actions, but
at least make it work with the existing implementation.
This copies the same logic as the partially expanded resource
ShouldDeferActionInvocation didn't check if the action expansion itself
was previously deferred.
The logic checking for action deferral was using the check itself as a
way to detect if an action was being invoked, and considered it an error
if the action config was already deferred. This was opposite the other
methods, which check for more general deferrals that would
defer the instance.

Allow action expansion to be deferred. This is unavoidable when
deferrals are globally allowed, so we must deal with it.

A previously deferred action config, or an action which reports that its
provider was deferred must also defer any calling resource.
These are valid values, and excluding them is only a superficial attempt
at preventing unknown values.
Before actions we not evaluating the caller correctly, and would end up
with the prior state rather than the current planned state in the
evaluation context. After the final plan is created, we need to update
the stored change so that all known values are present. We also need to
update the working state to indicate there is a change. This was never
needed before, because there was never a situation where an object was
evaluated during apply, but before that object was itself applied.
While this is also technically possible to use correctly, more often
than not it's an error, so prevent it for now.
This should ever have been valid
Refactor some of the action ref parsing, and remove redundant code.

Make sure we can validate when action indexes are incorrect or missing.
The action response diagnostics were being munged into simple hcl
diagnostics instead of using the usual attribution mechanisms. The fact
that actions have an optional config block means we have to doublecheck
that every time, since we don't want to apply the diagnostic to the
wrong block in case the provider returns any attribute paths with the
diagnostic.
Actions were initially allowed to refer back to the trigger resource via
their full address. In order to do that terraform needs to specifically
catch the reference and remove the cycle it causes. If this reference is
indirected in any way, we lose that ability and the apply will fail with
a graph cycle.

We've since introduced the "caller" object, which is a alias for "self"
from the triggering resource. Detect when the user has an action config
which should be using "caller" instead of a resource address, and
present a warning about the old syntax.
Fill in some missing targeting logic for actions and ConfigResource.

Find and record callers for invoke nodes, so `caller` can be evaluated
without having to process the entire calling resource.

Add the caller address to the invoke structures, and store it in the
planfile. This lets us get our caller reference automatically during
apply, without having to search through all nodes for resource action
triggers again.
@jbardin jbardin marked this pull request as ready for review June 2, 2026 13:05
@jbardin jbardin requested review from a team as code owners June 2, 2026 13:05
@jbardin jbardin requested a review from RonRicardo June 2, 2026 13:05
@jbardin jbardin marked this pull request as draft June 2, 2026 15:29
@jbardin
Copy link
Copy Markdown
Member Author

jbardin commented Jun 2, 2026

Turns out the temporary handling of evaluation in before_ events and caller isn't going to be sufficient to keep the main branch working. Holding off on the big merge temporarily to see what the scope of next steps are going to be.

jbardin added 8 commits June 3, 2026 15:08
Destroy actions can now be used in resources, with some constraints in
place. To avoid dependency cycles we must fully plan the action. This
means that the action config and the triggering condition are completely
known at plan time, so that the stored values can be used during apply.

This also means that ephemeral values cannot be used in destroy action
configuration, because they are not guaranteed to be the same during
apply.
The nested maps for requested providers weren't required, and the recent
refactoring made it more apparent that the nested would have been
overwritten even if multiple providers were allowed.

Action providers were not being connected, and because we need to avoid
using them as resolved providers, we need to connect those in a separate
loop. For now we can accomplish that by capturing the existing loop as a
closure, and running in twice for each map.
Pass in a correctly-typed caller value for static validation.
Action config block evaluation from within destroy context
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Actions not respecting resource dependencies actions: improve error message when action needs to be referenced by instance

1 participant