6 "github.com/hashicorp/hcl2/hcl"
7 "github.com/zclconf/go-cty/cty"
10 // expandBody wraps another hcl.Body and expands any "dynamic" blocks found
11 // inside whenever Content or PartialContent is called.
12 type expandBody struct {
14 forEachCtx *hcl.EvalContext
15 iteration *iteration // non-nil if we're nested inside another "dynamic" block
17 // These are used with PartialContent to produce a "remaining items"
18 // body to return. They are nil on all bodies fresh out of the transformer.
20 // Note that this is re-implemented here rather than delegating to the
21 // existing support required by the underlying body because we need to
22 // retain access to the entire original body on subsequent decode operations
23 // so we can retain any "dynamic" blocks for types we didn't take consume
25 hiddenAttrs map[string]struct{}
26 hiddenBlocks map[string]hcl.BlockHeaderSchema
29 func (b *expandBody) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
30 extSchema := b.extendSchema(schema)
31 rawContent, diags := b.original.Content(extSchema)
33 blocks, blockDiags := b.expandBlocks(schema, rawContent.Blocks, false)
34 diags = append(diags, blockDiags...)
35 attrs := b.prepareAttributes(rawContent.Attributes)
37 content := &hcl.BodyContent{
40 MissingItemRange: b.original.MissingItemRange(),
46 func (b *expandBody) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
47 extSchema := b.extendSchema(schema)
48 rawContent, _, diags := b.original.PartialContent(extSchema)
49 // We discard the "remain" argument above because we're going to construct
50 // our own remain that also takes into account remaining "dynamic" blocks.
52 blocks, blockDiags := b.expandBlocks(schema, rawContent.Blocks, true)
53 diags = append(diags, blockDiags...)
54 attrs := b.prepareAttributes(rawContent.Attributes)
56 content := &hcl.BodyContent{
59 MissingItemRange: b.original.MissingItemRange(),
62 remain := &expandBody{
64 forEachCtx: b.forEachCtx,
65 iteration: b.iteration,
66 hiddenAttrs: make(map[string]struct{}),
67 hiddenBlocks: make(map[string]hcl.BlockHeaderSchema),
69 for name := range b.hiddenAttrs {
70 remain.hiddenAttrs[name] = struct{}{}
72 for typeName, blockS := range b.hiddenBlocks {
73 remain.hiddenBlocks[typeName] = blockS
75 for _, attrS := range schema.Attributes {
76 remain.hiddenAttrs[attrS.Name] = struct{}{}
78 for _, blockS := range schema.Blocks {
79 remain.hiddenBlocks[blockS.Type] = blockS
82 return content, remain, diags
85 func (b *expandBody) extendSchema(schema *hcl.BodySchema) *hcl.BodySchema {
86 // We augment the requested schema to also include our special "dynamic"
87 // block type, since then we'll get instances of it interleaved with
88 // all of the literal child blocks we must also include.
89 extSchema := &hcl.BodySchema{
90 Attributes: schema.Attributes,
91 Blocks: make([]hcl.BlockHeaderSchema, len(schema.Blocks), len(schema.Blocks)+len(b.hiddenBlocks)+1),
93 copy(extSchema.Blocks, schema.Blocks)
94 extSchema.Blocks = append(extSchema.Blocks, dynamicBlockHeaderSchema)
96 // If we have any hiddenBlocks then we also need to register those here
97 // so that a call to "Content" on the underlying body won't fail.
98 // (We'll filter these out again once we process the result of either
99 // Content or PartialContent.)
100 for _, blockS := range b.hiddenBlocks {
101 extSchema.Blocks = append(extSchema.Blocks, blockS)
104 // If we have any hiddenAttrs then we also need to register these, for
105 // the same reason as we deal with hiddenBlocks above.
106 if len(b.hiddenAttrs) != 0 {
107 newAttrs := make([]hcl.AttributeSchema, len(schema.Attributes), len(schema.Attributes)+len(b.hiddenAttrs))
108 copy(newAttrs, extSchema.Attributes)
109 for name := range b.hiddenAttrs {
110 newAttrs = append(newAttrs, hcl.AttributeSchema{
115 extSchema.Attributes = newAttrs
121 func (b *expandBody) prepareAttributes(rawAttrs hcl.Attributes) hcl.Attributes {
122 if len(b.hiddenAttrs) == 0 && b.iteration == nil {
123 // Easy path: just pass through the attrs from the original body verbatim
127 // Otherwise we have some work to do: we must filter out any attributes
128 // that are hidden (since a previous PartialContent call already saw these)
129 // and wrap the expressions of the inner attributes so that they will
130 // have access to our iteration variables.
131 attrs := make(hcl.Attributes, len(rawAttrs))
132 for name, rawAttr := range rawAttrs {
133 if _, hidden := b.hiddenAttrs[name]; hidden {
136 if b.iteration != nil {
137 attr := *rawAttr // shallow copy so we can mutate it
138 attr.Expr = exprWrap{
139 Expression: attr.Expr,
144 // If we have no active iteration then no wrapping is required.
145 attrs[name] = rawAttr
151 func (b *expandBody) expandBlocks(schema *hcl.BodySchema, rawBlocks hcl.Blocks, partial bool) (hcl.Blocks, hcl.Diagnostics) {
152 var blocks hcl.Blocks
153 var diags hcl.Diagnostics
155 for _, rawBlock := range rawBlocks {
156 switch rawBlock.Type {
158 realBlockType := rawBlock.Labels[0]
159 if _, hidden := b.hiddenBlocks[realBlockType]; hidden {
163 var blockS *hcl.BlockHeaderSchema
164 for _, candidate := range schema.Blocks {
165 if candidate.Type == realBlockType {
171 // Not a block type that the caller requested.
173 diags = append(diags, &hcl.Diagnostic{
174 Severity: hcl.DiagError,
175 Summary: "Unsupported block type",
176 Detail: fmt.Sprintf("Blocks of type %q are not expected here.", realBlockType),
177 Subject: &rawBlock.LabelRanges[0],
183 spec, specDiags := b.decodeSpec(blockS, rawBlock)
184 diags = append(diags, specDiags...)
185 if specDiags.HasErrors() {
189 if spec.forEachVal.IsKnown() {
190 for it := spec.forEachVal.ElementIterator(); it.Next(); {
191 key, value := it.Element()
192 i := b.iteration.MakeChild(spec.iteratorName, key, value)
194 block, blockDiags := spec.newBlock(i, b.forEachCtx)
195 diags = append(diags, blockDiags...)
197 // Attach our new iteration context so that attributes
198 // and other nested blocks can refer to our iterator.
199 block.Body = b.expandChild(block.Body, i)
200 blocks = append(blocks, block)
204 // If our top-level iteration value isn't known then we're forced
205 // to compromise since HCL doesn't have any concept of an
206 // "unknown block". In this case then, we'll produce a single
207 // dynamic block with the iterator values set to DynamicVal,
208 // which at least makes the potential for a block visible
209 // in our result, even though it's not represented in a fully-accurate
211 i := b.iteration.MakeChild(spec.iteratorName, cty.DynamicVal, cty.DynamicVal)
212 block, blockDiags := spec.newBlock(i, b.forEachCtx)
213 diags = append(diags, blockDiags...)
215 block.Body = b.expandChild(block.Body, i)
217 // We additionally force all of the leaf attribute values
218 // in the result to be unknown so the calling application
219 // can, if necessary, use that as a heuristic to detect
220 // when a single nested block might be standing in for
221 // multiple blocks yet to be expanded. This retains the
222 // structure of the generated body but forces all of its
223 // leaf attribute values to be unknown.
224 block.Body = unknownBody{block.Body}
226 blocks = append(blocks, block)
231 if _, hidden := b.hiddenBlocks[rawBlock.Type]; !hidden {
232 // A static block doesn't create a new iteration context, but
233 // it does need to inherit _our own_ iteration context in
234 // case it contains expressions that refer to our inherited
235 // iterators, or nested "dynamic" blocks.
236 expandedBlock := *rawBlock // shallow copy
237 expandedBlock.Body = b.expandChild(rawBlock.Body, b.iteration)
238 blocks = append(blocks, &expandedBlock)
246 func (b *expandBody) expandChild(child hcl.Body, i *iteration) hcl.Body {
247 chiCtx := i.EvalContext(b.forEachCtx)
248 ret := Expand(child, chiCtx)
249 ret.(*expandBody).iteration = i
253 func (b *expandBody) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
254 // blocks aren't allowed in JustAttributes mode and this body can
255 // only produce blocks, so we'll just pass straight through to our
256 // underlying body here.
257 return b.original.JustAttributes()
260 func (b *expandBody) MissingItemRange() hcl.Range {
261 return b.original.MissingItemRange()