aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/hcl2/ext/dynblock/expand_body.go
blob: dd30822398378f41860ff8d0791d2cd0978cacf0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
package dynblock

import (
	"fmt"

	"github.com/hashicorp/hcl2/hcl"
	"github.com/zclconf/go-cty/cty"
)

// expandBody wraps another hcl.Body and expands any "dynamic" blocks found
// inside whenever Content or PartialContent is called.
type expandBody struct {
	original   hcl.Body
	forEachCtx *hcl.EvalContext
	iteration  *iteration // non-nil if we're nested inside another "dynamic" block

	// These are used with PartialContent to produce a "remaining items"
	// body to return. They are nil on all bodies fresh out of the transformer.
	//
	// Note that this is re-implemented here rather than delegating to the
	// existing support required by the underlying body because we need to
	// retain access to the entire original body on subsequent decode operations
	// so we can retain any "dynamic" blocks for types we didn't take consume
	// on the first pass.
	hiddenAttrs  map[string]struct{}
	hiddenBlocks map[string]hcl.BlockHeaderSchema
}

func (b *expandBody) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
	extSchema := b.extendSchema(schema)
	rawContent, diags := b.original.Content(extSchema)

	blocks, blockDiags := b.expandBlocks(schema, rawContent.Blocks, false)
	diags = append(diags, blockDiags...)
	attrs := b.prepareAttributes(rawContent.Attributes)

	content := &hcl.BodyContent{
		Attributes:       attrs,
		Blocks:           blocks,
		MissingItemRange: b.original.MissingItemRange(),
	}

	return content, diags
}

func (b *expandBody) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
	extSchema := b.extendSchema(schema)
	rawContent, _, diags := b.original.PartialContent(extSchema)
	// We discard the "remain" argument above because we're going to construct
	// our own remain that also takes into account remaining "dynamic" blocks.

	blocks, blockDiags := b.expandBlocks(schema, rawContent.Blocks, true)
	diags = append(diags, blockDiags...)
	attrs := b.prepareAttributes(rawContent.Attributes)

	content := &hcl.BodyContent{
		Attributes:       attrs,
		Blocks:           blocks,
		MissingItemRange: b.original.MissingItemRange(),
	}

	remain := &expandBody{
		original:     b.original,
		forEachCtx:   b.forEachCtx,
		iteration:    b.iteration,
		hiddenAttrs:  make(map[string]struct{}),
		hiddenBlocks: make(map[string]hcl.BlockHeaderSchema),
	}
	for name := range b.hiddenAttrs {
		remain.hiddenAttrs[name] = struct{}{}
	}
	for typeName, blockS := range b.hiddenBlocks {
		remain.hiddenBlocks[typeName] = blockS
	}
	for _, attrS := range schema.Attributes {
		remain.hiddenAttrs[attrS.Name] = struct{}{}
	}
	for _, blockS := range schema.Blocks {
		remain.hiddenBlocks[blockS.Type] = blockS
	}

	return content, remain, diags
}

func (b *expandBody) extendSchema(schema *hcl.BodySchema) *hcl.BodySchema {
	// We augment the requested schema to also include our special "dynamic"
	// block type, since then we'll get instances of it interleaved with
	// all of the literal child blocks we must also include.
	extSchema := &hcl.BodySchema{
		Attributes: schema.Attributes,
		Blocks:     make([]hcl.BlockHeaderSchema, len(schema.Blocks), len(schema.Blocks)+len(b.hiddenBlocks)+1),
	}
	copy(extSchema.Blocks, schema.Blocks)
	extSchema.Blocks = append(extSchema.Blocks, dynamicBlockHeaderSchema)

	// If we have any hiddenBlocks then we also need to register those here
	// so that a call to "Content" on the underlying body won't fail.
	// (We'll filter these out again once we process the result of either
	// Content or PartialContent.)
	for _, blockS := range b.hiddenBlocks {
		extSchema.Blocks = append(extSchema.Blocks, blockS)
	}

	// If we have any hiddenAttrs then we also need to register these, for
	// the same reason as we deal with hiddenBlocks above.
	if len(b.hiddenAttrs) != 0 {
		newAttrs := make([]hcl.AttributeSchema, len(schema.Attributes), len(schema.Attributes)+len(b.hiddenAttrs))
		copy(newAttrs, extSchema.Attributes)
		for name := range b.hiddenAttrs {
			newAttrs = append(newAttrs, hcl.AttributeSchema{
				Name:     name,
				Required: false,
			})
		}
		extSchema.Attributes = newAttrs
	}

	return extSchema
}

func (b *expandBody) prepareAttributes(rawAttrs hcl.Attributes) hcl.Attributes {
	if len(b.hiddenAttrs) == 0 && b.iteration == nil {
		// Easy path: just pass through the attrs from the original body verbatim
		return rawAttrs
	}

	// Otherwise we have some work to do: we must filter out any attributes
	// that are hidden (since a previous PartialContent call already saw these)
	// and wrap the expressions of the inner attributes so that they will
	// have access to our iteration variables.
	attrs := make(hcl.Attributes, len(rawAttrs))
	for name, rawAttr := range rawAttrs {
		if _, hidden := b.hiddenAttrs[name]; hidden {
			continue
		}
		if b.iteration != nil {
			attr := *rawAttr // shallow copy so we can mutate it
			attr.Expr = exprWrap{
				Expression: attr.Expr,
				i:          b.iteration,
			}
			attrs[name] = &attr
		} else {
			// If we have no active iteration then no wrapping is required.
			attrs[name] = rawAttr
		}
	}
	return attrs
}

func (b *expandBody) expandBlocks(schema *hcl.BodySchema, rawBlocks hcl.Blocks, partial bool) (hcl.Blocks, hcl.Diagnostics) {
	var blocks hcl.Blocks
	var diags hcl.Diagnostics

	for _, rawBlock := range rawBlocks {
		switch rawBlock.Type {
		case "dynamic":
			realBlockType := rawBlock.Labels[0]
			if _, hidden := b.hiddenBlocks[realBlockType]; hidden {
				continue
			}

			var blockS *hcl.BlockHeaderSchema
			for _, candidate := range schema.Blocks {
				if candidate.Type == realBlockType {
					blockS = &candidate
					break
				}
			}
			if blockS == nil {
				// Not a block type that the caller requested.
				if !partial {
					diags = append(diags, &hcl.Diagnostic{
						Severity: hcl.DiagError,
						Summary:  "Unsupported block type",
						Detail:   fmt.Sprintf("Blocks of type %q are not expected here.", realBlockType),
						Subject:  &rawBlock.LabelRanges[0],
					})
				}
				continue
			}

			spec, specDiags := b.decodeSpec(blockS, rawBlock)
			diags = append(diags, specDiags...)
			if specDiags.HasErrors() {
				continue
			}

			if spec.forEachVal.IsKnown() {
				for it := spec.forEachVal.ElementIterator(); it.Next(); {
					key, value := it.Element()
					i := b.iteration.MakeChild(spec.iteratorName, key, value)

					block, blockDiags := spec.newBlock(i, b.forEachCtx)
					diags = append(diags, blockDiags...)
					if block != nil {
						// Attach our new iteration context so that attributes
						// and other nested blocks can refer to our iterator.
						block.Body = b.expandChild(block.Body, i)
						blocks = append(blocks, block)
					}
				}
			} else {
				// If our top-level iteration value isn't known then we're forced
				// to compromise since HCL doesn't have any concept of an
				// "unknown block". In this case then, we'll produce a single
				// dynamic block with the iterator values set to DynamicVal,
				// which at least makes the potential for a block visible
				// in our result, even though it's not represented in a fully-accurate
				// way.
				i := b.iteration.MakeChild(spec.iteratorName, cty.DynamicVal, cty.DynamicVal)
				block, blockDiags := spec.newBlock(i, b.forEachCtx)
				diags = append(diags, blockDiags...)
				if block != nil {
					block.Body = b.expandChild(block.Body, i)

					// We additionally force all of the leaf attribute values
					// in the result to be unknown so the calling application
					// can, if necessary, use that as a heuristic to detect
					// when a single nested block might be standing in for
					// multiple blocks yet to be expanded. This retains the
					// structure of the generated body but forces all of its
					// leaf attribute values to be unknown.
					block.Body = unknownBody{block.Body}

					blocks = append(blocks, block)
				}
			}

		default:
			if _, hidden := b.hiddenBlocks[rawBlock.Type]; !hidden {
				// A static block doesn't create a new iteration context, but
				// it does need to inherit _our own_ iteration context in
				// case it contains expressions that refer to our inherited
				// iterators, or nested "dynamic" blocks.
				expandedBlock := *rawBlock // shallow copy
				expandedBlock.Body = b.expandChild(rawBlock.Body, b.iteration)
				blocks = append(blocks, &expandedBlock)
			}
		}
	}

	return blocks, diags
}

func (b *expandBody) expandChild(child hcl.Body, i *iteration) hcl.Body {
	chiCtx := i.EvalContext(b.forEachCtx)
	ret := Expand(child, chiCtx)
	ret.(*expandBody).iteration = i
	return ret
}

func (b *expandBody) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
	// blocks aren't allowed in JustAttributes mode and this body can
	// only produce blocks, so we'll just pass straight through to our
	// underlying body here.
	return b.original.JustAttributes()
}

func (b *expandBody) MissingItemRange() hcl.Range {
	return b.original.MissingItemRange()
}