mirror of
https://github.com/hashicorp/terraform.git
synced 2026-02-03 20:50:59 -05:00
after/before_destroy actions should run on terraform destroy
This commit is contained in:
parent
82e073fc19
commit
44f9cc5dc4
9 changed files with 408 additions and 357 deletions
|
|
@ -104,11 +104,15 @@ func decodeActionTriggerBlock(block *hcl.Block) (*ActionTrigger, hcl.Diagnostics
|
|||
event = BeforeUpdate
|
||||
case "after_update":
|
||||
event = AfterUpdate
|
||||
case "before_destroy":
|
||||
event = BeforeDestroy
|
||||
case "after_destroy":
|
||||
event = AfterDestroy
|
||||
default:
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: fmt.Sprintf("Invalid \"event\" value %s", hcl.ExprAsKeyword(expr)),
|
||||
Detail: "The \"event\" argument supports the following values: before_create, after_create, before_update, after_update.",
|
||||
Detail: "The \"event\" argument supports the following values: before_create, after_create, before_update, after_update, before_destroy, after_destroy.",
|
||||
Subject: expr.Range().Ptr(),
|
||||
})
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ func TestDecodeActionTriggerBlock(t *testing.T) {
|
|||
},
|
||||
},
|
||||
[]string{
|
||||
"MockExprTraversal:0,0-12: Invalid \"event\" value not_an_event; The \"event\" argument supports the following values: before_create, after_create, before_update, after_update.",
|
||||
"MockExprTraversal:0,0-12: Invalid \"event\" value not_an_event; The \"event\" argument supports the following values: before_create, after_create, before_update, after_update, before_destroy, after_destroy.",
|
||||
":0,0-0: No events specified; At least one event must be specified for an action_trigger.",
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
|
|
@ -4132,29 +4133,29 @@ resource "test_object" "b" {
|
|||
"don't trigger during create or update": {
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
action "test_action" "hello" {}
|
||||
resource "test_object" "a" {
|
||||
name = "a"
|
||||
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [before_destroy]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
action "test_action" "hello" {}
|
||||
resource "test_object" "a" {
|
||||
name = "a"
|
||||
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [before_destroy]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
action "test_action" "world" {}
|
||||
resource "test_object" "b" {
|
||||
name = "b"
|
||||
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [after_destroy]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
action "test_action" "world" {}
|
||||
resource "test_object" "b" {
|
||||
name = "b"
|
||||
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [after_destroy]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
`,
|
||||
},
|
||||
expectPlanActionCalled: false,
|
||||
buildState: func(s *states.SyncState) {
|
||||
|
|
@ -4168,23 +4169,22 @@ resource "test_object" "b" {
|
|||
"replace - after_destroy triggers before before_create": {
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
action "test_action" "hello" {}
|
||||
action "test_action" "world" {}
|
||||
resource "test_object" "a" {
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [before_create]
|
||||
actions = [action.test_action.world]
|
||||
}
|
||||
action_trigger {
|
||||
events = [after_destroy]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
action "test_action" "hello" {}
|
||||
action "test_action" "world" {}
|
||||
resource "test_object" "a" {
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [before_create]
|
||||
actions = [action.test_action.world]
|
||||
}
|
||||
action_trigger {
|
||||
events = [after_destroy]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
buildState: func(s *states.SyncState) {
|
||||
addr := mustResourceInstanceAddr("test_object.a")
|
||||
s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
|
||||
|
|
@ -4197,6 +4197,8 @@ resource "test_object" "a" {
|
|||
if len(p.Changes.ActionInvocations) != 2 {
|
||||
t.Fatalf("expected 2 action invocations, got %d", len(p.Changes.ActionInvocations))
|
||||
}
|
||||
fmt.Printf("\n\t p.Changes.ActionInvocations[0].Addr.String() --> %#v\n", p.Changes.ActionInvocations[0].Addr.String())
|
||||
fmt.Printf("\n\t p.Changes.ActionInvocations[1].Addr.String() --> %#v\n", p.Changes.ActionInvocations[1].Addr.String())
|
||||
// The first triggered action should be the after_destroy
|
||||
if p.Changes.ActionInvocations[0].Addr.String() != "action.test_action.hello" {
|
||||
t.Fatalf("expected first action to be 'action.test_action.hello', got %s", p.Changes.ActionInvocations[0].Addr.String())
|
||||
|
|
@ -4207,29 +4209,30 @@ resource "test_object" "a" {
|
|||
}
|
||||
},
|
||||
},
|
||||
|
||||
"replace - before_destroy triggers before before_create and before after_destroy": {
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
action "test_action" "hi" {}
|
||||
action "test_action" "hello" {}
|
||||
action "test_action" "world" {}
|
||||
resource "test_object" "a" {
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [before_create]
|
||||
actions = [action.test_action.world]
|
||||
}
|
||||
action_trigger {
|
||||
events = [after_destroy]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
action_trigger {
|
||||
events = [before_destroy]
|
||||
actions = [action.test_action.hi]
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
action "test_action" "hi" {}
|
||||
action "test_action" "hello" {}
|
||||
action "test_action" "world" {}
|
||||
resource "test_object" "a" {
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [before_create]
|
||||
actions = [action.test_action.world]
|
||||
}
|
||||
action_trigger {
|
||||
events = [after_destroy]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
action_trigger {
|
||||
events = [before_destroy]
|
||||
actions = [action.test_action.hi]
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
buildState: func(s *states.SyncState) {
|
||||
|
|
@ -4262,27 +4265,27 @@ resource "test_object" "a" {
|
|||
"replace with create_before_destroy - before_create and after_create trigger before before_destroy triggers": {
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
action "test_action" "hi" {}
|
||||
action "test_action" "hello" {}
|
||||
action "test_action" "world" {}
|
||||
resource "test_object" "a" {
|
||||
lifecycle {
|
||||
create_before_destroy = true
|
||||
action_trigger {
|
||||
events = [before_destroy]
|
||||
actions = [action.test_action.world]
|
||||
}
|
||||
action_trigger {
|
||||
events = [after_create]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
action_trigger {
|
||||
events = [before_create]
|
||||
actions = [action.test_action.hi]
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
action "test_action" "hi" {}
|
||||
action "test_action" "hello" {}
|
||||
action "test_action" "world" {}
|
||||
resource "test_object" "a" {
|
||||
lifecycle {
|
||||
create_before_destroy = true
|
||||
action_trigger {
|
||||
events = [before_destroy]
|
||||
actions = [action.test_action.world]
|
||||
}
|
||||
action_trigger {
|
||||
events = [after_create]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
action_trigger {
|
||||
events = [before_create]
|
||||
actions = [action.test_action.hi]
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
buildState: func(s *states.SyncState) {
|
||||
|
|
@ -4315,16 +4318,17 @@ resource "test_object" "a" {
|
|||
"destroy - triggers destroy actions": {
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
action "test_action" "hello" {}
|
||||
resource "test_object" "a" {
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [after_destroy]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
action "test_action" "hello" {}
|
||||
resource "test_object" "a" {
|
||||
name = "name"
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [after_destroy]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
planOpts: &PlanOpts{
|
||||
|
|
@ -4333,7 +4337,7 @@ resource "test_object" "a" {
|
|||
buildState: func(s *states.SyncState) {
|
||||
addr := mustResourceInstanceAddr("test_object.a")
|
||||
s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
|
||||
AttrsJSON: []byte(`{"name":"previous_run"}`),
|
||||
AttrsJSON: []byte(`{"name":"name"}`),
|
||||
Status: states.ObjectReady,
|
||||
}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
|
||||
},
|
||||
|
|
@ -4351,16 +4355,16 @@ resource "test_object" "a" {
|
|||
"forget - don't trigger destroy actions": {
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
action "test_action" "hello" {}
|
||||
resource "test_object" "a" {
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [after_destroy]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
action "test_action" "hello" {}
|
||||
resource "test_object" "a" {
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [after_destroy]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
planOpts: &PlanOpts{
|
||||
|
|
@ -4385,18 +4389,18 @@ resource "test_object" "a" {
|
|||
"removing an instance triggers destroy actions": {
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
action "test_action" "hello" {}
|
||||
resource "test_object" "a" {
|
||||
name = "instance"
|
||||
count = 1
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [after_destroy]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
action "test_action" "hello" {}
|
||||
resource "test_object" "a" {
|
||||
name = "instance"
|
||||
count = 1
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [after_destroy]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
buildState: func(s *states.SyncState) {
|
||||
|
|
@ -4433,18 +4437,18 @@ resource "test_object" "a" {
|
|||
"removed block - does trigger destroy actions": {
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
action "test_action" "hello" {}
|
||||
action "test_action" "hello" {}
|
||||
|
||||
removed {
|
||||
from = "test_object.a"
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [after_destroy]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
removed {
|
||||
from = "test_object.a"
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [after_destroy]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
buildState: func(s *states.SyncState) {
|
||||
|
|
@ -4468,19 +4472,19 @@ removed {
|
|||
"removed block with destroy set to false - does not trigger destroy actions": {
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
action "test_action" "hello" {}
|
||||
action "test_action" "hello" {}
|
||||
|
||||
removed {
|
||||
from = "test_object.a"
|
||||
lifecycle {
|
||||
destroy = true
|
||||
action_trigger {
|
||||
events = [after_destroy]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
removed {
|
||||
from = "test_object.a"
|
||||
lifecycle {
|
||||
destroy = true
|
||||
action_trigger {
|
||||
events = [after_destroy]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
buildState: func(s *states.SyncState) {
|
||||
|
|
@ -4501,21 +4505,21 @@ removed {
|
|||
"before_destroy actions can access the triggering resource": {
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
action "test_action" "hello" {
|
||||
config {
|
||||
attr = test_object.a.name
|
||||
}
|
||||
}
|
||||
resource "test_object" "a" {
|
||||
name = "instance"
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [before_destroy]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
action "test_action" "hello" {
|
||||
config {
|
||||
attr = test_object.a.name
|
||||
}
|
||||
}
|
||||
resource "test_object" "a" {
|
||||
name = "instance"
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [before_destroy]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
planOpts: &PlanOpts{
|
||||
|
|
@ -4549,23 +4553,23 @@ resource "test_object" "a" {
|
|||
"before_destroy actions can access the triggering resource instance": {
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
action "test_action" "hello" {
|
||||
count = 1
|
||||
config {
|
||||
attr = test_object.a[count.index].name
|
||||
}
|
||||
}
|
||||
resource "test_object" "a" {
|
||||
count = 1
|
||||
name = "instance#{count.index}"
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [before_destroy]
|
||||
actions = [action.test_action.hello[count.index]]
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
action "test_action" "hello" {
|
||||
count = 1
|
||||
config {
|
||||
attr = test_object.a[count.index].name
|
||||
}
|
||||
}
|
||||
resource "test_object" "a" {
|
||||
count = 1
|
||||
name = "instance#{count.index}"
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [before_destroy]
|
||||
actions = [action.test_action.hello[count.index]]
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
planOpts: &PlanOpts{
|
||||
|
|
@ -4599,21 +4603,21 @@ resource "test_object" "a" {
|
|||
"after_destroy actions can access the triggering resource": {
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
action "test_action" "hello" {
|
||||
config {
|
||||
attr = test_object.a.name
|
||||
}
|
||||
}
|
||||
resource "test_object" "a" {
|
||||
name = "instance"
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [after_destroy]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
action "test_action" "hello" {
|
||||
config {
|
||||
attr = test_object.a.name
|
||||
}
|
||||
}
|
||||
resource "test_object" "a" {
|
||||
name = "instance"
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [after_destroy]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
planOpts: &PlanOpts{
|
||||
|
|
@ -4647,23 +4651,23 @@ resource "test_object" "a" {
|
|||
"after_destroy actions can access the triggering resource instance": {
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
action "test_action" "hello" {
|
||||
count = 1
|
||||
config {
|
||||
attr = test_object.a[count.index].name
|
||||
}
|
||||
}
|
||||
resource "test_object" "a" {
|
||||
count = 1
|
||||
name = "instance#{count.index}"
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [after_destroy]
|
||||
actions = [action.test_action.hello[count.index]]
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
action "test_action" "hello" {
|
||||
count = 1
|
||||
config {
|
||||
attr = test_object.a[count.index].name
|
||||
}
|
||||
}
|
||||
resource "test_object" "a" {
|
||||
count = 1
|
||||
name = "instance#{count.index}"
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [after_destroy]
|
||||
actions = [action.test_action.hello[count.index]]
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
planOpts: &PlanOpts{
|
||||
|
|
@ -4697,24 +4701,24 @@ resource "test_object" "a" {
|
|||
"after_destroy actions can not access other resources than the triggering resource": {
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
action "test_action" "hello" {
|
||||
config {
|
||||
attr = test_object.a.name + test_object.forbidden.name
|
||||
}
|
||||
}
|
||||
resource "test_object" "a" {
|
||||
name = "instance"
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [after_destroy]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
}
|
||||
}
|
||||
resource "test_object" "forbidden" {
|
||||
name = "you-should-not-access-me"
|
||||
}
|
||||
`,
|
||||
action "test_action" "hello" {
|
||||
config {
|
||||
attr = test_object.a.name + test_object.forbidden.name
|
||||
}
|
||||
}
|
||||
resource "test_object" "a" {
|
||||
name = "instance"
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [after_destroy]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
}
|
||||
}
|
||||
resource "test_object" "forbidden" {
|
||||
name = "you-should-not-access-me"
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
planOpts: &PlanOpts{
|
||||
|
|
@ -4739,24 +4743,24 @@ resource "test_object" "forbidden" {
|
|||
"before_destroy actions can not access other resources than the triggering resource": {
|
||||
module: map[string]string{
|
||||
"main.tf": `
|
||||
action "test_action" "hello" {
|
||||
config {
|
||||
attr = test_object.a.name + test_object.forbidden.name
|
||||
}
|
||||
}
|
||||
resource "test_object" "a" {
|
||||
name = "instance"
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [before_destroy]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
}
|
||||
}
|
||||
resource "test_object" "forbidden" {
|
||||
name = "you-should-not-access-me"
|
||||
}
|
||||
`,
|
||||
action "test_action" "hello" {
|
||||
config {
|
||||
attr = test_object.a.name + test_object.forbidden.name
|
||||
}
|
||||
}
|
||||
resource "test_object" "a" {
|
||||
name = "instance"
|
||||
lifecycle {
|
||||
action_trigger {
|
||||
events = [before_destroy]
|
||||
actions = [action.test_action.hello]
|
||||
}
|
||||
}
|
||||
}
|
||||
resource "test_object" "forbidden" {
|
||||
name = "you-should-not-access-me"
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
planOpts: &PlanOpts{
|
||||
|
|
|
|||
|
|
@ -137,11 +137,13 @@ type PlanGraphBuilder struct {
|
|||
// See GraphBuilder
|
||||
func (b *PlanGraphBuilder) Build(path addrs.ModuleInstance) (*Graph, tfdiags.Diagnostics) {
|
||||
log.Printf("[TRACE] building graph for %s", b.Operation)
|
||||
return (&BasicGraphBuilder{
|
||||
g, d := (&BasicGraphBuilder{
|
||||
Steps: b.Steps(),
|
||||
Name: "PlanGraphBuilder",
|
||||
SkipGraphValidation: b.SkipGraphValidation,
|
||||
}).Build(path)
|
||||
|
||||
return g, d
|
||||
}
|
||||
|
||||
// See GraphBuilder
|
||||
|
|
@ -172,26 +174,6 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
|||
generateConfigPathForImportTargets: b.GenerateConfigPath,
|
||||
},
|
||||
|
||||
&ActionTriggerConfigTransformer{
|
||||
Config: b.Config,
|
||||
Operation: b.Operation,
|
||||
ActionTargets: b.ActionTargets,
|
||||
queryPlanMode: b.queryPlan,
|
||||
|
||||
ConcreteActionTriggerNodeFunc: func(node *nodeAbstractActionTriggerExpand, _ RelativeActionTiming) dag.Vertex {
|
||||
return &nodeActionTriggerPlanExpand{
|
||||
nodeAbstractActionTriggerExpand: node,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
&ActionInvokePlanTransformer{
|
||||
Config: b.Config,
|
||||
Operation: b.Operation,
|
||||
ActionTargets: b.ActionTargets,
|
||||
queryPlanMode: b.queryPlan,
|
||||
},
|
||||
|
||||
// Add dynamic values
|
||||
&RootVariableTransformer{
|
||||
Config: b.Config,
|
||||
|
|
@ -249,6 +231,26 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
|||
State: b.State,
|
||||
},
|
||||
|
||||
&ActionTriggerConfigTransformer{
|
||||
Config: b.Config,
|
||||
Operation: b.Operation,
|
||||
ActionTargets: b.ActionTargets,
|
||||
queryPlanMode: b.queryPlan,
|
||||
|
||||
ConcreteActionTriggerNodeFunc: func(node *nodeAbstractActionTriggerExpand, _ RelativeActionTiming) dag.Vertex {
|
||||
return &nodeActionTriggerPlanExpand{
|
||||
nodeAbstractActionTriggerExpand: node,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
&ActionInvokePlanTransformer{
|
||||
Config: b.Config,
|
||||
Operation: b.Operation,
|
||||
ActionTargets: b.ActionTargets,
|
||||
queryPlanMode: b.queryPlan,
|
||||
},
|
||||
|
||||
// Attach the state
|
||||
&AttachStateTransformer{State: b.State},
|
||||
|
||||
|
|
|
|||
|
|
@ -296,7 +296,7 @@ func evaluateActionCondition(ctx EvalContext, at actionConditionContext) (bool,
|
|||
func containsBeforeEvent(events []configs.ActionTriggerEvent) bool {
|
||||
for _, event := range events {
|
||||
switch event {
|
||||
case configs.BeforeCreate, configs.BeforeUpdate:
|
||||
case configs.BeforeCreate, configs.BeforeUpdate, configs.BeforeDestroy:
|
||||
return true
|
||||
default:
|
||||
continue
|
||||
|
|
@ -304,3 +304,32 @@ func containsBeforeEvent(events []configs.ActionTriggerEvent) bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func actionIsTriggeredByEvent(events []configs.ActionTriggerEvent, action plans.Action) []configs.ActionTriggerEvent {
|
||||
triggeredEvents := []configs.ActionTriggerEvent{}
|
||||
for _, event := range events {
|
||||
switch event {
|
||||
case configs.BeforeCreate, configs.AfterCreate:
|
||||
if action.IsReplace() || action == plans.Create {
|
||||
triggeredEvents = append(triggeredEvents, event)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
case configs.BeforeUpdate, configs.AfterUpdate:
|
||||
if action == plans.Update {
|
||||
triggeredEvents = append(triggeredEvents, event)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
case configs.BeforeDestroy, configs.AfterDestroy:
|
||||
if action == plans.DeleteThenCreate || action == plans.CreateThenDelete || action == plans.Delete {
|
||||
triggeredEvents = append(triggeredEvents, event)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown action trigger event %s", event))
|
||||
}
|
||||
}
|
||||
return triggeredEvents
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,11 @@ type nodeActionTriggerPlanExpand struct {
|
|||
*nodeAbstractActionTriggerExpand
|
||||
|
||||
resourceTargets []addrs.Targetable
|
||||
|
||||
// During planDestroy we don't expand the resource, but instead
|
||||
// use the state to set the resource instances to be processed.
|
||||
// This means we need to track them here and not rely on the expander.
|
||||
manualResourceExpansion []addrs.AbsResourceInstance
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
@ -69,74 +74,86 @@ func (n *nodeActionTriggerPlanExpand) DynamicExpand(ctx EvalContext) (*Graph, tf
|
|||
}
|
||||
}
|
||||
|
||||
// First we expand the module
|
||||
moduleInstances := expander.ExpandModule(n.lifecycleActionTrigger.resourceAddress.Module, false)
|
||||
for _, module := range moduleInstances {
|
||||
_, keys, _ := expander.ResourceInstanceKeys(n.lifecycleActionTrigger.resourceAddress.Absolute(module))
|
||||
for _, key := range keys {
|
||||
absResourceInstanceAddr := n.lifecycleActionTrigger.resourceAddress.Absolute(module).Instance(key)
|
||||
absResourceInstanceAddrs := []addrs.AbsResourceInstance{}
|
||||
|
||||
// If the triggering resource was targeted, make sure the instance
|
||||
// that triggered this was targeted specifically.
|
||||
// This is necessary since the expansion of a resource instance (and of an action trigger)
|
||||
// happens during the graph walk / execution, therefore the target transformer can not
|
||||
// filter out individual instances, this needs to happen during the graph walk / execution.
|
||||
if n.resourceTargets != nil {
|
||||
matched := false
|
||||
for _, resourceTarget := range n.resourceTargets {
|
||||
if resourceTarget.TargetContains(absResourceInstanceAddr) {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
continue
|
||||
// If we are in a walk that does not expand on its own, we need to use the
|
||||
// manualResourceExpansion list.
|
||||
if len(n.manualResourceExpansion) > 0 {
|
||||
absResourceInstanceAddrs = n.manualResourceExpansion
|
||||
} else {
|
||||
// First we expand the module
|
||||
moduleInstances := expander.ExpandModule(n.lifecycleActionTrigger.resourceAddress.Module, false)
|
||||
for _, module := range moduleInstances {
|
||||
_, keys, _ := expander.ResourceInstanceKeys(n.lifecycleActionTrigger.resourceAddress.Absolute(module))
|
||||
for _, key := range keys {
|
||||
absResourceInstanceAddr := n.lifecycleActionTrigger.resourceAddress.Absolute(module).Instance(key)
|
||||
absResourceInstanceAddrs = append(absResourceInstanceAddrs, absResourceInstanceAddr)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, absResourceInstanceAddr := range absResourceInstanceAddrs {
|
||||
module := absResourceInstanceAddr.Module
|
||||
key := absResourceInstanceAddr.Resource.Key
|
||||
// If the triggering resource was targeted, make sure the instance
|
||||
// that triggered this was targeted specifically.
|
||||
// This is necessary since the expansion of a resource instance (and of an action trigger)
|
||||
// happens during the graph walk / execution, therefore the target transformer can not
|
||||
// filter out individual instances, this needs to happen during the graph walk / execution.
|
||||
if n.resourceTargets != nil {
|
||||
matched := false
|
||||
for _, resourceTarget := range n.resourceTargets {
|
||||
if resourceTarget.TargetContains(absResourceInstanceAddr) {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// The n.Addr was derived from the ActionRef hcl.Expression referenced inside the resource's lifecycle block, and has not yet been
|
||||
// expanded or fully evaluated, so we will do that now.
|
||||
// Grab the instance key, necessary if the action uses [count.index] or [each.key]
|
||||
repData := instances.RepetitionData{}
|
||||
switch k := key.(type) {
|
||||
case addrs.IntKey:
|
||||
repData.CountIndex = k.Value()
|
||||
case addrs.StringKey:
|
||||
repData.EachKey = k.Value()
|
||||
repData.EachValue = cty.DynamicVal
|
||||
}
|
||||
|
||||
ref, evalActionDiags := evaluateActionExpression(n.lifecycleActionTrigger.actionExpr, repData)
|
||||
diags = append(diags, evalActionDiags...)
|
||||
if diags.HasErrors() {
|
||||
if !matched {
|
||||
continue
|
||||
}
|
||||
|
||||
// The reference is either an action or action instance
|
||||
var actionAddr addrs.AbsActionInstance
|
||||
switch sub := ref.Subject.(type) {
|
||||
case addrs.Action:
|
||||
actionAddr = sub.Absolute(module).Instance(addrs.NoKey)
|
||||
case addrs.ActionInstance:
|
||||
actionAddr = sub.Absolute(module)
|
||||
}
|
||||
|
||||
node := nodeActionTriggerPlanInstance{
|
||||
actionAddress: actionAddr,
|
||||
resolvedProvider: n.resolvedProvider,
|
||||
actionConfig: n.Config,
|
||||
lifecycleActionTrigger: &lifecycleActionTriggerInstance{
|
||||
resourceAddress: absResourceInstanceAddr,
|
||||
events: n.lifecycleActionTrigger.events,
|
||||
actionTriggerBlockIndex: n.lifecycleActionTrigger.actionTriggerBlockIndex,
|
||||
actionListIndex: n.lifecycleActionTrigger.actionListIndex,
|
||||
invokingSubject: n.lifecycleActionTrigger.invokingSubject,
|
||||
conditionExpr: n.lifecycleActionTrigger.conditionExpr,
|
||||
},
|
||||
}
|
||||
|
||||
g.Add(&node)
|
||||
}
|
||||
|
||||
// The n.Addr was derived from the ActionRef hcl.Expression referenced inside the resource's lifecycle block, and has not yet been
|
||||
// expanded or fully evaluated, so we will do that now.
|
||||
// Grab the instance key, necessary if the action uses [count.index] or [each.key]
|
||||
repData := instances.RepetitionData{}
|
||||
switch k := key.(type) {
|
||||
case addrs.IntKey:
|
||||
repData.CountIndex = k.Value()
|
||||
case addrs.StringKey:
|
||||
repData.EachKey = k.Value()
|
||||
repData.EachValue = cty.DynamicVal
|
||||
}
|
||||
|
||||
ref, evalActionDiags := evaluateActionExpression(n.lifecycleActionTrigger.actionExpr, repData)
|
||||
diags = append(diags, evalActionDiags...)
|
||||
if diags.HasErrors() {
|
||||
continue
|
||||
}
|
||||
|
||||
// The reference is either an action or action instance
|
||||
var actionAddr addrs.AbsActionInstance
|
||||
switch sub := ref.Subject.(type) {
|
||||
case addrs.Action:
|
||||
actionAddr = sub.Absolute(module).Instance(addrs.NoKey)
|
||||
case addrs.ActionInstance:
|
||||
actionAddr = sub.Absolute(module)
|
||||
}
|
||||
|
||||
node := nodeActionTriggerPlanInstance{
|
||||
actionAddress: actionAddr,
|
||||
resolvedProvider: n.resolvedProvider,
|
||||
actionConfig: n.Config,
|
||||
lifecycleActionTrigger: &lifecycleActionTriggerInstance{
|
||||
resourceAddress: absResourceInstanceAddr,
|
||||
events: n.lifecycleActionTrigger.events,
|
||||
actionTriggerBlockIndex: n.lifecycleActionTrigger.actionTriggerBlockIndex,
|
||||
actionListIndex: n.lifecycleActionTrigger.actionListIndex,
|
||||
invokingSubject: n.lifecycleActionTrigger.invokingSubject,
|
||||
conditionExpr: n.lifecycleActionTrigger.conditionExpr,
|
||||
},
|
||||
}
|
||||
|
||||
g.Add(&node)
|
||||
}
|
||||
|
||||
addRootNodeToGraph(&g)
|
||||
|
|
|
|||
|
|
@ -1065,32 +1065,3 @@ func depsEqual(a, b []addrs.ConfigResource) bool {
|
|||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func actionIsTriggeredByEvent(events []configs.ActionTriggerEvent, action plans.Action) []configs.ActionTriggerEvent {
|
||||
triggeredEvents := []configs.ActionTriggerEvent{}
|
||||
for _, event := range events {
|
||||
switch event {
|
||||
case configs.BeforeCreate, configs.AfterCreate:
|
||||
if action.IsReplace() || action == plans.Create {
|
||||
triggeredEvents = append(triggeredEvents, event)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
case configs.BeforeUpdate, configs.AfterUpdate:
|
||||
if action == plans.Update {
|
||||
triggeredEvents = append(triggeredEvents, event)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
case configs.BeforeDestroy, configs.AfterDestroy:
|
||||
if action == plans.DeleteThenCreate || action == plans.CreateThenDelete || action == plans.Delete {
|
||||
triggeredEvents = append(triggeredEvents, event)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown action trigger event %s", event))
|
||||
}
|
||||
}
|
||||
return triggeredEvents
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ package terraform
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
|
|
@ -22,9 +23,11 @@ type ActionTriggerConfigTransformer struct {
|
|||
ConcreteActionTriggerNodeFunc ConcreteActionTriggerNodeFunc
|
||||
}
|
||||
|
||||
var allowedOperations = []walkOperation{walkPlan, walkApply, walkDestroy, walkPlanDestroy}
|
||||
|
||||
func (t *ActionTriggerConfigTransformer) Transform(g *Graph) error {
|
||||
// We don't want to run if we are using the query plan mode or have targets in place
|
||||
if (t.Operation != walkPlan && t.Operation != walkApply) || t.queryPlanMode || len(t.ActionTargets) > 0 {
|
||||
if (!slices.Contains(allowedOperations, t.Operation)) || t.queryPlanMode || len(t.ActionTargets) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -50,8 +53,8 @@ func (t *ActionTriggerConfigTransformer) transform(g *Graph, config *configs.Con
|
|||
func (t *ActionTriggerConfigTransformer) transformSingle(g *Graph, config *configs.Config) error {
|
||||
// During plan we only want to create all triggers to run after the resource
|
||||
createNodesAsAfter := t.Operation == walkPlan
|
||||
// During apply we want all after trigger to also connect to the resource instance nodes
|
||||
connectToResourceInstanceNodes := t.Operation == walkApply
|
||||
// During apply/destroy we want all after trigger to also connect to the resource instance nodes
|
||||
connectToResourceInstanceNodes := t.Operation == walkApply || t.Operation == walkDestroy || t.Operation == walkPlanDestroy
|
||||
actionConfigs := addrs.MakeMap[addrs.ConfigAction, *configs.Action]()
|
||||
for _, a := range config.Module.Actions {
|
||||
actionConfigs.Put(a.Addr().InModule(config.Path), a)
|
||||
|
|
@ -86,9 +89,9 @@ func (t *ActionTriggerConfigTransformer) transformSingle(g *Graph, config *confi
|
|||
containsAfterEvent := false
|
||||
for _, event := range at.Events {
|
||||
switch event {
|
||||
case configs.BeforeCreate, configs.BeforeUpdate:
|
||||
case configs.BeforeCreate, configs.BeforeUpdate, configs.BeforeDestroy:
|
||||
containsBeforeEvent = true
|
||||
case configs.AfterCreate, configs.AfterUpdate:
|
||||
case configs.AfterCreate, configs.AfterUpdate, configs.AfterDestroy:
|
||||
containsAfterEvent = true
|
||||
}
|
||||
}
|
||||
|
|
@ -123,7 +126,7 @@ func (t *ActionTriggerConfigTransformer) transformSingle(g *Graph, config *confi
|
|||
|
||||
resourceAddr := r.Addr().InModule(config.Path)
|
||||
resourceNode, ok := resourceNodes.GetOk(resourceAddr)
|
||||
if !ok {
|
||||
if !ok && !connectToResourceInstanceNodes {
|
||||
panic(fmt.Sprintf("Could not find node for %s", resourceAddr))
|
||||
}
|
||||
|
||||
|
|
@ -141,10 +144,12 @@ func (t *ActionTriggerConfigTransformer) transformSingle(g *Graph, config *confi
|
|||
},
|
||||
}
|
||||
|
||||
var nat dag.Vertex
|
||||
|
||||
// If CreateNodesAsAfter is set we want all nodes to run after the resource
|
||||
// If not we want expansion nodes only to exist if they are being used
|
||||
if !createNodesAsAfter && containsBeforeEvent {
|
||||
nat := t.ConcreteActionTriggerNodeFunc(abstract, RelativeActionTimingBefore)
|
||||
nat = t.ConcreteActionTriggerNodeFunc(abstract, RelativeActionTimingBefore)
|
||||
g.Add(nat)
|
||||
|
||||
// We want to run before the resource nodes
|
||||
|
|
@ -165,7 +170,7 @@ func (t *ActionTriggerConfigTransformer) transformSingle(g *Graph, config *confi
|
|||
}
|
||||
|
||||
if createNodesAsAfter || containsAfterEvent {
|
||||
nat := t.ConcreteActionTriggerNodeFunc(abstract, RelativeActionTimingAfter)
|
||||
nat = t.ConcreteActionTriggerNodeFunc(abstract, RelativeActionTimingAfter)
|
||||
g.Add(nat)
|
||||
|
||||
// We want to run after the resource nodes
|
||||
|
|
@ -184,6 +189,15 @@ func (t *ActionTriggerConfigTransformer) transformSingle(g *Graph, config *confi
|
|||
}
|
||||
priorAfterNodes = append(priorAfterNodes, nat)
|
||||
}
|
||||
|
||||
// TODO: Clarify when this should be done versus the normal expansion process.
|
||||
if natpe, ok := nat.(*nodeActionTriggerPlanExpand); ok {
|
||||
aris := []addrs.AbsResourceInstance{}
|
||||
for _, ri := range resourceInstanceNodes.Get(resourceAddr) {
|
||||
aris = append(aris, ri.ResourceInstanceAddr())
|
||||
}
|
||||
natpe.manualResourceExpansion = aris
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -329,6 +329,16 @@ func (t *pruneUnusedNodesTransformer) Transform(g *Graph) error {
|
|||
continue
|
||||
}
|
||||
|
||||
// another special case are destroy actions, therefore we keep all action triggers
|
||||
_, ok := n.(*nodeActionTriggerPlanExpand)
|
||||
if ok {
|
||||
keep.Add(n)
|
||||
}
|
||||
_, ok = n.(*nodeExpandActionDeclaration)
|
||||
if ok {
|
||||
keep.Add(n)
|
||||
}
|
||||
|
||||
// from here we only search for managed resource destroy nodes
|
||||
n, ok := n.(GraphNodeDestroyer)
|
||||
if !ok {
|
||||
|
|
|
|||
Loading…
Reference in a new issue