github.com/terraform-linters/tflint@v0.51.2-0.20240520175844-3750771571b6/terraform/tfhcl/expand_body.go (about) 1 package tfhcl 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/hcl/v2" 7 "github.com/terraform-linters/tflint-plugin-sdk/hclext" 8 "github.com/zclconf/go-cty/cty" 9 ) 10 11 // expandBody wraps another hcl.Body and expands any "dynamic" blocks, count/for-each 12 // resources found inside whenever Content or PartialContent is called. 13 type expandBody struct { 14 original hcl.Body 15 ctx *hcl.EvalContext 16 dynamicIteration *dynamicIteration // non-nil if we're nested inside a "dynamic" block 17 metaArgIteration *metaArgIteration // non-nil if we're nested inside a block with meta-arguments 18 19 // These are used with PartialContent to produce a "remaining items" 20 // body to return. They are nil on all bodies fresh out of the transformer. 21 // 22 // Note that this is re-implemented here rather than delegating to the 23 // existing support required by the underlying body because we need to 24 // retain access to the entire original body on subsequent decode operations 25 // so we can retain any "dynamic" blocks for types we didn't take consume 26 // on the first pass. 27 hiddenAttrs map[string]struct{} 28 hiddenBlocks map[string]hcl.BlockHeaderSchema 29 } 30 31 func (b *expandBody) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) { 32 extSchema := b.extendSchema(schema) 33 rawContent, diags := b.original.Content(extSchema) 34 35 blocks, blockDiags := b.expandBlocks(schema, rawContent.Blocks, false) 36 diags = append(diags, blockDiags...) 37 attrs, attrDiags := b.prepareAttributes(rawContent.Attributes) 38 diags = append(diags, attrDiags...) 39 40 content := &hcl.BodyContent{ 41 Attributes: attrs, 42 Blocks: blocks, 43 MissingItemRange: b.original.MissingItemRange(), 44 } 45 46 return content, diags 47 } 48 49 func (b *expandBody) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) { 50 extSchema := b.extendSchema(schema) 51 rawContent, _, diags := b.original.PartialContent(extSchema) 52 // We discard the "remain" argument above because we're going to construct 53 // our own remain that also takes into account remaining "dynamic" blocks. 54 55 blocks, blockDiags := b.expandBlocks(schema, rawContent.Blocks, true) 56 diags = append(diags, blockDiags...) 57 attrs, attrDiags := b.prepareAttributes(rawContent.Attributes) 58 diags = append(diags, attrDiags...) 59 60 content := &hcl.BodyContent{ 61 Attributes: attrs, 62 Blocks: blocks, 63 MissingItemRange: b.original.MissingItemRange(), 64 } 65 66 remain := &expandBody{ 67 original: b.original, 68 ctx: b.ctx, 69 dynamicIteration: b.dynamicIteration, 70 metaArgIteration: b.metaArgIteration, 71 hiddenAttrs: make(map[string]struct{}), 72 hiddenBlocks: make(map[string]hcl.BlockHeaderSchema), 73 } 74 for name := range b.hiddenAttrs { 75 remain.hiddenAttrs[name] = struct{}{} 76 } 77 for typeName, blockS := range b.hiddenBlocks { 78 remain.hiddenBlocks[typeName] = blockS 79 } 80 for _, attrS := range schema.Attributes { 81 remain.hiddenAttrs[attrS.Name] = struct{}{} 82 } 83 for _, blockS := range schema.Blocks { 84 remain.hiddenBlocks[blockS.Type] = blockS 85 } 86 87 return content, remain, diags 88 } 89 90 func (b *expandBody) extendSchema(schema *hcl.BodySchema) *hcl.BodySchema { 91 // We augment the requested schema to also include our special "dynamic" 92 // block type, since then we'll get instances of it interleaved with 93 // all of the literal child blocks we must also include. 94 extSchema := &hcl.BodySchema{ 95 Attributes: schema.Attributes, 96 Blocks: make([]hcl.BlockHeaderSchema, len(schema.Blocks), len(schema.Blocks)+len(b.hiddenBlocks)+1), 97 } 98 copy(extSchema.Blocks, schema.Blocks) 99 extSchema.Blocks = append(extSchema.Blocks, dynamicBlockHeaderSchema) 100 101 // If we have any hiddenBlocks then we also need to register those here 102 // so that a call to "Content" on the underlying body won't fail. 103 // (We'll filter these out again once we process the result of either 104 // Content or PartialContent.) 105 for _, blockS := range b.hiddenBlocks { 106 extSchema.Blocks = append(extSchema.Blocks, blockS) 107 } 108 109 // If we have any hiddenAttrs then we also need to register these, for 110 // the same reason as we deal with hiddenBlocks above. 111 if len(b.hiddenAttrs) != 0 { 112 newAttrs := make([]hcl.AttributeSchema, len(schema.Attributes), len(schema.Attributes)+len(b.hiddenAttrs)) 113 copy(newAttrs, extSchema.Attributes) 114 for name := range b.hiddenAttrs { 115 newAttrs = append(newAttrs, hcl.AttributeSchema{ 116 Name: name, 117 Required: false, 118 }) 119 } 120 extSchema.Attributes = newAttrs 121 } 122 123 return extSchema 124 } 125 126 func (b *expandBody) prepareAttributes(rawAttrs hcl.Attributes) (hcl.Attributes, hcl.Diagnostics) { 127 var diags hcl.Diagnostics 128 129 if len(b.hiddenAttrs) == 0 && b.dynamicIteration == nil && b.metaArgIteration == nil { 130 // Easy path: just pass through the attrs from the original body verbatim 131 return rawAttrs, diags 132 } 133 134 // Otherwise we have some work to do: we must filter out any attributes 135 // that are hidden (since a previous PartialContent call already saw these) 136 // and wrap the expressions of the inner attributes so that they will 137 // have access to our iteration variables. 138 attrs := make(hcl.Attributes, len(rawAttrs)) 139 for name, rawAttr := range rawAttrs { 140 if _, hidden := b.hiddenAttrs[name]; hidden { 141 continue 142 } 143 if b.dynamicIteration != nil || b.metaArgIteration != nil { 144 attr := *rawAttr // shallow copy so we can mutate it 145 expr := exprWrap{ 146 Expression: attr.Expr, 147 di: b.dynamicIteration, 148 mi: b.metaArgIteration, 149 } 150 // Unlike hcl/ext/dynblock, wrapped expressions are evaluated immediately. 151 // The result is bound to the expression and can be accessed without 152 // the iterator context. 153 val, evalDiags := expr.Value(b.ctx) 154 if evalDiags.HasErrors() { 155 diags = append(diags, evalDiags...) 156 continue 157 } 158 // Marked values (e.g. sensitive values) are unbound for serialization. 159 if !val.ContainsMarked() { 160 attr.Expr = hclext.BindValue(val, expr) 161 } 162 attrs[name] = &attr 163 } else { 164 // If we have no active iteration then no wrapping is required. 165 attrs[name] = rawAttr 166 } 167 } 168 return attrs, diags 169 } 170 171 func (b *expandBody) expandBlocks(schema *hcl.BodySchema, rawBlocks hcl.Blocks, partial bool) (hcl.Blocks, hcl.Diagnostics) { 172 var blocks hcl.Blocks 173 var diags hcl.Diagnostics 174 175 for _, rawBlock := range rawBlocks { 176 switch rawBlock.Type { 177 case "dynamic": 178 expandedBlocks, expandDiags := b.expandDynamicBlock(schema, rawBlock, partial) 179 blocks = append(blocks, expandedBlocks...) 180 diags = append(diags, expandDiags...) 181 182 case "resource", "module": 183 expandedBlocks, expandDiags := b.expandMetaArgBlock(schema, rawBlock) 184 blocks = append(blocks, expandedBlocks...) 185 diags = append(diags, expandDiags...) 186 187 default: 188 if _, hidden := b.hiddenBlocks[rawBlock.Type]; !hidden { 189 blocks = append(blocks, b.expandStaticBlock(rawBlock)) 190 } 191 } 192 } 193 194 return blocks, diags 195 } 196 197 func (b *expandBody) expandDynamicBlock(schema *hcl.BodySchema, rawBlock *hcl.Block, partial bool) (hcl.Blocks, hcl.Diagnostics) { 198 var diags hcl.Diagnostics 199 200 realBlockType := rawBlock.Labels[0] 201 if _, hidden := b.hiddenBlocks[realBlockType]; hidden { 202 return hcl.Blocks{}, diags 203 } 204 205 var blockS *hcl.BlockHeaderSchema 206 for _, candidate := range schema.Blocks { 207 if candidate.Type == realBlockType { 208 blockS = &candidate 209 break 210 } 211 } 212 if blockS == nil { 213 // Not a block type that the caller requested. 214 if !partial { 215 diags = append(diags, &hcl.Diagnostic{ 216 Severity: hcl.DiagError, 217 Summary: "Unsupported block type", 218 Detail: fmt.Sprintf("Blocks of type %q are not expected here.", realBlockType), 219 Subject: &rawBlock.LabelRanges[0], 220 }) 221 } 222 return hcl.Blocks{}, diags 223 } 224 225 spec, specDiags := b.decodeDynamicSpec(blockS, rawBlock) 226 diags = append(diags, specDiags...) 227 if specDiags.HasErrors() { 228 return hcl.Blocks{}, diags 229 } 230 231 if !spec.forEachVal.IsKnown() { 232 // If for_each is unknown, no blocks are returned 233 return hcl.Blocks{}, diags 234 } 235 236 var blocks hcl.Blocks 237 238 for it := spec.forEachVal.ElementIterator(); it.Next(); { 239 key, value := it.Element() 240 i := b.dynamicIteration.MakeChild(spec.iteratorName, key, value) 241 242 block, blockDiags := spec.newBlock(i, b.ctx) 243 diags = append(diags, blockDiags...) 244 if block != nil { 245 // Attach our new iteration context so that attributes 246 // and other nested blocks can refer to our iterator. 247 block.Body = b.expandChild(block.Body, i, b.metaArgIteration) 248 blocks = append(blocks, block) 249 } 250 } 251 return blocks, diags 252 } 253 254 func (b *expandBody) expandMetaArgBlock(schema *hcl.BodySchema, rawBlock *hcl.Block) (hcl.Blocks, hcl.Diagnostics) { 255 var diags hcl.Diagnostics 256 257 if _, hidden := b.hiddenBlocks[rawBlock.Type]; hidden { 258 return hcl.Blocks{}, diags 259 } 260 261 spec, specDiags := b.decodeMetaArgSpec(rawBlock) 262 diags = append(diags, specDiags...) 263 if specDiags.HasErrors() { 264 return hcl.Blocks{}, diags 265 } 266 267 //// count attribute 268 269 if spec.countSet { 270 if !spec.countVal.IsKnown() { 271 // If count is unknown, no blocks are returned 272 return hcl.Blocks{}, diags 273 } 274 275 var blocks hcl.Blocks 276 277 for idx := 0; idx < spec.countNum; idx++ { 278 i := MakeCountIteration(cty.NumberIntVal(int64(idx))) 279 280 expandedBlock := *rawBlock // shallow copy 281 expandedBlock.Body = b.expandChild(rawBlock.Body, b.dynamicIteration, i) 282 blocks = append(blocks, &expandedBlock) 283 } 284 285 return blocks, diags 286 } 287 288 //// for_each attribute 289 290 if spec.forEachSet { 291 if !spec.forEachVal.IsKnown() { 292 // If for_each is unknown, no blocks are returned 293 return hcl.Blocks{}, diags 294 } 295 296 var blocks hcl.Blocks 297 298 for it := spec.forEachVal.ElementIterator(); it.Next(); { 299 i := MakeForEachIteration(it.Element()) 300 301 expandedBlock := *rawBlock // shallow copy 302 expandedBlock.Body = b.expandChild(rawBlock.Body, b.dynamicIteration, i) 303 blocks = append(blocks, &expandedBlock) 304 } 305 306 return blocks, diags 307 } 308 309 //// Neither count/for_each 310 311 return hcl.Blocks{b.expandStaticBlock(rawBlock)}, diags 312 } 313 314 func (b *expandBody) expandStaticBlock(rawBlock *hcl.Block) *hcl.Block { 315 // A static block doesn't create a new iteration context, but 316 // it does need to inherit _our own_ iteration context in 317 // case it contains expressions that refer to our inherited 318 // iterators, or nested "dynamic" blocks. 319 expandedBlock := *rawBlock 320 expandedBlock.Body = b.expandChild(rawBlock.Body, b.dynamicIteration, b.metaArgIteration) 321 return &expandedBlock 322 } 323 324 func (b *expandBody) expandChild(child hcl.Body, i *dynamicIteration, mi *metaArgIteration) hcl.Body { 325 chiCtx := i.EvalContext(mi.EvalContext(b.ctx)) 326 ret := Expand(child, chiCtx) 327 ret.(*expandBody).dynamicIteration = i 328 ret.(*expandBody).metaArgIteration = mi 329 return ret 330 } 331 332 func (b *expandBody) JustAttributes() (hcl.Attributes, hcl.Diagnostics) { 333 // blocks aren't allowed in JustAttributes mode and this body can 334 // only produce blocks, so we'll just pass straight through to our 335 // underlying body here. 336 return b.original.JustAttributes() 337 } 338 339 func (b *expandBody) MissingItemRange() hcl.Range { 340 return b.original.MissingItemRange() 341 }