github.com/opentofu/opentofu@v1.7.1/internal/tofu/evaluate_triggers.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package tofu 7 8 import ( 9 "strings" 10 11 "github.com/hashicorp/hcl/v2" 12 "github.com/hashicorp/hcl/v2/hclsyntax" 13 "github.com/opentofu/opentofu/internal/addrs" 14 "github.com/opentofu/opentofu/internal/instances" 15 "github.com/opentofu/opentofu/internal/tfdiags" 16 "github.com/zclconf/go-cty/cty" 17 ) 18 19 func evalReplaceTriggeredByExpr(expr hcl.Expression, keyData instances.RepetitionData) (*addrs.Reference, tfdiags.Diagnostics) { 20 var ref *addrs.Reference 21 var diags tfdiags.Diagnostics 22 23 traversal, diags := triggersExprToTraversal(expr, keyData) 24 if diags.HasErrors() { 25 return nil, diags 26 } 27 28 // We now have a static traversal, so we can just turn it into an addrs.Reference. 29 ref, ds := addrs.ParseRef(traversal) 30 diags = diags.Append(ds) 31 32 return ref, diags 33 } 34 35 // trggersExprToTraversal takes an hcl expression limited to the syntax allowed 36 // in replace_triggered_by, and converts it to a static traversal. The 37 // RepetitionData contains the data necessary to evaluate the only allowed 38 // variables in the expression, count.index and each.key. 39 func triggersExprToTraversal(expr hcl.Expression, keyData instances.RepetitionData) (hcl.Traversal, tfdiags.Diagnostics) { 40 var trav hcl.Traversal 41 var diags tfdiags.Diagnostics 42 43 switch e := expr.(type) { 44 case *hclsyntax.RelativeTraversalExpr: 45 t, d := triggersExprToTraversal(e.Source, keyData) 46 diags = diags.Append(d) 47 trav = append(trav, t...) 48 trav = append(trav, e.Traversal...) 49 50 case *hclsyntax.ScopeTraversalExpr: 51 // a static reference, we can just append the traversal 52 trav = append(trav, e.Traversal...) 53 54 case *hclsyntax.IndexExpr: 55 // Get the collection from the index expression 56 t, d := triggersExprToTraversal(e.Collection, keyData) 57 diags = diags.Append(d) 58 if diags.HasErrors() { 59 return nil, diags 60 } 61 trav = append(trav, t...) 62 63 // The index key is the only place where we could have variables that 64 // reference count and each, so we need to parse those independently. 65 idx, hclDiags := parseIndexKeyExpr(e.Key, keyData) 66 diags = diags.Append(hclDiags) 67 68 trav = append(trav, idx) 69 70 default: 71 // Something unexpected got through config validation. We're not sure 72 // what it is, but we'll point it out in the diagnostics for the user 73 // to fix. 74 diags = diags.Append(&hcl.Diagnostic{ 75 Severity: hcl.DiagError, 76 Summary: "Invalid replace_triggered_by expression", 77 Detail: "Unexpected expression found in replace_triggered_by.", 78 Subject: e.Range().Ptr(), 79 }) 80 } 81 82 return trav, diags 83 } 84 85 // parseIndexKeyExpr takes an hcl.Expression and parses it as an index key, while 86 // evaluating any references to count.index or each.key. 87 func parseIndexKeyExpr(expr hcl.Expression, keyData instances.RepetitionData) (hcl.TraverseIndex, hcl.Diagnostics) { 88 idx := hcl.TraverseIndex{ 89 SrcRange: expr.Range(), 90 } 91 92 trav, diags := hcl.RelTraversalForExpr(expr) 93 if diags.HasErrors() { 94 return idx, diags 95 } 96 97 keyParts := []string{} 98 99 for _, t := range trav { 100 attr, ok := t.(hcl.TraverseAttr) 101 if !ok { 102 diags = append(diags, &hcl.Diagnostic{ 103 Severity: hcl.DiagError, 104 Summary: "Invalid index expression", 105 Detail: "Only constant values, count.index or each.key are allowed in index expressions.", 106 Subject: expr.Range().Ptr(), 107 }) 108 return idx, diags 109 } 110 keyParts = append(keyParts, attr.Name) 111 } 112 113 switch strings.Join(keyParts, ".") { 114 case "count.index": 115 if keyData.CountIndex == cty.NilVal { 116 diags = diags.Append(&hcl.Diagnostic{ 117 Severity: hcl.DiagError, 118 Summary: `Reference to "count" in non-counted context`, 119 Detail: `The "count" object can only be used in "resource" blocks when the "count" argument is set.`, 120 Subject: expr.Range().Ptr(), 121 }) 122 } 123 idx.Key = keyData.CountIndex 124 125 case "each.key": 126 if keyData.EachKey == cty.NilVal { 127 diags = diags.Append(&hcl.Diagnostic{ 128 Severity: hcl.DiagError, 129 Summary: `Reference to "each" in context without for_each`, 130 Detail: `The "each" object can be used only in "resource" blocks when the "for_each" argument is set.`, 131 Subject: expr.Range().Ptr(), 132 }) 133 } 134 idx.Key = keyData.EachKey 135 default: 136 // Something may have slipped through validation, probably from a json 137 // configuration. 138 diags = append(diags, &hcl.Diagnostic{ 139 Severity: hcl.DiagError, 140 Summary: "Invalid index expression", 141 Detail: "Only constant values, count.index or each.key are allowed in index expressions.", 142 Subject: expr.Range().Ptr(), 143 }) 144 } 145 146 return idx, diags 147 148 }