@@ -10,22 +10,37 @@ import (
1010 "github.com/hashicorp/hcl/v2/hclsyntax"
1111
1212 "github.com/hashicorp/terraform/internal/addrs"
13+ "github.com/hashicorp/terraform/internal/lang/langrefs"
14+ "github.com/hashicorp/terraform/internal/tfdiags"
1315)
1416
17+ // There are many ways of handling plurality in error messages (linked_resource
18+ // vs linked_resources); this is one of them.
19+ type diagFn func (* hcl.Range ) * hcl.Diagnostic
20+
1521func invalidLinkedResourceDiag (subj * hcl.Range ) * hcl.Diagnostic {
1622 return & hcl.Diagnostic {
1723 Severity : hcl .DiagError ,
18- Summary : `Invalid " linked_resource" ` ,
19- Detail : `" linked_resource" must only refer to a managed resource in the current module.` ,
24+ Summary : `Invalid linked_resource` ,
25+ Detail : `linked_resource must only refer to a managed resource in the current module.` ,
2026 Subject : subj ,
2127 }
2228}
2329
2430func invalidLinkedResourcesDiag (subj * hcl.Range ) * hcl.Diagnostic {
2531 return & hcl.Diagnostic {
2632 Severity : hcl .DiagError ,
27- Summary : `Invalid "linked_resources"` ,
28- Detail : `"linked_resources" must only refer to managed resources in the current module.` ,
33+ Summary : `Invalid linked_resources` ,
34+ Detail : `linked_resources must only refer to managed resources in the current module.` ,
35+ Subject : subj ,
36+ }
37+ }
38+
39+ func invalidActionDiag (subj * hcl.Range ) * hcl.Diagnostic {
40+ return & hcl.Diagnostic {
41+ Severity : hcl .DiagError ,
42+ Summary : `Invalid action argument inside action_triggers` ,
43+ Detail : `action_triggers.actions must only refer to actions in the current module.` ,
2944 Subject : subj ,
3045 }
3146}
@@ -39,7 +54,7 @@ type Action struct {
3954 ForEach hcl.Expression
4055 // DependsOn []hcl.Traversal // not yet supported
4156
42- LinkedResources []hcl.Traversal
57+ LinkedResources []hcl.Expression
4358
4459 ProviderConfigRef * ProviderConfigRef
4560 Provider addrs.Provider
@@ -76,8 +91,7 @@ const (
7691
7792// ActionRef represents a reference to a configured Action
7893type ActionRef struct {
79- Traversal hcl.Traversal
80-
94+ Expr hcl.Expression
8195 Range hcl.Range
8296}
8397
@@ -142,37 +156,9 @@ func decodeActionTriggerBlock(block *hcl.Block) (*ActionTrigger, hcl.Diagnostics
142156 }
143157
144158 if attr , exists := content .Attributes ["actions" ]; exists {
145- exprs , ediags := hcl . ExprList (attr .Expr )
159+ actionRefs , ediags := decodeActionTriggerRef (attr .Expr )
146160 diags = append (diags , ediags ... )
147- actions := []ActionRef {}
148- for _ , expr := range exprs {
149- traversal , travDiags := hcl .AbsTraversalForExpr (expr )
150- diags = append (diags , travDiags ... )
151-
152- if len (traversal ) > 0 {
153- // verify that the traversal is an action
154- ref , refDiags := addrs .ParseRef (traversal )
155- diags = append (diags , refDiags .ToHCL ()... )
156-
157- switch ref .Subject .(type ) {
158- case addrs.ActionInstance , addrs.Action :
159- actionRef := ActionRef {
160- Traversal : traversal ,
161- Range : expr .Range (),
162- }
163- actions = append (actions , actionRef )
164- default :
165- diags = append (diags , & hcl.Diagnostic {
166- Severity : hcl .DiagError ,
167- Summary : "Invalid actions argument inside action_triggers" ,
168- Detail : "action_triggers.actions accepts a list of one or more actions, which must be in the current module." ,
169- Subject : expr .Range ().Ptr (),
170- })
171- continue
172- }
173- }
174- }
175- a .Actions = actions
161+ a .Actions = actionRefs
176162 }
177163
178164 if len (a .Actions ) == 0 {
@@ -250,30 +236,9 @@ func decodeActionBlock(block *hcl.Block) (*Action, hcl.Diagnostics) {
250236 Subject : & attr .NameRange ,
251237 })
252238 }
253-
254- traversal , travDiags := hcl .AbsTraversalForExpr (attr .Expr )
255- diags = append (diags , travDiags ... )
256- if len (traversal ) != 0 {
257- ref , refDiags := addrs .ParseRef (traversal )
258- diags = append (diags , refDiags .ToHCL ()... )
259-
260- switch res := ref .Subject .(type ) {
261- case addrs.Resource :
262- if res .Mode != addrs .ManagedResourceMode {
263- diags = append (diags , invalidLinkedResourceDiag (& attr .NameRange ))
264- } else {
265- a .LinkedResources = []hcl.Traversal {traversal }
266- }
267- case addrs.ResourceInstance :
268- if res .Resource .Mode != addrs .ManagedResourceMode {
269- diags = append (diags , invalidLinkedResourceDiag (& attr .NameRange ))
270- } else {
271- a .LinkedResources = []hcl.Traversal {traversal }
272- }
273- default :
274- diags = append (diags , invalidLinkedResourceDiag (& attr .NameRange ))
275- }
276- }
239+ lr , lrDiags := decodeLinkedResource (attr .Expr )
240+ diags = append (diags , lrDiags ... )
241+ a .LinkedResources = []hcl.Expression {lr }
277242 }
278243
279244 if attr , exists := content .Attributes ["linked_resources" ]; exists {
@@ -286,29 +251,9 @@ func decodeActionBlock(block *hcl.Block) (*Action, hcl.Diagnostics) {
286251 })
287252 }
288253
289- exprs , exprDiags := hcl .ExprList (attr .Expr )
290- diags = append (diags , exprDiags ... )
291-
292- if len (exprs ) > 0 {
293- lrs := make ([]hcl.Traversal , 0 , len (exprs ))
294- for _ , expr := range exprs {
295- traversal , travDiags := hcl .AbsTraversalForExpr (expr )
296- diags = append (diags , travDiags ... )
297-
298- if len (traversal ) != 0 {
299- ref , refDiags := addrs .ParseRef (traversal )
300- diags = append (diags , refDiags .ToHCL ()... )
301-
302- switch ref .Subject .(type ) {
303- case addrs.Resource , addrs.ResourceInstance :
304- lrs = append (lrs , traversal )
305- default :
306- diags = append (diags , invalidLinkedResourcesDiag (& attr .NameRange ))
307- }
308- }
309- }
310- a .LinkedResources = lrs
311- }
254+ lrs , lrDiags := decodeLinkedResources (attr .Expr )
255+ diags = append (diags , lrDiags ... )
256+ a .LinkedResources = lrs
312257 }
313258
314259 for _ , block := range content .Blocks {
@@ -422,3 +367,198 @@ func (a *Action) ProviderConfigAddr() addrs.LocalProviderConfig {
422367 Alias : a .ProviderConfigRef .Alias ,
423368 }
424369}
370+
371+ // decodeActionTriggerRef decodes and does basic validation of the Actions
372+ // expression list inside a resource's ActionTrigger block, ensuring each only
373+ // reference a single action. This function was largely copied from
374+ // decodeReplaceTriggeredBy, but is much more permissive in what References are
375+ // allowed.
376+ func decodeActionTriggerRef (expr hcl.Expression ) ([]ActionRef , hcl.Diagnostics ) {
377+ exprs , diags := hcl .ExprList (expr )
378+ if diags .HasErrors () {
379+ return nil , diags
380+ }
381+ actionRefs := make ([]ActionRef , len (exprs ))
382+
383+ for i , expr := range exprs {
384+ // Since we are manually parsing the action_trigger.Actions argument, we
385+ // need to specially handle json configs, in which case the values will
386+ // be json strings rather than hcl. To simplify parsing however we will
387+ // decode the individual list elements, rather than the entire
388+ // expression.
389+ var jsDiags hcl.Diagnostics
390+ expr , jsDiags = unwrapJSONRefExpr (expr )
391+ diags = diags .Extend (jsDiags )
392+ if diags .HasErrors () {
393+ continue
394+ }
395+ actionRefs [i ] = ActionRef {
396+ Expr : expr ,
397+ Range : expr .Range (),
398+ }
399+
400+ refs , refDiags := langrefs .ReferencesInExpr (addrs .ParseRef , expr )
401+ for _ , diag := range refDiags {
402+ severity := hcl .DiagError
403+ if diag .Severity () == tfdiags .Warning {
404+ severity = hcl .DiagWarning
405+ }
406+
407+ diags = append (diags , & hcl.Diagnostic {
408+ Severity : severity ,
409+ Summary : diag .Description ().Summary ,
410+ Detail : diag .Description ().Detail ,
411+ Subject : expr .Range ().Ptr (),
412+ })
413+ }
414+
415+ if refDiags .HasErrors () {
416+ continue
417+ }
418+
419+ actionCount := 0
420+ for _ , ref := range refs {
421+ switch ref .Subject .(type ) {
422+ case addrs.Action , addrs.ActionInstance :
423+ actionCount ++
424+ case addrs.ModuleCall , addrs.ModuleCallInstance , addrs.ModuleCallInstanceOutput :
425+ diags = append (diags , & hcl.Diagnostic {
426+ Severity : hcl .DiagError ,
427+ Summary : "Invalid reference to action outside this module" ,
428+ Detail : "Actions can only be referenced in the module they are declared in." ,
429+ Subject : expr .Range ().Ptr (),
430+ })
431+ continue
432+ case addrs.Resource , addrs.ResourceInstance :
433+ // definitely not an action
434+ diags = append (diags , invalidActionDiag (expr .Range ().Ptr ()))
435+ continue
436+ default :
437+ // we've checked what we can
438+ }
439+ }
440+
441+ switch {
442+ case actionCount == 0 :
443+ diags = append (diags , & hcl.Diagnostic {
444+ Severity : hcl .DiagError ,
445+ Summary : "No actions specified" ,
446+ Detail : "At least one action must be specified for an action_trigger." ,
447+ Subject : expr .Range ().Ptr (),
448+ })
449+ case actionCount > 1 :
450+ diags = append (diags , & hcl.Diagnostic {
451+ Severity : hcl .DiagError ,
452+ Summary : "Invalid action expression" ,
453+ Detail : "Multiple action references in actions expression." ,
454+ Subject : expr .Range ().Ptr (),
455+ })
456+ }
457+
458+ }
459+
460+ return actionRefs , diags
461+ }
462+
463+ // decodeLinkedResources decodes and does basic validation of an Action's
464+ // LinkedResources.
465+ func decodeLinkedResources (expr hcl.Expression ) ([]hcl.Expression , hcl.Diagnostics ) {
466+ exprs , diags := hcl .ExprList (expr )
467+ if diags .HasErrors () {
468+ return nil , diags
469+ }
470+
471+ for i , expr := range exprs {
472+ // We are manually parsing config, so we need to handle json configs, in
473+ // which case the values will be json strings rather than hcl.
474+ var jsDiags hcl.Diagnostics
475+ expr , jsDiags = unwrapJSONRefExpr (expr )
476+ diags = diags .Extend (jsDiags )
477+ if diags .HasErrors () {
478+ continue
479+ }
480+
481+ // re-assign the value in case it was modified by unwrapJSONRefExpr
482+ exprs [i ] = expr
483+
484+ _ , lrDiags := decodeUnwrappedLinkedResource (expr , invalidLinkedResourcesDiag )
485+ diags = append (diags , lrDiags ... )
486+
487+ }
488+
489+ return exprs , diags
490+ }
491+
492+ func decodeLinkedResource (expr hcl.Expression ) (hcl.Expression , hcl.Diagnostics ) {
493+ // Handle possible json configs
494+ expr , diags := unwrapJSONRefExpr (expr )
495+ if diags .HasErrors () {
496+ return expr , diags
497+ }
498+
499+ return decodeUnwrappedLinkedResource (expr , invalidLinkedResourceDiag )
500+ }
501+
502+ func decodeUnwrappedLinkedResource (expr hcl.Expression , diagFunc diagFn ) (hcl.Expression , hcl.Diagnostics ) {
503+ var diags hcl.Diagnostics
504+
505+ refs , refDiags := langrefs .ReferencesInExpr (addrs .ParseRef , expr )
506+ for _ , diag := range refDiags {
507+ severity := hcl .DiagError
508+ if diag .Severity () == tfdiags .Warning {
509+ severity = hcl .DiagWarning
510+ }
511+
512+ diags = append (diags , & hcl.Diagnostic {
513+ Severity : severity ,
514+ Summary : diag .Description ().Summary ,
515+ Detail : diag .Description ().Detail ,
516+ Subject : expr .Range ().Ptr (),
517+ })
518+ }
519+
520+ if refDiags .HasErrors () {
521+ return expr , diags
522+ }
523+
524+ resourceCount := 0
525+ for _ , ref := range refs {
526+ switch sub := ref .Subject .(type ) {
527+ case addrs.ResourceInstance :
528+ if sub .Resource .Mode == addrs .ManagedResourceMode {
529+ diags = append (diags , diagFunc (expr .Range ().Ptr ()))
530+ } else {
531+ resourceCount ++
532+ }
533+ case addrs.Resource :
534+ if sub .Mode != addrs .ManagedResourceMode {
535+ diags = append (diags , diagFunc (expr .Range ().Ptr ()))
536+ } else {
537+ resourceCount ++
538+ }
539+ case addrs.ModuleCall , addrs.ModuleCallInstance , addrs.ModuleCallInstanceOutput :
540+ diags = append (diags , diagFunc (expr .Range ().Ptr ()))
541+ default :
542+ // we've checked what we can without evaluating references!
543+ }
544+ }
545+
546+ switch {
547+ case resourceCount == 0 :
548+ diags = append (diags , & hcl.Diagnostic {
549+ Severity : hcl .DiagError ,
550+ Summary : "Invalid linked_resource expression" ,
551+ Detail : "Missing resource reference in linked_resource expression." ,
552+ Subject : expr .Range ().Ptr (),
553+ })
554+ case resourceCount > 1 :
555+ diags = append (diags , & hcl.Diagnostic {
556+ Severity : hcl .DiagError ,
557+ Summary : "Invalid linked_resource expression" ,
558+ Detail : "Multiple resource references in linked_resource expression." ,
559+ Subject : expr .Range ().Ptr (),
560+ })
561+ }
562+
563+ return expr , diags
564+ }
0 commit comments