github.com/hashicorp/hcl/v2@v2.20.0/ext/dynblock/expand_body.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package dynblock 5 6 import ( 7 "fmt" 8 9 "github.com/hashicorp/hcl/v2" 10 "github.com/zclconf/go-cty/cty" 11 ) 12 13 // expandBody wraps another hcl.Body and expands any "dynamic" blocks found 14 // inside whenever Content or PartialContent is called. 15 type expandBody struct { 16 original hcl.Body 17 forEachCtx *hcl.EvalContext 18 iteration *iteration // non-nil if we're nested inside another "dynamic" block 19 20 checkForEach []func(cty.Value, hcl.Expression, *hcl.EvalContext) hcl.Diagnostics 21 22 // These are used with PartialContent to produce a "remaining items" 23 // body to return. They are nil on all bodies fresh out of the transformer. 24 // 25 // Note that this is re-implemented here rather than delegating to the 26 // existing support required by the underlying body because we need to 27 // retain access to the entire original body on subsequent decode operations 28 // so we can retain any "dynamic" blocks for types we didn't take consume 29 // on the first pass. 30 hiddenAttrs map[string]struct{} 31 hiddenBlocks map[string]hcl.BlockHeaderSchema 32 } 33 34 func (b *expandBody) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) { 35 extSchema := b.extendSchema(schema) 36 rawContent, diags := b.original.Content(extSchema) 37 38 blocks, blockDiags := b.expandBlocks(schema, rawContent.Blocks, false) 39 diags = append(diags, blockDiags...) 40 attrs := b.prepareAttributes(rawContent.Attributes) 41 42 content := &hcl.BodyContent{ 43 Attributes: attrs, 44 Blocks: blocks, 45 MissingItemRange: b.original.MissingItemRange(), 46 } 47 48 return content, diags 49 } 50 51 func (b *expandBody) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) { 52 extSchema := b.extendSchema(schema) 53 rawContent, _, diags := b.original.PartialContent(extSchema) 54 // We discard the "remain" argument above because we're going to construct 55 // our own remain that also takes into account remaining "dynamic" blocks. 56 57 blocks, blockDiags := b.expandBlocks(schema, rawContent.Blocks, true) 58 diags = append(diags, blockDiags...) 59 attrs := b.prepareAttributes(rawContent.Attributes) 60 61 content := &hcl.BodyContent{ 62 Attributes: attrs, 63 Blocks: blocks, 64 MissingItemRange: b.original.MissingItemRange(), 65 } 66 67 remain := &expandBody{ 68 original: b.original, 69 forEachCtx: b.forEachCtx, 70 iteration: b.iteration, 71 checkForEach: b.checkForEach, 72 hiddenAttrs: make(map[string]struct{}), 73 hiddenBlocks: make(map[string]hcl.BlockHeaderSchema), 74 } 75 for name := range b.hiddenAttrs { 76 remain.hiddenAttrs[name] = struct{}{} 77 } 78 for typeName, blockS := range b.hiddenBlocks { 79 remain.hiddenBlocks[typeName] = blockS 80 } 81 for _, attrS := range schema.Attributes { 82 remain.hiddenAttrs[attrS.Name] = struct{}{} 83 } 84 for _, blockS := range schema.Blocks { 85 remain.hiddenBlocks[blockS.Type] = blockS 86 } 87 88 return content, remain, diags 89 } 90 91 func (b *expandBody) extendSchema(schema *hcl.BodySchema) *hcl.BodySchema { 92 // We augment the requested schema to also include our special "dynamic" 93 // block type, since then we'll get instances of it interleaved with 94 // all of the literal child blocks we must also include. 95 extSchema := &hcl.BodySchema{ 96 Attributes: schema.Attributes, 97 Blocks: make([]hcl.BlockHeaderSchema, len(schema.Blocks), len(schema.Blocks)+len(b.hiddenBlocks)+1), 98 } 99 copy(extSchema.Blocks, schema.Blocks) 100 extSchema.Blocks = append(extSchema.Blocks, dynamicBlockHeaderSchema) 101 102 // If we have any hiddenBlocks then we also need to register those here 103 // so that a call to "Content" on the underlying body won't fail. 104 // (We'll filter these out again once we process the result of either 105 // Content or PartialContent.) 106 for _, blockS := range b.hiddenBlocks { 107 extSchema.Blocks = append(extSchema.Blocks, blockS) 108 } 109 110 // If we have any hiddenAttrs then we also need to register these, for 111 // the same reason as we deal with hiddenBlocks above. 112 if len(b.hiddenAttrs) != 0 { 113 newAttrs := make([]hcl.AttributeSchema, len(schema.Attributes), len(schema.Attributes)+len(b.hiddenAttrs)) 114 copy(newAttrs, extSchema.Attributes) 115 for name := range b.hiddenAttrs { 116 newAttrs = append(newAttrs, hcl.AttributeSchema{ 117 Name: name, 118 Required: false, 119 }) 120 } 121 extSchema.Attributes = newAttrs 122 } 123 124 return extSchema 125 } 126 127 func (b *expandBody) prepareAttributes(rawAttrs hcl.Attributes) hcl.Attributes { 128 if len(b.hiddenAttrs) == 0 && b.iteration == nil { 129 // Easy path: just pass through the attrs from the original body verbatim 130 return rawAttrs 131 } 132 133 // Otherwise we have some work to do: we must filter out any attributes 134 // that are hidden (since a previous PartialContent call already saw these) 135 // and wrap the expressions of the inner attributes so that they will 136 // have access to our iteration variables. 137 attrs := make(hcl.Attributes, len(rawAttrs)) 138 for name, rawAttr := range rawAttrs { 139 if _, hidden := b.hiddenAttrs[name]; hidden { 140 continue 141 } 142 if b.iteration != nil { 143 attr := *rawAttr // shallow copy so we can mutate it 144 attr.Expr = exprWrap{ 145 Expression: attr.Expr, 146 i: b.iteration, 147 } 148 attrs[name] = &attr 149 } else { 150 // If we have no active iteration then no wrapping is required. 151 attrs[name] = rawAttr 152 } 153 } 154 return attrs 155 } 156 157 func (b *expandBody) expandBlocks(schema *hcl.BodySchema, rawBlocks hcl.Blocks, partial bool) (hcl.Blocks, hcl.Diagnostics) { 158 var blocks hcl.Blocks 159 var diags hcl.Diagnostics 160 161 for _, rawBlock := range rawBlocks { 162 switch rawBlock.Type { 163 case "dynamic": 164 realBlockType := rawBlock.Labels[0] 165 if _, hidden := b.hiddenBlocks[realBlockType]; hidden { 166 continue 167 } 168 169 var blockS *hcl.BlockHeaderSchema 170 for _, candidate := range schema.Blocks { 171 if candidate.Type == realBlockType { 172 blockS = &candidate 173 break 174 } 175 } 176 if blockS == nil { 177 // Not a block type that the caller requested. 178 if !partial { 179 diags = append(diags, &hcl.Diagnostic{ 180 Severity: hcl.DiagError, 181 Summary: "Unsupported block type", 182 Detail: fmt.Sprintf("Blocks of type %q are not expected here.", realBlockType), 183 Subject: &rawBlock.LabelRanges[0], 184 }) 185 } 186 continue 187 } 188 189 spec, specDiags := b.decodeSpec(blockS, rawBlock) 190 diags = append(diags, specDiags...) 191 if specDiags.HasErrors() { 192 continue 193 } 194 195 if spec.forEachVal.IsKnown() { 196 for it := spec.forEachVal.ElementIterator(); it.Next(); { 197 key, value := it.Element() 198 i := b.iteration.MakeChild(spec.iteratorName, key, value) 199 200 block, blockDiags := spec.newBlock(i, b.forEachCtx) 201 diags = append(diags, blockDiags...) 202 if block != nil { 203 // Attach our new iteration context so that attributes 204 // and other nested blocks can refer to our iterator. 205 block.Body = b.expandChild(block.Body, i) 206 blocks = append(blocks, block) 207 } 208 } 209 } else { 210 // If our top-level iteration value isn't known then we 211 // substitute an unknownBody, which will cause the entire block 212 // to evaluate to an unknown value. 213 i := b.iteration.MakeChild(spec.iteratorName, cty.DynamicVal, cty.DynamicVal) 214 block, blockDiags := spec.newBlock(i, b.forEachCtx) 215 diags = append(diags, blockDiags...) 216 if block != nil { 217 block.Body = unknownBody{b.expandChild(block.Body, i)} 218 blocks = append(blocks, block) 219 } 220 } 221 222 default: 223 if _, hidden := b.hiddenBlocks[rawBlock.Type]; !hidden { 224 // A static block doesn't create a new iteration context, but 225 // it does need to inherit _our own_ iteration context in 226 // case it contains expressions that refer to our inherited 227 // iterators, or nested "dynamic" blocks. 228 expandedBlock := *rawBlock // shallow copy 229 expandedBlock.Body = b.expandChild(rawBlock.Body, b.iteration) 230 blocks = append(blocks, &expandedBlock) 231 } 232 } 233 } 234 235 return blocks, diags 236 } 237 238 func (b *expandBody) expandChild(child hcl.Body, i *iteration) hcl.Body { 239 chiCtx := i.EvalContext(b.forEachCtx) 240 ret := Expand(child, chiCtx) 241 ret.(*expandBody).iteration = i 242 ret.(*expandBody).checkForEach = b.checkForEach 243 return ret 244 } 245 246 func (b *expandBody) JustAttributes() (hcl.Attributes, hcl.Diagnostics) { 247 // blocks aren't allowed in JustAttributes mode and this body can 248 // only produce blocks, so we'll just pass straight through to our 249 // underlying body here. 250 return b.original.JustAttributes() 251 } 252 253 func (b *expandBody) MissingItemRange() hcl.Range { 254 return b.original.MissingItemRange() 255 }