Skip to content

Commit 7721cd5

Browse files
refactor: make action diff transformer only responsible for attaching action invocations
1 parent 327a992 commit 7721cd5

9 files changed

+225
-83
lines changed

internal/plans/changes_src.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,8 @@ func (acs *ActionInvocationInstanceSrc) Less(other *ActionInvocationInstanceSrc)
617617
return acs.ActionTrigger.Less(other.ActionTrigger)
618618
}
619619

620+
// TODO: Do we still need this?
621+
620622
// FilterLaterActionInvocations returns the list of action invocations that
621623
// should be triggered after this one. This function assumes the supplied list
622624
// of action invocations has already been filtered to invocations against the

internal/terraform/graph_builder_apply.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,29 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
152152
Config: b.Config,
153153
},
154154

155+
&ActionTriggerConfigTransformer{
156+
Config: b.Config,
157+
Operation: b.Operation,
158+
ActionTargets: b.ActionTargets,
159+
160+
ConcreteActionTriggerNodeFunc: func(node *nodeAbstractActionTriggerExpand, timing RelativeActionTiming) dag.Vertex {
161+
return &nodeActionTriggerApplyExpand{
162+
nodeAbstractActionTriggerExpand: node,
163+
164+
relativeTiming: timing,
165+
}
166+
},
167+
// we want before_* actions to run before and after_* actions to run after the resource
168+
CreateNodesAsAfter: false,
169+
},
170+
171+
&ActionInvokeApplyTransformer{
172+
Config: b.Config,
173+
Operation: b.Operation,
174+
ActionTargets: b.ActionTargets,
175+
Changes: b.Changes,
176+
},
177+
155178
&ActionDiffTransformer{
156179
Changes: b.Changes,
157180
Config: b.Config,

internal/terraform/graph_builder_plan.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,14 +171,17 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
171171
ActionTargets: b.ActionTargets,
172172
queryPlanMode: b.queryPlan,
173173

174-
ConcreteActionTriggerNodeFunc: func(node *nodeAbstractActionTriggerExpand) dag.Vertex {
174+
ConcreteActionTriggerNodeFunc: func(node *nodeAbstractActionTriggerExpand, _ RelativeActionTiming) dag.Vertex {
175175
return &nodeActionTriggerPlanExpand{
176176
nodeAbstractActionTriggerExpand: node,
177177
}
178178
},
179+
180+
// We plan all actions after the resource is handled
181+
CreateNodesAsAfter: true,
179182
},
180183

181-
&ActionInvokeTransformer{
184+
&ActionInvokePlanTransformer{
182185
Config: b.Config,
183186
Operation: b.Operation,
184187
ActionTargets: b.ActionTargets,

internal/terraform/node_action_trigger_abstract.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,16 @@ import (
1414
"github.com/hashicorp/terraform/internal/lang/langrefs"
1515
)
1616

17+
type RelativeActionTiming = string
18+
19+
const (
20+
RelativeActionTimingBefore = "before"
21+
RelativeActionTimingAfter = "after"
22+
)
23+
1724
// ConcreteActionTriggerNodeFunc is a callback type used to convert an
1825
// abstract action trigger to a concrete one of some type.
19-
type ConcreteActionTriggerNodeFunc func(*nodeAbstractActionTriggerExpand) dag.Vertex
26+
type ConcreteActionTriggerNodeFunc func(*nodeAbstractActionTriggerExpand, RelativeActionTiming) dag.Vertex
2027

2128
type nodeAbstractActionTriggerExpand struct {
2229
Addr addrs.ConfigAction
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
package terraform
5+
6+
import (
7+
"fmt"
8+
9+
"github.com/hashicorp/terraform/internal/addrs"
10+
"github.com/hashicorp/terraform/internal/dag"
11+
"github.com/hashicorp/terraform/internal/lang/langrefs"
12+
"github.com/hashicorp/terraform/internal/plans"
13+
"github.com/hashicorp/terraform/internal/tfdiags"
14+
)
15+
16+
type nodeActionTriggerApplyExpand struct {
17+
*nodeAbstractActionTriggerExpand
18+
19+
actionInvocationInstances []*plans.ActionInvocationInstanceSrc
20+
relativeTiming RelativeActionTiming
21+
}
22+
23+
var (
24+
_ GraphNodeDynamicExpandable = (*nodeActionTriggerApplyExpand)(nil)
25+
_ GraphNodeReferencer = (*nodeActionTriggerApplyExpand)(nil)
26+
)
27+
28+
func (n *nodeActionTriggerApplyExpand) Name() string {
29+
return fmt.Sprintf("%s (apply)", n.nodeAbstractActionTriggerExpand.Name())
30+
}
31+
32+
func (n *nodeActionTriggerApplyExpand) DynamicExpand(ctx EvalContext) (*Graph, tfdiags.Diagnostics) {
33+
var g Graph
34+
var diags tfdiags.Diagnostics
35+
36+
if n.lifecycleActionTrigger == nil {
37+
panic("Only actions triggered by plan and apply are supported")
38+
}
39+
40+
invocationMap := map[*plans.ActionInvocationInstanceSrc]*nodeActionTriggerApplyInstance{}
41+
// We already planned the action invocations, so we can just add them to the graph
42+
for _, ai := range n.actionInvocationInstances {
43+
node := &nodeActionTriggerApplyInstance{
44+
ActionInvocation: ai,
45+
resolvedProvider: n.resolvedProvider,
46+
ActionTriggerRange: n.lifecycleActionTrigger.invokingSubject.Ptr(),
47+
ConditionExpr: n.lifecycleActionTrigger.conditionExpr,
48+
}
49+
g.Add(node)
50+
invocationMap[ai] = node
51+
}
52+
53+
for _, ai := range n.actionInvocationInstances {
54+
node := invocationMap[ai]
55+
others := ai.FilterLaterActionInvocations(n.actionInvocationInstances)
56+
for _, other := range others {
57+
otherNode := invocationMap[other]
58+
g.Connect(dag.BasicEdge(otherNode, node))
59+
}
60+
}
61+
62+
addRootNodeToGraph(&g)
63+
return &g, diags
64+
}
65+
66+
func (n *nodeActionTriggerApplyExpand) SetActionInvocationInstances(instances []*plans.ActionInvocationInstanceSrc) {
67+
n.actionInvocationInstances = instances
68+
}
69+
70+
func (n *nodeActionTriggerApplyExpand) References() []*addrs.Reference {
71+
var refs []*addrs.Reference
72+
refs = append(refs, &addrs.Reference{
73+
Subject: n.Addr.Action,
74+
})
75+
76+
if n.lifecycleActionTrigger != nil {
77+
conditionRefs, _ := langrefs.ReferencesInExpr(addrs.ParseRef, n.lifecycleActionTrigger.conditionExpr)
78+
refs = append(refs, conditionRefs...)
79+
}
80+
81+
return refs
82+
}

internal/terraform/transform_action_diff.go

Lines changed: 22 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88

99
"github.com/hashicorp/terraform/internal/addrs"
1010
"github.com/hashicorp/terraform/internal/configs"
11-
"github.com/hashicorp/terraform/internal/dag"
1211
"github.com/hashicorp/terraform/internal/plans"
1312
)
1413

@@ -21,85 +20,41 @@ type ActionDiffTransformer struct {
2120

2221
func (t *ActionDiffTransformer) Transform(g *Graph) error {
2322
applyNodes := addrs.MakeMap[addrs.AbsResourceInstance, *NodeApplyableResourceInstance]()
23+
actionTriggerNodes := addrs.MakeMap[addrs.ConfigResource, []*nodeActionTriggerApplyExpand]()
2424
for _, vs := range g.Vertices() {
25-
applyableResource, ok := vs.(*NodeApplyableResourceInstance)
26-
if !ok {
27-
continue
25+
if applyableResource, ok := vs.(*NodeApplyableResourceInstance); ok {
26+
applyNodes.Put(applyableResource.Addr, applyableResource)
2827
}
2928

30-
applyNodes.Put(applyableResource.Addr, applyableResource)
29+
if atn, ok := vs.(*nodeActionTriggerApplyExpand); ok {
30+
configResource := actionTriggerNodes.Get(atn.lifecycleActionTrigger.resourceAddress)
31+
actionTriggerNodes.Put(atn.lifecycleActionTrigger.resourceAddress, append(configResource, atn))
32+
}
3133
}
3234

33-
invocationMap := map[*plans.ActionInvocationInstanceSrc]*nodeActionTriggerApplyInstance{}
34-
triggerMap := addrs.MakeMap[addrs.AbsResourceInstance, []*plans.ActionInvocationInstanceSrc]()
35-
for _, action := range t.Changes.ActionInvocations {
36-
// Add nodes for each action invocation
37-
node := &nodeActionTriggerApplyInstance{
38-
ActionInvocation: action,
35+
for _, ai := range t.Changes.ActionInvocations {
36+
lat, ok := ai.ActionTrigger.(*plans.LifecycleActionTrigger)
37+
if !ok {
38+
continue
3939
}
40+
isBefore := lat.ActionTriggerEvent == configs.BeforeCreate || lat.ActionTriggerEvent == configs.BeforeUpdate
41+
isAfter := lat.ActionTriggerEvent == configs.AfterCreate || lat.ActionTriggerEvent == configs.AfterUpdate
4042

41-
// If the action invocations is triggered within the lifecycle of a resource
42-
// we want to add information about the source location to the apply node
43-
if at, ok := action.ActionTrigger.(*plans.LifecycleActionTrigger); ok {
44-
moduleInstance := t.Config.DescendantForInstance(at.TriggeringResourceAddr.Module)
45-
if moduleInstance == nil {
46-
panic(fmt.Sprintf("Could not find module instance for resource %s in config", at.TriggeringResourceAddr.String()))
47-
}
48-
49-
resourceInstance := moduleInstance.Module.ResourceByAddr(at.TriggeringResourceAddr.Resource.Resource)
50-
if resourceInstance == nil {
51-
panic(fmt.Sprintf("Could not find resource instance for resource %s in config", at.TriggeringResourceAddr.String()))
52-
}
53-
54-
triggerBlock := resourceInstance.Managed.ActionTriggers[at.ActionTriggerBlockIndex]
55-
if triggerBlock == nil {
56-
panic(fmt.Sprintf("Could not find action trigger block %d for resource %s in config", at.ActionTriggerBlockIndex, at.TriggeringResourceAddr.String()))
57-
}
58-
59-
triggerMap.Put(at.TriggeringResourceAddr, append(triggerMap.Get(at.TriggeringResourceAddr), action))
60-
61-
act := triggerBlock.Actions[at.ActionsListIndex]
62-
node.ActionTriggerRange = &act.Range
63-
node.ConditionExpr = triggerBlock.Condition
43+
atns, ok := actionTriggerNodes.GetOk(lat.TriggeringResourceAddr.ConfigResource())
44+
if !ok {
45+
return fmt.Errorf("no action trigger nodes found for resource %s", lat.TriggeringResourceAddr)
6446
}
47+
// We add the action invocations one by one
48+
for _, atn := range atns {
49+
beforeMatches := atn.relativeTiming == RelativeActionTimingBefore && isBefore
50+
afterMatches := atn.relativeTiming == RelativeActionTimingAfter && isAfter
6551

66-
g.Add(node)
67-
invocationMap[action] = node
68-
69-
// Add edge to triggering resource
70-
if lat, ok := action.ActionTrigger.(*plans.LifecycleActionTrigger); ok {
71-
// Add edges for lifecycle action triggers
72-
resourceNode, ok := applyNodes.GetOk(lat.TriggeringResourceAddr)
73-
if !ok {
74-
panic("Could not find resource node for lifecycle action trigger")
52+
if (beforeMatches || afterMatches) && atn.lifecycleActionTrigger.actionTriggerBlockIndex == lat.ActionTriggerBlockIndex && atn.lifecycleActionTrigger.actionListIndex == lat.ActionsListIndex {
53+
atn.actionInvocationInstances = append(atn.actionInvocationInstances, ai)
7554
}
7655

77-
switch lat.ActionTriggerEvent {
78-
case configs.BeforeCreate, configs.BeforeUpdate, configs.BeforeDestroy:
79-
g.Connect(dag.BasicEdge(resourceNode, node))
80-
case configs.AfterCreate, configs.AfterUpdate, configs.AfterDestroy:
81-
g.Connect(dag.BasicEdge(node, resourceNode))
82-
default:
83-
panic("Unknown event")
84-
}
8556
}
8657
}
8758

88-
// Find all dependencies between action invocations
89-
for _, action := range t.Changes.ActionInvocations {
90-
at, ok := action.ActionTrigger.(*plans.LifecycleActionTrigger)
91-
if !ok {
92-
// only add dependencies between lifecycle actions. invoke actions
93-
// all act independently.
94-
continue
95-
}
96-
97-
node := invocationMap[action]
98-
others := action.FilterLaterActionInvocations(triggerMap.Get(at.TriggeringResourceAddr))
99-
for _, other := range others {
100-
otherNode := invocationMap[other]
101-
g.Connect(dag.BasicEdge(otherNode, node))
102-
}
103-
}
10459
return nil
10560
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
package terraform
5+
6+
import (
7+
"github.com/hashicorp/terraform/internal/addrs"
8+
"github.com/hashicorp/terraform/internal/configs"
9+
"github.com/hashicorp/terraform/internal/plans"
10+
)
11+
12+
type ActionInvokeApplyTransformer struct {
13+
Config *configs.Config
14+
ActionTargets []addrs.Targetable
15+
Operation walkOperation
16+
Changes *plans.ChangesSrc
17+
18+
queryPlanMode bool
19+
}
20+
21+
func (t *ActionInvokeApplyTransformer) Transform(g *Graph) error {
22+
if t.Operation != walkApply || t.queryPlanMode || len(t.ActionTargets) == 0 {
23+
return nil
24+
}
25+
26+
// We just want to add all invoke triggered action invocations
27+
for _, action := range t.Changes.ActionInvocations {
28+
// Add nodes for each action invocation
29+
node := &nodeActionTriggerApplyInstance{
30+
ActionInvocation: action,
31+
}
32+
g.Add(node)
33+
}
34+
35+
return nil
36+
}

internal/terraform/transform_action_invoke.go renamed to internal/terraform/transform_action_invoke_plan.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ import (
1010
"github.com/hashicorp/terraform/internal/configs"
1111
)
1212

13-
type ActionInvokeTransformer struct {
13+
type ActionInvokePlanTransformer struct {
1414
Config *configs.Config
1515
ActionTargets []addrs.Targetable
1616
Operation walkOperation
1717

1818
queryPlanMode bool
1919
}
2020

21-
func (t *ActionInvokeTransformer) Transform(g *Graph) error {
21+
func (t *ActionInvokePlanTransformer) Transform(g *Graph) error {
2222
if t.Operation != walkPlan || t.queryPlanMode || len(t.ActionTargets) == 0 {
2323
return nil
2424
}
@@ -38,6 +38,8 @@ func (t *ActionInvokeTransformer) Transform(g *Graph) error {
3838
if module != nil {
3939
config = module.Module.Actions[target.Action.Action.String()]
4040
}
41+
default:
42+
return fmt.Errorf("Targeted unknown action type %T", target)
4143
}
4244

4345
if config == nil {

0 commit comments

Comments
 (0)