From 1f0dd6b80abc76229e8bb235fa976722b94729c7 Mon Sep 17 00:00:00 2001 From: Daniel Schmidt Date: Mon, 25 Aug 2025 17:12:18 +0200 Subject: [PATCH] actions: support conditions in trigger blocks --- .../terraform/context_apply_action_test.go | 141 ++++++ .../terraform/context_plan_actions_test.go | 454 ++++++++++++++++++ .../terraform/node_action_trigger_apply.go | 36 +- .../node_action_trigger_instance_plan.go | 42 ++ .../terraform/node_action_trigger_plan.go | 7 +- internal/terraform/transform_action_diff.go | 1 + internal/terraform/transform_action_plan.go | 1 + 7 files changed, 678 insertions(+), 4 deletions(-) diff --git a/internal/terraform/context_apply_action_test.go b/internal/terraform/context_apply_action_test.go index 0423689de0..ff808b70c5 100644 --- a/internal/terraform/context_apply_action_test.go +++ b/internal/terraform/context_apply_action_test.go @@ -1706,6 +1706,147 @@ resource "test_object" "a" { }, }, }, + "conditions": { + module: map[string]string{ + "main.tf": ` +action "act_unlinked" "hello" { +count = 3 +config { + attr = "value-${count.index}" +} +} +resource "test_object" "foo" { +name = "foo" +} +resource "test_object" "resource" { +name = "resource" +lifecycle { + action_trigger { + events = [before_create] + condition = test_object.foo.name == "bar" + actions = [action.act_unlinked.hello[0]] + } + + action_trigger { + events = [before_create] + condition = test_object.foo.name == "foo" + actions = [action.act_unlinked.hello[1], action.act_unlinked.hello[2]] + } + } +} +`, + }, + expectInvokeActionCalled: true, + expectInvokeActionCalls: []providers.InvokeActionRequest{{ + ActionType: "act_unlinked", + PlannedActionData: cty.ObjectVal(map[string]cty.Value{ + "attr": cty.StringVal("value-1"), + }), + }, { + ActionType: "act_unlinked", + PlannedActionData: cty.ObjectVal(map[string]cty.Value{ + "attr": cty.StringVal("value-2"), + }), + }}, + }, + + "simple condition evaluation - true": { + module: map[string]string{ + "main.tf": ` +action "act_unlinked" "hello" {} +resource "test_object" "a" { + name = "foo" + lifecycle { + action_trigger { + events = [after_create] + condition = "foo" == "foo" + actions = [action.act_unlinked.hello] + } + } +} +`, + }, + expectInvokeActionCalled: true, + }, + + "simple condition evaluation - false": { + module: map[string]string{ + "main.tf": ` +action "act_unlinked" "hello" {} +resource "test_object" "a" { + name = "foo" + lifecycle { + action_trigger { + events = [after_create] + condition = "foo" == "bar" + actions = [action.act_unlinked.hello] + } + } +} +`, + }, + expectInvokeActionCalled: false, + }, + + "using count.index in after_create condition": { + module: map[string]string{ + "main.tf": ` +action "act_unlinked" "hello" {} +resource "test_object" "a" { + count = 3 + name = "item-${count.index}" + lifecycle { + action_trigger { + events = [after_create] + condition = count.index == 1 + actions = [action.act_unlinked.hello] + } + } +} +`, + }, + expectInvokeActionCalled: true, + }, + + "using each.key in after_create condition": { + module: map[string]string{ + "main.tf": ` +action "act_unlinked" "hello" {} +resource "test_object" "a" { + for_each = toset(["foo", "bar"]) + name = each.key + lifecycle { + action_trigger { + events = [after_create] + condition = each.key == "foo" + actions = [action.act_unlinked.hello] + } + } +} +`, + }, + expectInvokeActionCalled: true, + }, + + "using each.value in after_create condition": { + module: map[string]string{ + "main.tf": ` +action "act_unlinked" "hello" {} +resource "test_object" "a" { + for_each = {"foo" = "value1", "bar" = "value2"} + name = each.value + lifecycle { + action_trigger { + events = [after_create] + condition = each.value == "value1" + actions = [action.act_unlinked.hello] + } + } +} +`, + }, + expectInvokeActionCalled: true, + }, } { t.Run(name, func(t *testing.T) { if tc.toBeImplemented { diff --git a/internal/terraform/context_plan_actions_test.go b/internal/terraform/context_plan_actions_test.go index a37b7e42f9..e59835a55a 100644 --- a/internal/terraform/context_plan_actions_test.go +++ b/internal/terraform/context_plan_actions_test.go @@ -2328,6 +2328,460 @@ resource "test_object" "a" { } }, }, + + "boolean condition": { + module: map[string]string{ + "main.tf": ` +action "test_unlinked" "hello" {} +action "test_unlinked" "world" {} +action "test_unlinked" "bye" {} +resource "test_object" "foo" { +name = "foo" +} +resource "test_object" "a" { +lifecycle { + action_trigger { + events = [before_create] + condition = test_object.foo.name == "foo" + actions = [action.test_unlinked.hello, action.test_unlinked.world] + } + action_trigger { + events = [after_create] + condition = test_object.foo.name == "bye" + actions = [action.test_unlinked.bye] + } +} +} +`, + }, + expectPlanActionCalled: true, + + assertPlan: func(t *testing.T, p *plans.Plan) { + if len(p.Changes.ActionInvocations) != 2 { + t.Fatalf("expected 2 actions in plan, got %d", len(p.Changes.ActionInvocations)) + } + + invokedActionAddrs := []string{} + for _, action := range p.Changes.ActionInvocations { + invokedActionAddrs = append(invokedActionAddrs, action.Addr.String()) + } + slices.Sort(invokedActionAddrs) + expectedActions := []string{ + "action.test_unlinked.hello", + "action.test_unlinked.world", + } + if !cmp.Equal(expectedActions, invokedActionAddrs) { + t.Fatalf("expected actions: %v, got %v", expectedActions, invokedActionAddrs) + } + }, + }, + + "unknown condition": { + module: map[string]string{ + "main.tf": ` +variable "cond" { + type = string +} +action "test_unlinked" "hello" {} +resource "test_object" "a" { + lifecycle { + action_trigger { + events = [before_create] + condition = var.cond == "foo" + actions = [action.test_unlinked.hello] + } + } +} +`, + }, + expectPlanActionCalled: false, + planOpts: &PlanOpts{ + Mode: plans.NormalMode, + SetVariables: InputValues{ + "cond": &InputValue{ + Value: cty.UnknownVal(cty.String), + SourceType: ValueFromCaller, + }, + }, + }, + expectPlanDiagnostics: func(m *configs.Config) tfdiags.Diagnostics { + return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Condition must be known", + Detail: "The condition expression resulted in an unknown value, but it must be a known boolean value.", + Subject: &hcl.Range{ + Filename: filepath.Join(m.Module.SourceDir, "main.tf"), + Start: hcl.Pos{Line: 10, Column: 19, Byte: 186}, + End: hcl.Pos{Line: 10, Column: 36, Byte: 203}, + }, + }) + }, + }, + + "non-boolean condition": { + module: map[string]string{ + "main.tf": ` +action "test_unlinked" "hello" {} +resource "test_object" "foo" { + name = "foo" +} +resource "test_object" "a" { + lifecycle { + action_trigger { + events = [before_create] + condition = test_object.foo.name + actions = [action.test_unlinked.hello] + } + } +} +`, + }, + expectPlanActionCalled: false, + + expectPlanDiagnostics: func(m *configs.Config) tfdiags.Diagnostics { + return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Incorrect value type", + Detail: "Invalid expression value: a bool is required.", + Subject: &hcl.Range{ + Filename: filepath.Join(m.Module.SourceDir, "main.tf"), + Start: hcl.Pos{Line: 10, Column: 19, Byte: 196}, + End: hcl.Pos{Line: 10, Column: 39, Byte: 216}, + }, + }) + }, + }, + + "using self in before_* condition": { + module: map[string]string{ + "main.tf": ` +action "test_unlinked" "hello" {} +action "test_unlinked" "world" {} +resource "test_object" "a" { + name = "foo" + lifecycle { + action_trigger { + events = [before_create] + condition = self.name == "foo" + actions = [action.test_unlinked.hello] + } + action_trigger { + events = [after_update] + condition = self.name == "bar" + actions = [action.test_unlinked.world] + } + } +} +`, + }, + expectPlanActionCalled: false, + + expectPlanDiagnostics: func(m *configs.Config) tfdiags.Diagnostics { + // We only expect one diagnostic, as the other condition is valid + return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Self reference not allowed", + Detail: `The condition expression cannot reference "self".`, + Subject: &hcl.Range{ + Filename: filepath.Join(m.Module.SourceDir, "main.tf"), + Start: hcl.Pos{Line: 11, Column: 19, Byte: 199}, + End: hcl.Pos{Line: 11, Column: 37, Byte: 217}, + }, + }) + }, + }, + + "using self in after_* condition": { + module: map[string]string{ + "main.tf": ` +action "test_unlinked" "hello" {} +action "test_unlinked" "world" {} +resource "test_object" "a" { + name = "foo" + lifecycle { + action_trigger { + events = [after_create] + condition = self.name == "foo" + actions = [action.test_unlinked.hello] + } + action_trigger { + events = [after_update] + condition = self.name == "bar" + actions = [action.test_unlinked.world] + } + } +} +`, + }, + expectPlanActionCalled: false, + + expectPlanDiagnostics: func(m *configs.Config) tfdiags.Diagnostics { + // We only expect one diagnostic, as the other condition is valid + return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Self reference not allowed", + Detail: `The condition expression cannot reference "self".`, + Subject: &hcl.Range{ + Filename: filepath.Join(m.Module.SourceDir, "main.tf"), + Start: hcl.Pos{Line: 11, Column: 19, Byte: 198}, + End: hcl.Pos{Line: 11, Column: 37, Byte: 216}, + }, + }) + }, + }, + + "using each in before_* condition": { + module: map[string]string{ + "main.tf": ` +action "test_unlinked" "hello" {} +action "test_unlinked" "world" {} +resource "test_object" "a" { + for_each = toset(["foo", "bar"]) + name = each.key + lifecycle { + action_trigger { + events = [before_create] + condition = each.key == "foo" + actions = [action.test_unlinked.hello] + } + } +} +`, + }, + expectPlanActionCalled: false, + + expectPlanDiagnostics: func(m *configs.Config) tfdiags.Diagnostics { + return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Each reference not allowed", + Detail: `The condition expression cannot reference "each" if the action is run before the resource is applied.`, + Subject: &hcl.Range{ + Filename: filepath.Join(m.Module.SourceDir, "main.tf"), + Start: hcl.Pos{Line: 12, Column: 19, Byte: 237}, + End: hcl.Pos{Line: 12, Column: 36, Byte: 254}, + }, + }).Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Each reference not allowed", + Detail: `The condition expression cannot reference "each" if the action is run before the resource is applied.`, + Subject: &hcl.Range{ + Filename: filepath.Join(m.Module.SourceDir, "main.tf"), + Start: hcl.Pos{Line: 12, Column: 19, Byte: 237}, + End: hcl.Pos{Line: 12, Column: 36, Byte: 254}, + }, + }) + }, + }, + + "using each in after_* condition": { + module: map[string]string{ + "main.tf": ` +action "test_unlinked" "hello" {} +action "test_unlinked" "world" {} +resource "test_object" "a" { + for_each = toset(["foo", "bar"]) + name = each.key + lifecycle { + action_trigger { + events = [after_create] + condition = each.key == "foo" + actions = [action.test_unlinked.hello] + } + action_trigger { + events = [after_update] + condition = each.key == "bar" + actions = [action.test_unlinked.world] + } + } +} +`, + }, + expectPlanActionCalled: true, + + assertPlan: func(t *testing.T, p *plans.Plan) { + if len(p.Changes.ActionInvocations) != 1 { + t.Errorf("Expected 1 action invocations, got %d", len(p.Changes.ActionInvocations)) + } + if p.Changes.ActionInvocations[0].Addr.String() != "action.test_unlinked.hello" { + t.Errorf("Expected action 'action.test_unlinked.hello', got %s", p.Changes.ActionInvocations[0].Addr.String()) + } + }, + }, + + "using count.index in before_* condition": { + module: map[string]string{ + "main.tf": ` +action "test_unlinked" "hello" {} +action "test_unlinked" "world" {} +resource "test_object" "a" { + count = 3 + name = "item-${count.index}" + lifecycle { + action_trigger { + events = [before_create] + condition = count.index == 1 + actions = [action.test_unlinked.hello] + } + action_trigger { + events = [before_update] + condition = count.index == 2 + actions = [action.test_unlinked.world] + } + } +} + `, + }, + expectPlanActionCalled: false, + + expectPlanDiagnostics: func(m *configs.Config) tfdiags.Diagnostics { + return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Count reference not allowed", + Detail: `The condition expression cannot reference "count" if the action is run before the resource is applied.`, + Subject: &hcl.Range{ + Filename: filepath.Join(m.Module.SourceDir, "main.tf"), + Start: hcl.Pos{Line: 11, Column: 19, Byte: 226}, + End: hcl.Pos{Line: 11, Column: 35, Byte: 242}, + }, + }).Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Count reference not allowed", + Detail: `The condition expression cannot reference "count" if the action is run before the resource is applied.`, + Subject: &hcl.Range{ + Filename: filepath.Join(m.Module.SourceDir, "main.tf"), + Start: hcl.Pos{Line: 11, Column: 19, Byte: 226}, + End: hcl.Pos{Line: 11, Column: 35, Byte: 242}, + }, + }).Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Count reference not allowed", + Detail: `The condition expression cannot reference "count" if the action is run before the resource is applied.`, + Subject: &hcl.Range{ + Filename: filepath.Join(m.Module.SourceDir, "main.tf"), + Start: hcl.Pos{Line: 11, Column: 19, Byte: 226}, + End: hcl.Pos{Line: 11, Column: 35, Byte: 242}, + }, + }) + }, + }, + + "using count.index in after_* condition": { + module: map[string]string{ + "main.tf": ` +action "test_unlinked" "hello" {} +action "test_unlinked" "world" {} +resource "test_object" "a" { + count = 3 + name = "item-${count.index}" + lifecycle { + action_trigger { + events = [after_create] + condition = count.index == 1 + actions = [action.test_unlinked.hello] + } + action_trigger { + events = [after_update] + condition = count.index == 2 + actions = [action.test_unlinked.world] + } + } +} + `, + }, + expectPlanActionCalled: true, + + assertPlan: func(t *testing.T, p *plans.Plan) { + if len(p.Changes.ActionInvocations) != 1 { + t.Errorf("Expected 1 action invocation, got %d", len(p.Changes.ActionInvocations)) + } + if p.Changes.ActionInvocations[0].Addr.String() != "action.test_unlinked.hello" { + t.Errorf("Expected action invocation %q, got %q", "action.test_unlinked.hello", p.Changes.ActionInvocations[0].Addr.String()) + } + }, + }, + + "using each.value in before_* condition": { + module: map[string]string{ + "main.tf": ` +action "test_unlinked" "hello" {} +action "test_unlinked" "world" {} +resource "test_object" "a" { + for_each = {"foo" = "value1", "bar" = "value2"} + name = each.value + lifecycle { + action_trigger { + events = [before_create] + condition = each.value == "value1" + actions = [action.test_unlinked.hello] + } + action_trigger { + events = [before_update] + condition = each.value == "value2" + actions = [action.test_unlinked.world] + } + } +} + `, + }, + expectPlanActionCalled: false, + + expectPlanDiagnostics: func(m *configs.Config) tfdiags.Diagnostics { + return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Each reference not allowed", + Detail: `The condition expression cannot reference "each" if the action is run before the resource is applied.`, + Subject: &hcl.Range{ + Filename: filepath.Join(m.Module.SourceDir, "main.tf"), + Start: hcl.Pos{Line: 11, Column: 19, Byte: 253}, + End: hcl.Pos{Line: 11, Column: 41, Byte: 275}, + }, + }).Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Each reference not allowed", + Detail: `The condition expression cannot reference "each" if the action is run before the resource is applied.`, + Subject: &hcl.Range{ + Filename: filepath.Join(m.Module.SourceDir, "main.tf"), + Start: hcl.Pos{Line: 11, Column: 19, Byte: 253}, + End: hcl.Pos{Line: 11, Column: 41, Byte: 275}, + }, + }) + }, + }, + + "using each.value in after_* condition": { + module: map[string]string{ + "main.tf": ` +action "test_unlinked" "hello" {} +action "test_unlinked" "world" {} +resource "test_object" "a" { + for_each = {"foo" = "value1", "bar" = "value2"} + name = each.value + lifecycle { + action_trigger { + events = [after_create] + condition = each.value == "value1" + actions = [action.test_unlinked.hello] + } + action_trigger { + events = [after_update] + condition = each.value == "value2" + actions = [action.test_unlinked.world] + } + } +} + `, + }, + expectPlanActionCalled: true, + + assertPlan: func(t *testing.T, p *plans.Plan) { + if len(p.Changes.ActionInvocations) != 1 { + t.Errorf("Expected 1 action invocations, got %d", len(p.Changes.ActionInvocations)) + } + if p.Changes.ActionInvocations[0].Addr.String() != "action.test_unlinked.hello" { + t.Errorf("Expected action 'action.test_unlinked.hello', got %s", p.Changes.ActionInvocations[0].Addr.String()) + } + }, + }, } { t.Run(name, func(t *testing.T) { if tc.toBeImplemented { diff --git a/internal/terraform/node_action_trigger_apply.go b/internal/terraform/node_action_trigger_apply.go index 31b2ac54f3..9eab1ba990 100644 --- a/internal/terraform/node_action_trigger_apply.go +++ b/internal/terraform/node_action_trigger_apply.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/lang/ephemeral" + "github.com/hashicorp/terraform/internal/lang/langrefs" "github.com/hashicorp/terraform/internal/plans" "github.com/hashicorp/terraform/internal/plans/objchange" "github.com/hashicorp/terraform/internal/providers" @@ -20,6 +21,7 @@ type nodeActionTriggerApply struct { ActionInvocation *plans.ActionInvocationInstanceSrc resolvedProvider addrs.AbsProviderConfig ActionTriggerRange *hcl.Range + ConditionExpr hcl.Expression } var ( @@ -35,7 +37,26 @@ func (n *nodeActionTriggerApply) Execute(ctx EvalContext, wo walkOperation) tfdi var diags tfdiags.Diagnostics actionInvocation := n.ActionInvocation - // TODO: Handle verifying the condition here, if we have any. + if n.ConditionExpr != nil { + condition, conditionDiags := evaluateCondition(ctx, n.ConditionExpr) + diags = diags.Append(conditionDiags) + if diags.HasErrors() { + return diags + } + if !condition.IsWhollyKnown() { + return diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Condition expression is not known", + Detail: "During apply the condition expression must be known, and must evaluate to a boolean value", + Subject: n.ConditionExpr.Range().Ptr(), + }) + } + // If the condition evaluates to false, skip the action + if condition.False() { + return diags + } + } + ai := ctx.Changes().GetActionInvocation(actionInvocation.Addr, actionInvocation.ActionTrigger) if ai == nil { diags = diags.Append(&hcl.Diagnostic{ @@ -164,6 +185,14 @@ func (n *nodeActionTriggerApply) References() []*addrs.Reference { Subject: n.ActionInvocation.Addr.Action, }) + conditionRefs, refDiags := langrefs.ReferencesInExpr(addrs.ParseRef, n.ConditionExpr) + if refDiags.HasErrors() { + panic(fmt.Sprintf("error parsing references in expression: %v", refDiags)) + } + if conditionRefs != nil { + refs = append(refs, conditionRefs...) + } + return refs } @@ -172,6 +201,11 @@ func (n *nodeActionTriggerApply) ModulePath() addrs.Module { return n.ActionInvocation.Addr.Module.Module() } +// GraphNodeModuleInstance +func (n *nodeActionTriggerApply) Path() addrs.ModuleInstance { + return n.ActionInvocation.Addr.Module +} + func (n *nodeActionTriggerApply) AddSubjectToDiagnostics(input tfdiags.Diagnostics) tfdiags.Diagnostics { var diags tfdiags.Diagnostics if len(input) > 0 { diff --git a/internal/terraform/node_action_trigger_instance_plan.go b/internal/terraform/node_action_trigger_instance_plan.go index 856a9de6b0..1f9f920e65 100644 --- a/internal/terraform/node_action_trigger_instance_plan.go +++ b/internal/terraform/node_action_trigger_instance_plan.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/terraform/internal/plans/deferring" "github.com/hashicorp/terraform/internal/providers" "github.com/hashicorp/terraform/internal/tfdiags" + "github.com/zclconf/go-cty/cty" ) type nodeActionTriggerPlanInstance struct { @@ -32,6 +33,7 @@ type lifecycleActionTriggerInstance struct { actionTriggerBlockIndex int actionListIndex int invokingSubject *hcl.Range + conditionExpr hcl.Expression } func (at *lifecycleActionTriggerInstance) Name() string { @@ -111,6 +113,21 @@ func (n *nodeActionTriggerPlanInstance) Execute(ctx EvalContext, operation walkO if triggeringEvent == nil { panic("triggeringEvent cannot be nil") } + + // Evaluate the condition expression if it exists (otherwise it's true) + if n.lifecycleActionTrigger != nil && n.lifecycleActionTrigger.conditionExpr != nil { + condition, conditionDiags := evaluateCondition(ctx, n.lifecycleActionTrigger.conditionExpr) + diags = diags.Append(conditionDiags) + if conditionDiags.HasErrors() { + return conditionDiags + } + + // The condition is false so we skip the action + if condition.False() { + return diags + } + } + // We need to set the triggering event on the action invocation ai.ActionTrigger = n.lifecycleActionTrigger.ActionTrigger(*triggeringEvent) @@ -183,3 +200,28 @@ func (n *nodeActionTriggerPlanInstance) Path() addrs.ModuleInstance { // to the same module. So we can simply return the module path of the action. return n.actionAddress.Module } + +func evaluateCondition(ctx EvalContext, conditionExpr hcl.Expression) (cty.Value, tfdiags.Diagnostics) { + // TODO: Support self in conditions + val, diags := ctx.EvaluateExpr(conditionExpr, cty.Bool, nil) + if diags.HasErrors() { + return cty.False, diags + } + + // TODO: Support unknown condition values + if !val.IsWhollyKnown() { + panic("condition is not wholly known") + } + // If the condition is neither true nor false, it's an error + if !(val.True() || val.False()) { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid condition", + Detail: "The condition must be either true or false", + Subject: conditionExpr.Range().Ptr(), + }) + return cty.False, diags + } + + return val, nil +} diff --git a/internal/terraform/node_action_trigger_plan.go b/internal/terraform/node_action_trigger_plan.go index a9c19a917a..6fc2531b65 100644 --- a/internal/terraform/node_action_trigger_plan.go +++ b/internal/terraform/node_action_trigger_plan.go @@ -24,13 +24,13 @@ type nodeActionTriggerPlanExpand struct { } type lifecycleActionTrigger struct { - resourceAddress addrs.ConfigResource - events []configs.ActionTriggerEvent - //condition hcl.Expression + resourceAddress addrs.ConfigResource + events []configs.ActionTriggerEvent actionTriggerBlockIndex int actionListIndex int invokingSubject *hcl.Range actionExpr hcl.Expression + conditionExpr hcl.Expression } func (at *lifecycleActionTrigger) Name() string { @@ -103,6 +103,7 @@ func (n *nodeActionTriggerPlanExpand) DynamicExpand(ctx EvalContext) (*Graph, tf actionTriggerBlockIndex: n.lifecycleActionTrigger.actionTriggerBlockIndex, actionListIndex: n.lifecycleActionTrigger.actionListIndex, invokingSubject: n.lifecycleActionTrigger.invokingSubject, + conditionExpr: n.lifecycleActionTrigger.conditionExpr, }, } diff --git a/internal/terraform/transform_action_diff.go b/internal/terraform/transform_action_diff.go index 72463e97e9..d06a01ffe5 100644 --- a/internal/terraform/transform_action_diff.go +++ b/internal/terraform/transform_action_diff.go @@ -57,6 +57,7 @@ func (t *ActionDiffTransformer) Transform(g *Graph) error { act := triggerBlock.Actions[at.ActionsListIndex] node.ActionTriggerRange = &act.Range + node.ConditionExpr = triggerBlock.Condition } g.Add(node) diff --git a/internal/terraform/transform_action_plan.go b/internal/terraform/transform_action_plan.go index 904455a760..752a8e9b2f 100644 --- a/internal/terraform/transform_action_plan.go +++ b/internal/terraform/transform_action_plan.go @@ -146,6 +146,7 @@ func (t *ActionPlanTransformer) transformSingle(g *Graph, config *configs.Config actionTriggerBlockIndex: i, actionListIndex: j, invokingSubject: action.Expr.Range().Ptr(), + conditionExpr: at.Condition, }, }