Skip to content

Commit f574c9c

Browse files
authored
actions: validate that action referenced in action_trigger exists in config during transform (#37559)
1 parent 1cb7d18 commit f574c9c

File tree

2 files changed

+78
-0
lines changed

2 files changed

+78
-0
lines changed

internal/terraform/context_plan_actions_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3257,6 +3257,35 @@ resource "test_object" "a" {
32573257
}
32583258
}
32593259

3260+
func TestContextPlan_validateActionInTriggerExists(t *testing.T) {
3261+
// this validation occurs during TransformConfig
3262+
module := `
3263+
resource "test_object" "a" {
3264+
lifecycle {
3265+
action_trigger {
3266+
events = [after_create]
3267+
actions = [action.act_unlinked.hello]
3268+
}
3269+
}
3270+
}
3271+
`
3272+
m := testModuleInline(t, map[string]string{"main.tf": module})
3273+
p := simpleMockProvider()
3274+
ctx := testContext2(t, &ContextOpts{
3275+
Providers: map[addrs.Provider]providers.Factory{
3276+
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
3277+
},
3278+
})
3279+
3280+
_, diags := ctx.Plan(m, nil, DefaultPlanOpts)
3281+
if !diags.HasErrors() {
3282+
t.Fatal("expected errors, got success!")
3283+
}
3284+
if diags.Err().Error() != "Configuration for triggered action does not exist: The configuration for the given action action.act_unlinked.hello does not exist. All triggered actions must have an associated configuration." {
3285+
t.Fatal("wrong error!")
3286+
}
3287+
}
3288+
32603289
func mustActionInstanceAddr(t *testing.T, address string) addrs.AbsActionInstance {
32613290
action, diags := addrs.ParseAbsActionInstanceStr(address)
32623291
if len(diags) > 0 {

internal/terraform/transform_config.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/hashicorp/terraform/internal/addrs"
1313
"github.com/hashicorp/terraform/internal/configs"
1414
"github.com/hashicorp/terraform/internal/dag"
15+
"github.com/hashicorp/terraform/internal/lang/langrefs"
1516
"github.com/hashicorp/terraform/internal/tfdiags"
1617
)
1718

@@ -131,9 +132,14 @@ func (t *ConfigTransformer) transformSingle(g *Graph, config *configs.Config) er
131132
}
132133
}
133134

135+
// collect all the Action Declarations (configs.Actions) in this module so
136+
// we can validate that actions referenced in a resource's ActionTriggers
137+
// exist in this module.
138+
allConfigActions := make(map[string]*configs.Action)
134139
for _, a := range module.Actions {
135140
if a != nil {
136141
addr := a.Addr().InModule(path)
142+
allConfigActions[addr.String()] = a
137143
log.Printf("[TRACE] ConfigTransformer: Adding action %s", addr)
138144
abstract := &NodeAbstractAction{
139145
Addr: addr,
@@ -162,6 +168,49 @@ func (t *ConfigTransformer) transformSingle(g *Graph, config *configs.Config) er
162168
continue
163169
}
164170

171+
// Verify that any actions referenced in the resource's ActionTriggers exist in this module
172+
var diags tfdiags.Diagnostics
173+
if r.Managed != nil && r.Managed.ActionTriggers != nil {
174+
for i, at := range r.Managed.ActionTriggers {
175+
for _, action := range at.Actions {
176+
177+
refs, parseRefDiags := langrefs.ReferencesInExpr(addrs.ParseRef, action.Expr)
178+
if parseRefDiags != nil {
179+
return parseRefDiags.Err()
180+
}
181+
182+
var configAction addrs.ConfigAction
183+
184+
for _, ref := range refs {
185+
switch a := ref.Subject.(type) {
186+
case addrs.Action:
187+
configAction = a.InModule(config.Path)
188+
case addrs.ActionInstance:
189+
configAction = a.Action.InModule(config.Path)
190+
case addrs.CountAttr, addrs.ForEachAttr:
191+
// nothing to do, these will get evaluated later
192+
default:
193+
// This should have been caught during validation
194+
panic(fmt.Sprintf("unexpected action address %T", a))
195+
}
196+
}
197+
198+
_, ok := allConfigActions[configAction.String()]
199+
if !ok {
200+
diags = diags.Append(&hcl.Diagnostic{
201+
Severity: hcl.DiagError,
202+
Summary: "Configuration for triggered action does not exist",
203+
Detail: fmt.Sprintf("The configuration for the given action %s does not exist. All triggered actions must have an associated configuration.", configAction.String()),
204+
Subject: &r.Managed.ActionTriggers[i].DeclRange,
205+
})
206+
}
207+
}
208+
}
209+
}
210+
if diags.HasErrors() {
211+
return diags.Err()
212+
}
213+
165214
// If any of the import targets can apply to this node's instances,
166215
// filter them down to the applicable addresses.
167216
var imports []*ImportTarget

0 commit comments

Comments
 (0)