github.com/hashicorp/hcl/v2@v2.20.0/ext/dynblock/variables.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package dynblock 5 6 import ( 7 "github.com/hashicorp/hcl/v2" 8 "github.com/zclconf/go-cty/cty" 9 ) 10 11 // WalkVariables begins the recursive process of walking all expressions and 12 // nested blocks in the given body and its child bodies while taking into 13 // account any "dynamic" blocks. 14 // 15 // This function requires that the caller walk through the nested block 16 // structure in the given body level-by-level so that an appropriate schema 17 // can be provided at each level to inform further processing. This workflow 18 // is thus easiest to use for calling applications that have some higher-level 19 // schema representation available with which to drive this multi-step 20 // process. If your application uses the hcldec package, you may be able to 21 // use VariablesHCLDec instead for a more automatic approach. 22 func WalkVariables(body hcl.Body) WalkVariablesNode { 23 return WalkVariablesNode{ 24 body: body, 25 includeContent: true, 26 } 27 } 28 29 // WalkExpandVariables is like Variables but it includes only the variables 30 // required for successful block expansion, ignoring any variables referenced 31 // inside block contents. The result is the minimal set of all variables 32 // required for a call to Expand, excluding variables that would only be 33 // needed to subsequently call Content or PartialContent on the expanded 34 // body. 35 func WalkExpandVariables(body hcl.Body) WalkVariablesNode { 36 return WalkVariablesNode{ 37 body: body, 38 } 39 } 40 41 type WalkVariablesNode struct { 42 body hcl.Body 43 it *iteration 44 45 includeContent bool 46 } 47 48 type WalkVariablesChild struct { 49 BlockTypeName string 50 Node WalkVariablesNode 51 } 52 53 // Body returns the HCL Body associated with the child node, in case the caller 54 // wants to do some sort of inspection of it in order to decide what schema 55 // to pass to Visit. 56 // 57 // Most implementations should just fetch a fixed schema based on the 58 // BlockTypeName field and not access this. Deciding on a schema dynamically 59 // based on the body is a strange thing to do and generally necessary only if 60 // your caller is already doing other bizarre things with HCL bodies. 61 func (c WalkVariablesChild) Body() hcl.Body { 62 return c.Node.body 63 } 64 65 // Visit returns the variable traversals required for any "dynamic" blocks 66 // directly in the body associated with this node, and also returns any child 67 // nodes that must be visited in order to continue the walk. 68 // 69 // Each child node has its associated block type name given in its BlockTypeName 70 // field, which the calling application should use to determine the appropriate 71 // schema for the content of each child node and pass it to the child node's 72 // own Visit method to continue the walk recursively. 73 func (n WalkVariablesNode) Visit(schema *hcl.BodySchema) (vars []hcl.Traversal, children []WalkVariablesChild) { 74 extSchema := n.extendSchema(schema) 75 container, _, _ := n.body.PartialContent(extSchema) 76 if container == nil { 77 return vars, children 78 } 79 80 children = make([]WalkVariablesChild, 0, len(container.Blocks)) 81 82 if n.includeContent { 83 for _, attr := range container.Attributes { 84 for _, traversal := range attr.Expr.Variables() { 85 var ours, inherited bool 86 if n.it != nil { 87 ours = traversal.RootName() == n.it.IteratorName 88 _, inherited = n.it.Inherited[traversal.RootName()] 89 } 90 91 if !(ours || inherited) { 92 vars = append(vars, traversal) 93 } 94 } 95 } 96 } 97 98 for _, block := range container.Blocks { 99 switch block.Type { 100 101 case "dynamic": 102 blockTypeName := block.Labels[0] 103 inner, _, _ := block.Body.PartialContent(variableDetectionInnerSchema) 104 if inner == nil { 105 continue 106 } 107 108 iteratorName := blockTypeName 109 if attr, exists := inner.Attributes["iterator"]; exists { 110 iterTraversal, _ := hcl.AbsTraversalForExpr(attr.Expr) 111 if len(iterTraversal) == 0 { 112 // Ignore this invalid dynamic block, since it'll produce 113 // an error if someone tries to extract content from it 114 // later anyway. 115 continue 116 } 117 iteratorName = iterTraversal.RootName() 118 } 119 blockIt := n.it.MakeChild(iteratorName, cty.DynamicVal, cty.DynamicVal) 120 121 if attr, exists := inner.Attributes["for_each"]; exists { 122 // Filter out iterator names inherited from parent blocks 123 for _, traversal := range attr.Expr.Variables() { 124 if _, inherited := blockIt.Inherited[traversal.RootName()]; !inherited { 125 vars = append(vars, traversal) 126 } 127 } 128 } 129 if attr, exists := inner.Attributes["labels"]; exists { 130 // Filter out both our own iterator name _and_ those inherited 131 // from parent blocks, since we provide _both_ of these to the 132 // label expressions. 133 for _, traversal := range attr.Expr.Variables() { 134 ours := traversal.RootName() == iteratorName 135 _, inherited := blockIt.Inherited[traversal.RootName()] 136 137 if !(ours || inherited) { 138 vars = append(vars, traversal) 139 } 140 } 141 } 142 143 for _, contentBlock := range inner.Blocks { 144 // We only request "content" blocks in our schema, so we know 145 // any blocks we find here will be content blocks. We require 146 // exactly one content block for actual expansion, but we'll 147 // be more liberal here so that callers can still collect 148 // variables from erroneous "dynamic" blocks. 149 children = append(children, WalkVariablesChild{ 150 BlockTypeName: blockTypeName, 151 Node: WalkVariablesNode{ 152 body: contentBlock.Body, 153 it: blockIt, 154 includeContent: n.includeContent, 155 }, 156 }) 157 } 158 159 default: 160 children = append(children, WalkVariablesChild{ 161 BlockTypeName: block.Type, 162 Node: WalkVariablesNode{ 163 body: block.Body, 164 it: n.it, 165 includeContent: n.includeContent, 166 }, 167 }) 168 169 } 170 } 171 172 return vars, children 173 } 174 175 func (n WalkVariablesNode) extendSchema(schema *hcl.BodySchema) *hcl.BodySchema { 176 // We augment the requested schema to also include our special "dynamic" 177 // block type, since then we'll get instances of it interleaved with 178 // all of the literal child blocks we must also include. 179 extSchema := &hcl.BodySchema{ 180 Attributes: schema.Attributes, 181 Blocks: make([]hcl.BlockHeaderSchema, len(schema.Blocks), len(schema.Blocks)+1), 182 } 183 copy(extSchema.Blocks, schema.Blocks) 184 extSchema.Blocks = append(extSchema.Blocks, dynamicBlockHeaderSchema) 185 186 return extSchema 187 } 188 189 // This is a more relaxed schema than what's in schema.go, since we 190 // want to maximize the amount of variables we can find even if there 191 // are erroneous blocks. 192 var variableDetectionInnerSchema = &hcl.BodySchema{ 193 Attributes: []hcl.AttributeSchema{ 194 { 195 Name: "for_each", 196 Required: false, 197 }, 198 { 199 Name: "labels", 200 Required: false, 201 }, 202 { 203 Name: "iterator", 204 Required: false, 205 }, 206 }, 207 Blocks: []hcl.BlockHeaderSchema{ 208 { 209 Type: "content", 210 }, 211 }, 212 }