]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/hcl2/ext/dynblock/expand_body.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / hcl2 / ext / dynblock / expand_body.go
1 package dynblock
2
3 import (
4 "fmt"
5
6 "github.com/hashicorp/hcl2/hcl"
7 "github.com/zclconf/go-cty/cty"
8 )
9
10 // expandBody wraps another hcl.Body and expands any "dynamic" blocks found
11 // inside whenever Content or PartialContent is called.
12 type expandBody struct {
13 original hcl.Body
14 forEachCtx *hcl.EvalContext
15 iteration *iteration // non-nil if we're nested inside another "dynamic" block
16
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.
19 //
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
24 // on the first pass.
25 hiddenAttrs map[string]struct{}
26 hiddenBlocks map[string]hcl.BlockHeaderSchema
27 }
28
29 func (b *expandBody) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
30 extSchema := b.extendSchema(schema)
31 rawContent, diags := b.original.Content(extSchema)
32
33 blocks, blockDiags := b.expandBlocks(schema, rawContent.Blocks, false)
34 diags = append(diags, blockDiags...)
35 attrs := b.prepareAttributes(rawContent.Attributes)
36
37 content := &hcl.BodyContent{
38 Attributes: attrs,
39 Blocks: blocks,
40 MissingItemRange: b.original.MissingItemRange(),
41 }
42
43 return content, diags
44 }
45
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.
51
52 blocks, blockDiags := b.expandBlocks(schema, rawContent.Blocks, true)
53 diags = append(diags, blockDiags...)
54 attrs := b.prepareAttributes(rawContent.Attributes)
55
56 content := &hcl.BodyContent{
57 Attributes: attrs,
58 Blocks: blocks,
59 MissingItemRange: b.original.MissingItemRange(),
60 }
61
62 remain := &expandBody{
63 original: b.original,
64 forEachCtx: b.forEachCtx,
65 iteration: b.iteration,
66 hiddenAttrs: make(map[string]struct{}),
67 hiddenBlocks: make(map[string]hcl.BlockHeaderSchema),
68 }
69 for name := range b.hiddenAttrs {
70 remain.hiddenAttrs[name] = struct{}{}
71 }
72 for typeName, blockS := range b.hiddenBlocks {
73 remain.hiddenBlocks[typeName] = blockS
74 }
75 for _, attrS := range schema.Attributes {
76 remain.hiddenAttrs[attrS.Name] = struct{}{}
77 }
78 for _, blockS := range schema.Blocks {
79 remain.hiddenBlocks[blockS.Type] = blockS
80 }
81
82 return content, remain, diags
83 }
84
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),
92 }
93 copy(extSchema.Blocks, schema.Blocks)
94 extSchema.Blocks = append(extSchema.Blocks, dynamicBlockHeaderSchema)
95
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)
102 }
103
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{
111 Name: name,
112 Required: false,
113 })
114 }
115 extSchema.Attributes = newAttrs
116 }
117
118 return extSchema
119 }
120
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
124 return rawAttrs
125 }
126
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 {
134 continue
135 }
136 if b.iteration != nil {
137 attr := *rawAttr // shallow copy so we can mutate it
138 attr.Expr = exprWrap{
139 Expression: attr.Expr,
140 i: b.iteration,
141 }
142 attrs[name] = &attr
143 } else {
144 // If we have no active iteration then no wrapping is required.
145 attrs[name] = rawAttr
146 }
147 }
148 return attrs
149 }
150
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
154
155 for _, rawBlock := range rawBlocks {
156 switch rawBlock.Type {
157 case "dynamic":
158 realBlockType := rawBlock.Labels[0]
159 if _, hidden := b.hiddenBlocks[realBlockType]; hidden {
160 continue
161 }
162
163 var blockS *hcl.BlockHeaderSchema
164 for _, candidate := range schema.Blocks {
165 if candidate.Type == realBlockType {
166 blockS = &candidate
167 break
168 }
169 }
170 if blockS == nil {
171 // Not a block type that the caller requested.
172 if !partial {
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],
178 })
179 }
180 continue
181 }
182
183 spec, specDiags := b.decodeSpec(blockS, rawBlock)
184 diags = append(diags, specDiags...)
185 if specDiags.HasErrors() {
186 continue
187 }
188
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)
193
194 block, blockDiags := spec.newBlock(i, b.forEachCtx)
195 diags = append(diags, blockDiags...)
196 if block != nil {
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)
201 }
202 }
203 } else {
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
210 // way.
211 i := b.iteration.MakeChild(spec.iteratorName, cty.DynamicVal, cty.DynamicVal)
212 block, blockDiags := spec.newBlock(i, b.forEachCtx)
213 diags = append(diags, blockDiags...)
214 if block != nil {
215 block.Body = b.expandChild(block.Body, i)
216
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}
225
226 blocks = append(blocks, block)
227 }
228 }
229
230 default:
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)
239 }
240 }
241 }
242
243 return blocks, diags
244 }
245
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
250 return ret
251 }
252
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()
258 }
259
260 func (b *expandBody) MissingItemRange() hcl.Range {
261 return b.original.MissingItemRange()
262 }