diff options
author | Nathan Dench <ndenc2@gmail.com> | 2019-05-24 15:16:44 +1000 |
---|---|---|
committer | Nathan Dench <ndenc2@gmail.com> | 2019-05-24 15:16:44 +1000 |
commit | 107c1cdb09c575aa2f61d97f48d8587eb6bada4c (patch) | |
tree | ca7d008643efc555c388baeaf1d986e0b6b3e28c /vendor/github.com/hashicorp/hcl2/ext | |
parent | 844b5a68d8af4791755b8f0ad293cc99f5959183 (diff) | |
download | terraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.tar.gz terraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.tar.zst terraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.zip |
Upgrade to 0.12
Diffstat (limited to 'vendor/github.com/hashicorp/hcl2/ext')
14 files changed, 1602 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/hcl2/ext/dynblock/README.md b/vendor/github.com/hashicorp/hcl2/ext/dynblock/README.md new file mode 100644 index 0000000..2b24fdb --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/ext/dynblock/README.md | |||
@@ -0,0 +1,184 @@ | |||
1 | # HCL Dynamic Blocks Extension | ||
2 | |||
3 | This HCL extension implements a special block type named "dynamic" that can | ||
4 | be used to dynamically generate blocks of other types by iterating over | ||
5 | collection values. | ||
6 | |||
7 | Normally the block structure in an HCL configuration file is rigid, even | ||
8 | though dynamic expressions can be used within attribute values. This is | ||
9 | convenient for most applications since it allows the overall structure of | ||
10 | the document to be decoded easily, but in some applications it is desirable | ||
11 | to allow dynamic block generation within certain portions of the configuration. | ||
12 | |||
13 | Dynamic block generation is performed using the `dynamic` block type: | ||
14 | |||
15 | ```hcl | ||
16 | toplevel { | ||
17 | nested { | ||
18 | foo = "static block 1" | ||
19 | } | ||
20 | |||
21 | dynamic "nested" { | ||
22 | for_each = ["a", "b", "c"] | ||
23 | iterator = nested | ||
24 | content { | ||
25 | foo = "dynamic block ${nested.value}" | ||
26 | } | ||
27 | } | ||
28 | |||
29 | nested { | ||
30 | foo = "static block 2" | ||
31 | } | ||
32 | } | ||
33 | ``` | ||
34 | |||
35 | The above is interpreted as if it were written as follows: | ||
36 | |||
37 | ```hcl | ||
38 | toplevel { | ||
39 | nested { | ||
40 | foo = "static block 1" | ||
41 | } | ||
42 | |||
43 | nested { | ||
44 | foo = "dynamic block a" | ||
45 | } | ||
46 | |||
47 | nested { | ||
48 | foo = "dynamic block b" | ||
49 | } | ||
50 | |||
51 | nested { | ||
52 | foo = "dynamic block c" | ||
53 | } | ||
54 | |||
55 | nested { | ||
56 | foo = "static block 2" | ||
57 | } | ||
58 | } | ||
59 | ``` | ||
60 | |||
61 | Since HCL block syntax is not normally exposed to the possibility of unknown | ||
62 | values, this extension must make some compromises when asked to iterate over | ||
63 | an unknown collection. If the length of the collection cannot be statically | ||
64 | recognized (because it is an unknown value of list, map, or set type) then | ||
65 | the `dynamic` construct will generate a _single_ dynamic block whose iterator | ||
66 | key and value are both unknown values of the dynamic pseudo-type, thus causing | ||
67 | any attribute values derived from iteration to appear as unknown values. There | ||
68 | is no explicit representation of the fact that the length of the collection may | ||
69 | eventually be different than one. | ||
70 | |||
71 | ## Usage | ||
72 | |||
73 | Pass a body to function `Expand` to obtain a new body that will, on access | ||
74 | to its content, evaluate and expand any nested `dynamic` blocks. | ||
75 | Dynamic block processing is also automatically propagated into any nested | ||
76 | blocks that are returned, allowing users to nest dynamic blocks inside | ||
77 | one another and to nest dynamic blocks inside other static blocks. | ||
78 | |||
79 | HCL structural decoding does not normally have access to an `EvalContext`, so | ||
80 | any variables and functions that should be available to the `for_each` | ||
81 | and `labels` expressions must be passed in when calling `Expand`. Expressions | ||
82 | within the `content` block are evaluated separately and so can be passed a | ||
83 | separate `EvalContext` if desired, during normal attribute expression | ||
84 | evaluation. | ||
85 | |||
86 | ## Detecting Variables | ||
87 | |||
88 | Some applications dynamically generate an `EvalContext` by analyzing which | ||
89 | variables are referenced by an expression before evaluating it. | ||
90 | |||
91 | This unfortunately requires some extra effort when this analysis is required | ||
92 | for the context passed to `Expand`: the HCL API requires a schema to be | ||
93 | provided in order to do any analysis of the blocks in a body, but the low-level | ||
94 | schema model provides a description of only one level of nested blocks at | ||
95 | a time, and thus a new schema must be provided for each additional level of | ||
96 | nesting. | ||
97 | |||
98 | To make this arduous process as convenient as possbile, this package provides | ||
99 | a helper function `WalkForEachVariables`, which returns a `WalkVariablesNode` | ||
100 | instance that can be used to find variables directly in a given body and also | ||
101 | determine which nested blocks require recursive calls. Using this mechanism | ||
102 | requires that the caller be able to look up a schema given a nested block type. | ||
103 | For _simple_ formats where a specific block type name always has the same schema | ||
104 | regardless of context, a walk can be implemented as follows: | ||
105 | |||
106 | ```go | ||
107 | func walkVariables(node dynblock.WalkVariablesNode, schema *hcl.BodySchema) []hcl.Traversal { | ||
108 | vars, children := node.Visit(schema) | ||
109 | |||
110 | for _, child := range children { | ||
111 | var childSchema *hcl.BodySchema | ||
112 | switch child.BlockTypeName { | ||
113 | case "a": | ||
114 | childSchema = &hcl.BodySchema{ | ||
115 | Blocks: []hcl.BlockHeaderSchema{ | ||
116 | { | ||
117 | Type: "b", | ||
118 | LabelNames: []string{"key"}, | ||
119 | }, | ||
120 | }, | ||
121 | } | ||
122 | case "b": | ||
123 | childSchema = &hcl.BodySchema{ | ||
124 | Attributes: []hcl.AttributeSchema{ | ||
125 | { | ||
126 | Name: "val", | ||
127 | Required: true, | ||
128 | }, | ||
129 | }, | ||
130 | } | ||
131 | default: | ||
132 | // Should never happen, because the above cases should be exhaustive | ||
133 | // for the application's configuration format. | ||
134 | panic(fmt.Errorf("can't find schema for unknown block type %q", child.BlockTypeName)) | ||
135 | } | ||
136 | |||
137 | vars = append(vars, testWalkAndAccumVars(child.Node, childSchema)...) | ||
138 | } | ||
139 | } | ||
140 | ``` | ||
141 | |||
142 | ### Detecting Variables with `hcldec` Specifications | ||
143 | |||
144 | For applications that use the higher-level `hcldec` package to decode nested | ||
145 | configuration structures into `cty` values, the same specification can be used | ||
146 | to automatically drive the recursive variable-detection walk described above. | ||
147 | |||
148 | The helper function `ForEachVariablesHCLDec` allows an entire recursive | ||
149 | configuration structure to be analyzed in a single call given a `hcldec.Spec` | ||
150 | that describes the nested block structure. This means a `hcldec`-based | ||
151 | application can support dynamic blocks with only a little additional effort: | ||
152 | |||
153 | ```go | ||
154 | func decodeBody(body hcl.Body, spec hcldec.Spec) (cty.Value, hcl.Diagnostics) { | ||
155 | // Determine which variables are needed to expand dynamic blocks | ||
156 | neededForDynamic := dynblock.ForEachVariablesHCLDec(body, spec) | ||
157 | |||
158 | // Build a suitable EvalContext and expand dynamic blocks | ||
159 | dynCtx := buildEvalContext(neededForDynamic) | ||
160 | dynBody := dynblock.Expand(body, dynCtx) | ||
161 | |||
162 | // Determine which variables are needed to fully decode the expanded body | ||
163 | // This will analyze expressions that came both from static blocks in the | ||
164 | // original body and from blocks that were dynamically added by Expand. | ||
165 | neededForDecode := hcldec.Variables(dynBody, spec) | ||
166 | |||
167 | // Build a suitable EvalContext and then fully decode the body as per the | ||
168 | // hcldec specification. | ||
169 | decCtx := buildEvalContext(neededForDecode) | ||
170 | return hcldec.Decode(dynBody, spec, decCtx) | ||
171 | } | ||
172 | |||
173 | func buildEvalContext(needed []hcl.Traversal) *hcl.EvalContext { | ||
174 | // (to be implemented by your application) | ||
175 | } | ||
176 | ``` | ||
177 | |||
178 | # Performance | ||
179 | |||
180 | This extension is going quite harshly against the grain of the HCL API, and | ||
181 | so it uses lots of wrapping objects and temporary data structures to get its | ||
182 | work done. HCL in general is not suitable for use in high-performance situations | ||
183 | or situations sensitive to memory pressure, but that is _especially_ true for | ||
184 | this extension. | ||
diff --git a/vendor/github.com/hashicorp/hcl2/ext/dynblock/expand_body.go b/vendor/github.com/hashicorp/hcl2/ext/dynblock/expand_body.go new file mode 100644 index 0000000..dd30822 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/ext/dynblock/expand_body.go | |||
@@ -0,0 +1,262 @@ | |||
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 | } | ||
diff --git a/vendor/github.com/hashicorp/hcl2/ext/dynblock/expand_spec.go b/vendor/github.com/hashicorp/hcl2/ext/dynblock/expand_spec.go new file mode 100644 index 0000000..41c0be2 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/ext/dynblock/expand_spec.go | |||
@@ -0,0 +1,215 @@ | |||
1 | package dynblock | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | |||
6 | "github.com/hashicorp/hcl2/hcl" | ||
7 | "github.com/zclconf/go-cty/cty" | ||
8 | "github.com/zclconf/go-cty/cty/convert" | ||
9 | ) | ||
10 | |||
11 | type expandSpec struct { | ||
12 | blockType string | ||
13 | blockTypeRange hcl.Range | ||
14 | defRange hcl.Range | ||
15 | forEachVal cty.Value | ||
16 | iteratorName string | ||
17 | labelExprs []hcl.Expression | ||
18 | contentBody hcl.Body | ||
19 | inherited map[string]*iteration | ||
20 | } | ||
21 | |||
22 | func (b *expandBody) decodeSpec(blockS *hcl.BlockHeaderSchema, rawSpec *hcl.Block) (*expandSpec, hcl.Diagnostics) { | ||
23 | var diags hcl.Diagnostics | ||
24 | |||
25 | var schema *hcl.BodySchema | ||
26 | if len(blockS.LabelNames) != 0 { | ||
27 | schema = dynamicBlockBodySchemaLabels | ||
28 | } else { | ||
29 | schema = dynamicBlockBodySchemaNoLabels | ||
30 | } | ||
31 | |||
32 | specContent, specDiags := rawSpec.Body.Content(schema) | ||
33 | diags = append(diags, specDiags...) | ||
34 | if specDiags.HasErrors() { | ||
35 | return nil, diags | ||
36 | } | ||
37 | |||
38 | //// for_each attribute | ||
39 | |||
40 | eachAttr := specContent.Attributes["for_each"] | ||
41 | eachVal, eachDiags := eachAttr.Expr.Value(b.forEachCtx) | ||
42 | diags = append(diags, eachDiags...) | ||
43 | |||
44 | if !eachVal.CanIterateElements() && eachVal.Type() != cty.DynamicPseudoType { | ||
45 | // We skip this error for DynamicPseudoType because that means we either | ||
46 | // have a null (which is checked immediately below) or an unknown | ||
47 | // (which is handled in the expandBody Content methods). | ||
48 | diags = append(diags, &hcl.Diagnostic{ | ||
49 | Severity: hcl.DiagError, | ||
50 | Summary: "Invalid dynamic for_each value", | ||
51 | Detail: fmt.Sprintf("Cannot use a %s value in for_each. An iterable collection is required.", eachVal.Type().FriendlyName()), | ||
52 | Subject: eachAttr.Expr.Range().Ptr(), | ||
53 | Expression: eachAttr.Expr, | ||
54 | EvalContext: b.forEachCtx, | ||
55 | }) | ||
56 | return nil, diags | ||
57 | } | ||
58 | if eachVal.IsNull() { | ||
59 | diags = append(diags, &hcl.Diagnostic{ | ||
60 | Severity: hcl.DiagError, | ||
61 | Summary: "Invalid dynamic for_each value", | ||
62 | Detail: "Cannot use a null value in for_each.", | ||
63 | Subject: eachAttr.Expr.Range().Ptr(), | ||
64 | Expression: eachAttr.Expr, | ||
65 | EvalContext: b.forEachCtx, | ||
66 | }) | ||
67 | return nil, diags | ||
68 | } | ||
69 | |||
70 | //// iterator attribute | ||
71 | |||
72 | iteratorName := blockS.Type | ||
73 | if iteratorAttr := specContent.Attributes["iterator"]; iteratorAttr != nil { | ||
74 | itTraversal, itDiags := hcl.AbsTraversalForExpr(iteratorAttr.Expr) | ||
75 | diags = append(diags, itDiags...) | ||
76 | if itDiags.HasErrors() { | ||
77 | return nil, diags | ||
78 | } | ||
79 | |||
80 | if len(itTraversal) != 1 { | ||
81 | diags = append(diags, &hcl.Diagnostic{ | ||
82 | Severity: hcl.DiagError, | ||
83 | Summary: "Invalid dynamic iterator name", | ||
84 | Detail: "Dynamic iterator must be a single variable name.", | ||
85 | Subject: itTraversal.SourceRange().Ptr(), | ||
86 | }) | ||
87 | return nil, diags | ||
88 | } | ||
89 | |||
90 | iteratorName = itTraversal.RootName() | ||
91 | } | ||
92 | |||
93 | var labelExprs []hcl.Expression | ||
94 | if labelsAttr := specContent.Attributes["labels"]; labelsAttr != nil { | ||
95 | var labelDiags hcl.Diagnostics | ||
96 | labelExprs, labelDiags = hcl.ExprList(labelsAttr.Expr) | ||
97 | diags = append(diags, labelDiags...) | ||
98 | if labelDiags.HasErrors() { | ||
99 | return nil, diags | ||
100 | } | ||
101 | |||
102 | if len(labelExprs) > len(blockS.LabelNames) { | ||
103 | diags = append(diags, &hcl.Diagnostic{ | ||
104 | Severity: hcl.DiagError, | ||
105 | Summary: "Extraneous dynamic block label", | ||
106 | Detail: fmt.Sprintf("Blocks of type %q require %d label(s).", blockS.Type, len(blockS.LabelNames)), | ||
107 | Subject: labelExprs[len(blockS.LabelNames)].Range().Ptr(), | ||
108 | }) | ||
109 | return nil, diags | ||
110 | } else if len(labelExprs) < len(blockS.LabelNames) { | ||
111 | diags = append(diags, &hcl.Diagnostic{ | ||
112 | Severity: hcl.DiagError, | ||
113 | Summary: "Insufficient dynamic block labels", | ||
114 | Detail: fmt.Sprintf("Blocks of type %q require %d label(s).", blockS.Type, len(blockS.LabelNames)), | ||
115 | Subject: labelsAttr.Expr.Range().Ptr(), | ||
116 | }) | ||
117 | return nil, diags | ||
118 | } | ||
119 | } | ||
120 | |||
121 | // Since our schema requests only blocks of type "content", we can assume | ||
122 | // that all entries in specContent.Blocks are content blocks. | ||
123 | if len(specContent.Blocks) == 0 { | ||
124 | diags = append(diags, &hcl.Diagnostic{ | ||
125 | Severity: hcl.DiagError, | ||
126 | Summary: "Missing dynamic content block", | ||
127 | Detail: "A dynamic block must have a nested block of type \"content\" to describe the body of each generated block.", | ||
128 | Subject: &specContent.MissingItemRange, | ||
129 | }) | ||
130 | return nil, diags | ||
131 | } | ||
132 | if len(specContent.Blocks) > 1 { | ||
133 | diags = append(diags, &hcl.Diagnostic{ | ||
134 | Severity: hcl.DiagError, | ||
135 | Summary: "Extraneous dynamic content block", | ||
136 | Detail: "Only one nested content block is allowed for each dynamic block.", | ||
137 | Subject: &specContent.Blocks[1].DefRange, | ||
138 | }) | ||
139 | return nil, diags | ||
140 | } | ||
141 | |||
142 | return &expandSpec{ | ||
143 | blockType: blockS.Type, | ||
144 | blockTypeRange: rawSpec.LabelRanges[0], | ||
145 | defRange: rawSpec.DefRange, | ||
146 | forEachVal: eachVal, | ||
147 | iteratorName: iteratorName, | ||
148 | labelExprs: labelExprs, | ||
149 | contentBody: specContent.Blocks[0].Body, | ||
150 | }, diags | ||
151 | } | ||
152 | |||
153 | func (s *expandSpec) newBlock(i *iteration, ctx *hcl.EvalContext) (*hcl.Block, hcl.Diagnostics) { | ||
154 | var diags hcl.Diagnostics | ||
155 | var labels []string | ||
156 | var labelRanges []hcl.Range | ||
157 | lCtx := i.EvalContext(ctx) | ||
158 | for _, labelExpr := range s.labelExprs { | ||
159 | labelVal, labelDiags := labelExpr.Value(lCtx) | ||
160 | diags = append(diags, labelDiags...) | ||
161 | if labelDiags.HasErrors() { | ||
162 | return nil, diags | ||
163 | } | ||
164 | |||
165 | var convErr error | ||
166 | labelVal, convErr = convert.Convert(labelVal, cty.String) | ||
167 | if convErr != nil { | ||
168 | diags = append(diags, &hcl.Diagnostic{ | ||
169 | Severity: hcl.DiagError, | ||
170 | Summary: "Invalid dynamic block label", | ||
171 | Detail: fmt.Sprintf("Cannot use this value as a dynamic block label: %s.", convErr), | ||
172 | Subject: labelExpr.Range().Ptr(), | ||
173 | Expression: labelExpr, | ||
174 | EvalContext: lCtx, | ||
175 | }) | ||
176 | return nil, diags | ||
177 | } | ||
178 | if labelVal.IsNull() { | ||
179 | diags = append(diags, &hcl.Diagnostic{ | ||
180 | Severity: hcl.DiagError, | ||
181 | Summary: "Invalid dynamic block label", | ||
182 | Detail: "Cannot use a null value as a dynamic block label.", | ||
183 | Subject: labelExpr.Range().Ptr(), | ||
184 | Expression: labelExpr, | ||
185 | EvalContext: lCtx, | ||
186 | }) | ||
187 | return nil, diags | ||
188 | } | ||
189 | if !labelVal.IsKnown() { | ||
190 | diags = append(diags, &hcl.Diagnostic{ | ||
191 | Severity: hcl.DiagError, | ||
192 | Summary: "Invalid dynamic block label", | ||
193 | Detail: "This value is not yet known. Dynamic block labels must be immediately-known values.", | ||
194 | Subject: labelExpr.Range().Ptr(), | ||
195 | Expression: labelExpr, | ||
196 | EvalContext: lCtx, | ||
197 | }) | ||
198 | return nil, diags | ||
199 | } | ||
200 | |||
201 | labels = append(labels, labelVal.AsString()) | ||
202 | labelRanges = append(labelRanges, labelExpr.Range()) | ||
203 | } | ||
204 | |||
205 | block := &hcl.Block{ | ||
206 | Type: s.blockType, | ||
207 | TypeRange: s.blockTypeRange, | ||
208 | Labels: labels, | ||
209 | LabelRanges: labelRanges, | ||
210 | DefRange: s.defRange, | ||
211 | Body: s.contentBody, | ||
212 | } | ||
213 | |||
214 | return block, diags | ||
215 | } | ||
diff --git a/vendor/github.com/hashicorp/hcl2/ext/dynblock/expr_wrap.go b/vendor/github.com/hashicorp/hcl2/ext/dynblock/expr_wrap.go new file mode 100644 index 0000000..6916fc1 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/ext/dynblock/expr_wrap.go | |||
@@ -0,0 +1,42 @@ | |||
1 | package dynblock | ||
2 | |||
3 | import ( | ||
4 | "github.com/hashicorp/hcl2/hcl" | ||
5 | "github.com/zclconf/go-cty/cty" | ||
6 | ) | ||
7 | |||
8 | type exprWrap struct { | ||
9 | hcl.Expression | ||
10 | i *iteration | ||
11 | } | ||
12 | |||
13 | func (e exprWrap) Variables() []hcl.Traversal { | ||
14 | raw := e.Expression.Variables() | ||
15 | ret := make([]hcl.Traversal, 0, len(raw)) | ||
16 | |||
17 | // Filter out traversals that refer to our iterator name or any | ||
18 | // iterator we've inherited; we're going to provide those in | ||
19 | // our Value wrapper, so the caller doesn't need to know about them. | ||
20 | for _, traversal := range raw { | ||
21 | rootName := traversal.RootName() | ||
22 | if rootName == e.i.IteratorName { | ||
23 | continue | ||
24 | } | ||
25 | if _, inherited := e.i.Inherited[rootName]; inherited { | ||
26 | continue | ||
27 | } | ||
28 | ret = append(ret, traversal) | ||
29 | } | ||
30 | return ret | ||
31 | } | ||
32 | |||
33 | func (e exprWrap) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | ||
34 | extCtx := e.i.EvalContext(ctx) | ||
35 | return e.Expression.Value(extCtx) | ||
36 | } | ||
37 | |||
38 | // UnwrapExpression returns the expression being wrapped by this instance. | ||
39 | // This allows the original expression to be recovered by hcl.UnwrapExpression. | ||
40 | func (e exprWrap) UnwrapExpression() hcl.Expression { | ||
41 | return e.Expression | ||
42 | } | ||
diff --git a/vendor/github.com/hashicorp/hcl2/ext/dynblock/iteration.go b/vendor/github.com/hashicorp/hcl2/ext/dynblock/iteration.go new file mode 100644 index 0000000..7056d33 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/ext/dynblock/iteration.go | |||
@@ -0,0 +1,66 @@ | |||
1 | package dynblock | ||
2 | |||
3 | import ( | ||
4 | "github.com/hashicorp/hcl2/hcl" | ||
5 | "github.com/zclconf/go-cty/cty" | ||
6 | ) | ||
7 | |||
8 | type iteration struct { | ||
9 | IteratorName string | ||
10 | Key cty.Value | ||
11 | Value cty.Value | ||
12 | Inherited map[string]*iteration | ||
13 | } | ||
14 | |||
15 | func (s *expandSpec) MakeIteration(key, value cty.Value) *iteration { | ||
16 | return &iteration{ | ||
17 | IteratorName: s.iteratorName, | ||
18 | Key: key, | ||
19 | Value: value, | ||
20 | Inherited: s.inherited, | ||
21 | } | ||
22 | } | ||
23 | |||
24 | func (i *iteration) Object() cty.Value { | ||
25 | return cty.ObjectVal(map[string]cty.Value{ | ||
26 | "key": i.Key, | ||
27 | "value": i.Value, | ||
28 | }) | ||
29 | } | ||
30 | |||
31 | func (i *iteration) EvalContext(base *hcl.EvalContext) *hcl.EvalContext { | ||
32 | new := base.NewChild() | ||
33 | |||
34 | if i != nil { | ||
35 | new.Variables = map[string]cty.Value{} | ||
36 | for name, otherIt := range i.Inherited { | ||
37 | new.Variables[name] = otherIt.Object() | ||
38 | } | ||
39 | new.Variables[i.IteratorName] = i.Object() | ||
40 | } | ||
41 | |||
42 | return new | ||
43 | } | ||
44 | |||
45 | func (i *iteration) MakeChild(iteratorName string, key, value cty.Value) *iteration { | ||
46 | if i == nil { | ||
47 | // Create entirely new root iteration, then | ||
48 | return &iteration{ | ||
49 | IteratorName: iteratorName, | ||
50 | Key: key, | ||
51 | Value: value, | ||
52 | } | ||
53 | } | ||
54 | |||
55 | inherited := map[string]*iteration{} | ||
56 | for name, otherIt := range i.Inherited { | ||
57 | inherited[name] = otherIt | ||
58 | } | ||
59 | inherited[i.IteratorName] = i | ||
60 | return &iteration{ | ||
61 | IteratorName: iteratorName, | ||
62 | Key: key, | ||
63 | Value: value, | ||
64 | Inherited: inherited, | ||
65 | } | ||
66 | } | ||
diff --git a/vendor/github.com/hashicorp/hcl2/ext/dynblock/public.go b/vendor/github.com/hashicorp/hcl2/ext/dynblock/public.go new file mode 100644 index 0000000..b7e8ca9 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/ext/dynblock/public.go | |||
@@ -0,0 +1,44 @@ | |||
1 | package dynblock | ||
2 | |||
3 | import ( | ||
4 | "github.com/hashicorp/hcl2/hcl" | ||
5 | ) | ||
6 | |||
7 | // Expand "dynamic" blocks in the given body, returning a new body that | ||
8 | // has those blocks expanded. | ||
9 | // | ||
10 | // The given EvalContext is used when evaluating "for_each" and "labels" | ||
11 | // attributes within dynamic blocks, allowing those expressions access to | ||
12 | // variables and functions beyond the iterator variable created by the | ||
13 | // iteration. | ||
14 | // | ||
15 | // Expand returns no diagnostics because no blocks are actually expanded | ||
16 | // until a call to Content or PartialContent on the returned body, which | ||
17 | // will then expand only the blocks selected by the schema. | ||
18 | // | ||
19 | // "dynamic" blocks are also expanded automatically within nested blocks | ||
20 | // in the given body, including within other dynamic blocks, thus allowing | ||
21 | // multi-dimensional iteration. However, it is not possible to | ||
22 | // dynamically-generate the "dynamic" blocks themselves except through nesting. | ||
23 | // | ||
24 | // parent { | ||
25 | // dynamic "child" { | ||
26 | // for_each = child_objs | ||
27 | // content { | ||
28 | // dynamic "grandchild" { | ||
29 | // for_each = child.value.children | ||
30 | // labels = [grandchild.key] | ||
31 | // content { | ||
32 | // parent_key = child.key | ||
33 | // value = grandchild.value | ||
34 | // } | ||
35 | // } | ||
36 | // } | ||
37 | // } | ||
38 | // } | ||
39 | func Expand(body hcl.Body, ctx *hcl.EvalContext) hcl.Body { | ||
40 | return &expandBody{ | ||
41 | original: body, | ||
42 | forEachCtx: ctx, | ||
43 | } | ||
44 | } | ||
diff --git a/vendor/github.com/hashicorp/hcl2/ext/dynblock/schema.go b/vendor/github.com/hashicorp/hcl2/ext/dynblock/schema.go new file mode 100644 index 0000000..dc8ed5a --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/ext/dynblock/schema.go | |||
@@ -0,0 +1,50 @@ | |||
1 | package dynblock | ||
2 | |||
3 | import "github.com/hashicorp/hcl2/hcl" | ||
4 | |||
5 | var dynamicBlockHeaderSchema = hcl.BlockHeaderSchema{ | ||
6 | Type: "dynamic", | ||
7 | LabelNames: []string{"type"}, | ||
8 | } | ||
9 | |||
10 | var dynamicBlockBodySchemaLabels = &hcl.BodySchema{ | ||
11 | Attributes: []hcl.AttributeSchema{ | ||
12 | { | ||
13 | Name: "for_each", | ||
14 | Required: true, | ||
15 | }, | ||
16 | { | ||
17 | Name: "iterator", | ||
18 | Required: false, | ||
19 | }, | ||
20 | { | ||
21 | Name: "labels", | ||
22 | Required: true, | ||
23 | }, | ||
24 | }, | ||
25 | Blocks: []hcl.BlockHeaderSchema{ | ||
26 | { | ||
27 | Type: "content", | ||
28 | LabelNames: nil, | ||
29 | }, | ||
30 | }, | ||
31 | } | ||
32 | |||
33 | var dynamicBlockBodySchemaNoLabels = &hcl.BodySchema{ | ||
34 | Attributes: []hcl.AttributeSchema{ | ||
35 | { | ||
36 | Name: "for_each", | ||
37 | Required: true, | ||
38 | }, | ||
39 | { | ||
40 | Name: "iterator", | ||
41 | Required: false, | ||
42 | }, | ||
43 | }, | ||
44 | Blocks: []hcl.BlockHeaderSchema{ | ||
45 | { | ||
46 | Type: "content", | ||
47 | LabelNames: nil, | ||
48 | }, | ||
49 | }, | ||
50 | } | ||
diff --git a/vendor/github.com/hashicorp/hcl2/ext/dynblock/unknown_body.go b/vendor/github.com/hashicorp/hcl2/ext/dynblock/unknown_body.go new file mode 100644 index 0000000..932f6a3 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/ext/dynblock/unknown_body.go | |||
@@ -0,0 +1,84 @@ | |||
1 | package dynblock | ||
2 | |||
3 | import ( | ||
4 | "github.com/hashicorp/hcl2/hcl" | ||
5 | "github.com/zclconf/go-cty/cty" | ||
6 | ) | ||
7 | |||
8 | // unknownBody is a funny body that just reports everything inside it as | ||
9 | // unknown. It uses a given other body as a sort of template for what attributes | ||
10 | // and blocks are inside -- including source location information -- but | ||
11 | // subsitutes unknown values of unknown type for all attributes. | ||
12 | // | ||
13 | // This rather odd process is used to handle expansion of dynamic blocks whose | ||
14 | // for_each expression is unknown. Since a block cannot itself be unknown, | ||
15 | // we instead arrange for everything _inside_ the block to be unknown instead, | ||
16 | // to give the best possible approximation. | ||
17 | type unknownBody struct { | ||
18 | template hcl.Body | ||
19 | } | ||
20 | |||
21 | var _ hcl.Body = unknownBody{} | ||
22 | |||
23 | func (b unknownBody) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) { | ||
24 | content, diags := b.template.Content(schema) | ||
25 | content = b.fixupContent(content) | ||
26 | |||
27 | // We're intentionally preserving the diagnostics reported from the | ||
28 | // inner body so that we can still report where the template body doesn't | ||
29 | // match the requested schema. | ||
30 | return content, diags | ||
31 | } | ||
32 | |||
33 | func (b unknownBody) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) { | ||
34 | content, remain, diags := b.template.PartialContent(schema) | ||
35 | content = b.fixupContent(content) | ||
36 | remain = unknownBody{remain} // remaining content must also be wrapped | ||
37 | |||
38 | // We're intentionally preserving the diagnostics reported from the | ||
39 | // inner body so that we can still report where the template body doesn't | ||
40 | // match the requested schema. | ||
41 | return content, remain, diags | ||
42 | } | ||
43 | |||
44 | func (b unknownBody) JustAttributes() (hcl.Attributes, hcl.Diagnostics) { | ||
45 | attrs, diags := b.template.JustAttributes() | ||
46 | attrs = b.fixupAttrs(attrs) | ||
47 | |||
48 | // We're intentionally preserving the diagnostics reported from the | ||
49 | // inner body so that we can still report where the template body doesn't | ||
50 | // match the requested schema. | ||
51 | return attrs, diags | ||
52 | } | ||
53 | |||
54 | func (b unknownBody) MissingItemRange() hcl.Range { | ||
55 | return b.template.MissingItemRange() | ||
56 | } | ||
57 | |||
58 | func (b unknownBody) fixupContent(got *hcl.BodyContent) *hcl.BodyContent { | ||
59 | ret := &hcl.BodyContent{} | ||
60 | ret.Attributes = b.fixupAttrs(got.Attributes) | ||
61 | if len(got.Blocks) > 0 { | ||
62 | ret.Blocks = make(hcl.Blocks, 0, len(got.Blocks)) | ||
63 | for _, gotBlock := range got.Blocks { | ||
64 | new := *gotBlock // shallow copy | ||
65 | new.Body = unknownBody{gotBlock.Body} // nested content must also be marked unknown | ||
66 | ret.Blocks = append(ret.Blocks, &new) | ||
67 | } | ||
68 | } | ||
69 | |||
70 | return ret | ||
71 | } | ||
72 | |||
73 | func (b unknownBody) fixupAttrs(got hcl.Attributes) hcl.Attributes { | ||
74 | if len(got) == 0 { | ||
75 | return nil | ||
76 | } | ||
77 | ret := make(hcl.Attributes, len(got)) | ||
78 | for name, gotAttr := range got { | ||
79 | new := *gotAttr // shallow copy | ||
80 | new.Expr = hcl.StaticExpr(cty.DynamicVal, gotAttr.Expr.Range()) | ||
81 | ret[name] = &new | ||
82 | } | ||
83 | return ret | ||
84 | } | ||
diff --git a/vendor/github.com/hashicorp/hcl2/ext/dynblock/variables.go b/vendor/github.com/hashicorp/hcl2/ext/dynblock/variables.go new file mode 100644 index 0000000..ad838f3 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/ext/dynblock/variables.go | |||
@@ -0,0 +1,209 @@ | |||
1 | package dynblock | ||
2 | |||
3 | import ( | ||
4 | "github.com/hashicorp/hcl2/hcl" | ||
5 | "github.com/zclconf/go-cty/cty" | ||
6 | ) | ||
7 | |||
8 | // WalkVariables begins the recursive process of walking all expressions and | ||
9 | // nested blocks in the given body and its child bodies while taking into | ||
10 | // account any "dynamic" blocks. | ||
11 | // | ||
12 | // This function requires that the caller walk through the nested block | ||
13 | // structure in the given body level-by-level so that an appropriate schema | ||
14 | // can be provided at each level to inform further processing. This workflow | ||
15 | // is thus easiest to use for calling applications that have some higher-level | ||
16 | // schema representation available with which to drive this multi-step | ||
17 | // process. If your application uses the hcldec package, you may be able to | ||
18 | // use VariablesHCLDec instead for a more automatic approach. | ||
19 | func WalkVariables(body hcl.Body) WalkVariablesNode { | ||
20 | return WalkVariablesNode{ | ||
21 | body: body, | ||
22 | includeContent: true, | ||
23 | } | ||
24 | } | ||
25 | |||
26 | // WalkExpandVariables is like Variables but it includes only the variables | ||
27 | // required for successful block expansion, ignoring any variables referenced | ||
28 | // inside block contents. The result is the minimal set of all variables | ||
29 | // required for a call to Expand, excluding variables that would only be | ||
30 | // needed to subsequently call Content or PartialContent on the expanded | ||
31 | // body. | ||
32 | func WalkExpandVariables(body hcl.Body) WalkVariablesNode { | ||
33 | return WalkVariablesNode{ | ||
34 | body: body, | ||
35 | } | ||
36 | } | ||
37 | |||
38 | type WalkVariablesNode struct { | ||
39 | body hcl.Body | ||
40 | it *iteration | ||
41 | |||
42 | includeContent bool | ||
43 | } | ||
44 | |||
45 | type WalkVariablesChild struct { | ||
46 | BlockTypeName string | ||
47 | Node WalkVariablesNode | ||
48 | } | ||
49 | |||
50 | // Body returns the HCL Body associated with the child node, in case the caller | ||
51 | // wants to do some sort of inspection of it in order to decide what schema | ||
52 | // to pass to Visit. | ||
53 | // | ||
54 | // Most implementations should just fetch a fixed schema based on the | ||
55 | // BlockTypeName field and not access this. Deciding on a schema dynamically | ||
56 | // based on the body is a strange thing to do and generally necessary only if | ||
57 | // your caller is already doing other bizarre things with HCL bodies. | ||
58 | func (c WalkVariablesChild) Body() hcl.Body { | ||
59 | return c.Node.body | ||
60 | } | ||
61 | |||
62 | // Visit returns the variable traversals required for any "dynamic" blocks | ||
63 | // directly in the body associated with this node, and also returns any child | ||
64 | // nodes that must be visited in order to continue the walk. | ||
65 | // | ||
66 | // Each child node has its associated block type name given in its BlockTypeName | ||
67 | // field, which the calling application should use to determine the appropriate | ||
68 | // schema for the content of each child node and pass it to the child node's | ||
69 | // own Visit method to continue the walk recursively. | ||
70 | func (n WalkVariablesNode) Visit(schema *hcl.BodySchema) (vars []hcl.Traversal, children []WalkVariablesChild) { | ||
71 | extSchema := n.extendSchema(schema) | ||
72 | container, _, _ := n.body.PartialContent(extSchema) | ||
73 | if container == nil { | ||
74 | return vars, children | ||
75 | } | ||
76 | |||
77 | children = make([]WalkVariablesChild, 0, len(container.Blocks)) | ||
78 | |||
79 | if n.includeContent { | ||
80 | for _, attr := range container.Attributes { | ||
81 | for _, traversal := range attr.Expr.Variables() { | ||
82 | var ours, inherited bool | ||
83 | if n.it != nil { | ||
84 | ours = traversal.RootName() == n.it.IteratorName | ||
85 | _, inherited = n.it.Inherited[traversal.RootName()] | ||
86 | } | ||
87 | |||
88 | if !(ours || inherited) { | ||
89 | vars = append(vars, traversal) | ||
90 | } | ||
91 | } | ||
92 | } | ||
93 | } | ||
94 | |||
95 | for _, block := range container.Blocks { | ||
96 | switch block.Type { | ||
97 | |||
98 | case "dynamic": | ||
99 | blockTypeName := block.Labels[0] | ||
100 | inner, _, _ := block.Body.PartialContent(variableDetectionInnerSchema) | ||
101 | if inner == nil { | ||
102 | continue | ||
103 | } | ||
104 | |||
105 | iteratorName := blockTypeName | ||
106 | if attr, exists := inner.Attributes["iterator"]; exists { | ||
107 | iterTraversal, _ := hcl.AbsTraversalForExpr(attr.Expr) | ||
108 | if len(iterTraversal) == 0 { | ||
109 | // Ignore this invalid dynamic block, since it'll produce | ||
110 | // an error if someone tries to extract content from it | ||
111 | // later anyway. | ||
112 | continue | ||
113 | } | ||
114 | iteratorName = iterTraversal.RootName() | ||
115 | } | ||
116 | blockIt := n.it.MakeChild(iteratorName, cty.DynamicVal, cty.DynamicVal) | ||
117 | |||
118 | if attr, exists := inner.Attributes["for_each"]; exists { | ||
119 | // Filter out iterator names inherited from parent blocks | ||
120 | for _, traversal := range attr.Expr.Variables() { | ||
121 | if _, inherited := blockIt.Inherited[traversal.RootName()]; !inherited { | ||
122 | vars = append(vars, traversal) | ||
123 | } | ||
124 | } | ||
125 | } | ||
126 | if attr, exists := inner.Attributes["labels"]; exists { | ||
127 | // Filter out both our own iterator name _and_ those inherited | ||
128 | // from parent blocks, since we provide _both_ of these to the | ||
129 | // label expressions. | ||
130 | for _, traversal := range attr.Expr.Variables() { | ||
131 | ours := traversal.RootName() == iteratorName | ||
132 | _, inherited := blockIt.Inherited[traversal.RootName()] | ||
133 | |||
134 | if !(ours || inherited) { | ||
135 | vars = append(vars, traversal) | ||
136 | } | ||
137 | } | ||
138 | } | ||
139 | |||
140 | for _, contentBlock := range inner.Blocks { | ||
141 | // We only request "content" blocks in our schema, so we know | ||
142 | // any blocks we find here will be content blocks. We require | ||
143 | // exactly one content block for actual expansion, but we'll | ||
144 | // be more liberal here so that callers can still collect | ||
145 | // variables from erroneous "dynamic" blocks. | ||
146 | children = append(children, WalkVariablesChild{ | ||
147 | BlockTypeName: blockTypeName, | ||
148 | Node: WalkVariablesNode{ | ||
149 | body: contentBlock.Body, | ||
150 | it: blockIt, | ||
151 | includeContent: n.includeContent, | ||
152 | }, | ||
153 | }) | ||
154 | } | ||
155 | |||
156 | default: | ||
157 | children = append(children, WalkVariablesChild{ | ||
158 | BlockTypeName: block.Type, | ||
159 | Node: WalkVariablesNode{ | ||
160 | body: block.Body, | ||
161 | it: n.it, | ||
162 | includeContent: n.includeContent, | ||
163 | }, | ||
164 | }) | ||
165 | |||
166 | } | ||
167 | } | ||
168 | |||
169 | return vars, children | ||
170 | } | ||
171 | |||
172 | func (n WalkVariablesNode) extendSchema(schema *hcl.BodySchema) *hcl.BodySchema { | ||
173 | // We augment the requested schema to also include our special "dynamic" | ||
174 | // block type, since then we'll get instances of it interleaved with | ||
175 | // all of the literal child blocks we must also include. | ||
176 | extSchema := &hcl.BodySchema{ | ||
177 | Attributes: schema.Attributes, | ||
178 | Blocks: make([]hcl.BlockHeaderSchema, len(schema.Blocks), len(schema.Blocks)+1), | ||
179 | } | ||
180 | copy(extSchema.Blocks, schema.Blocks) | ||
181 | extSchema.Blocks = append(extSchema.Blocks, dynamicBlockHeaderSchema) | ||
182 | |||
183 | return extSchema | ||
184 | } | ||
185 | |||
186 | // This is a more relaxed schema than what's in schema.go, since we | ||
187 | // want to maximize the amount of variables we can find even if there | ||
188 | // are erroneous blocks. | ||
189 | var variableDetectionInnerSchema = &hcl.BodySchema{ | ||
190 | Attributes: []hcl.AttributeSchema{ | ||
191 | { | ||
192 | Name: "for_each", | ||
193 | Required: false, | ||
194 | }, | ||
195 | { | ||
196 | Name: "labels", | ||
197 | Required: false, | ||
198 | }, | ||
199 | { | ||
200 | Name: "iterator", | ||
201 | Required: false, | ||
202 | }, | ||
203 | }, | ||
204 | Blocks: []hcl.BlockHeaderSchema{ | ||
205 | { | ||
206 | Type: "content", | ||
207 | }, | ||
208 | }, | ||
209 | } | ||
diff --git a/vendor/github.com/hashicorp/hcl2/ext/dynblock/variables_hcldec.go b/vendor/github.com/hashicorp/hcl2/ext/dynblock/variables_hcldec.go new file mode 100644 index 0000000..a078d91 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/ext/dynblock/variables_hcldec.go | |||
@@ -0,0 +1,43 @@ | |||
1 | package dynblock | ||
2 | |||
3 | import ( | ||
4 | "github.com/hashicorp/hcl2/hcl" | ||
5 | "github.com/hashicorp/hcl2/hcldec" | ||
6 | ) | ||
7 | |||
8 | // VariablesHCLDec is a wrapper around WalkVariables that uses the given hcldec | ||
9 | // specification to automatically drive the recursive walk through nested | ||
10 | // blocks in the given body. | ||
11 | // | ||
12 | // This is a drop-in replacement for hcldec.Variables which is able to treat | ||
13 | // blocks of type "dynamic" in the same special way that dynblock.Expand would, | ||
14 | // exposing both the variables referenced in the "for_each" and "labels" | ||
15 | // arguments and variables used in the nested "content" block. | ||
16 | func VariablesHCLDec(body hcl.Body, spec hcldec.Spec) []hcl.Traversal { | ||
17 | rootNode := WalkVariables(body) | ||
18 | return walkVariablesWithHCLDec(rootNode, spec) | ||
19 | } | ||
20 | |||
21 | // ExpandVariablesHCLDec is like VariablesHCLDec but it includes only the | ||
22 | // minimal set of variables required to call Expand, ignoring variables that | ||
23 | // are referenced only inside normal block contents. See WalkExpandVariables | ||
24 | // for more information. | ||
25 | func ExpandVariablesHCLDec(body hcl.Body, spec hcldec.Spec) []hcl.Traversal { | ||
26 | rootNode := WalkExpandVariables(body) | ||
27 | return walkVariablesWithHCLDec(rootNode, spec) | ||
28 | } | ||
29 | |||
30 | func walkVariablesWithHCLDec(node WalkVariablesNode, spec hcldec.Spec) []hcl.Traversal { | ||
31 | vars, children := node.Visit(hcldec.ImpliedSchema(spec)) | ||
32 | |||
33 | if len(children) > 0 { | ||
34 | childSpecs := hcldec.ChildBlockTypes(spec) | ||
35 | for _, child := range children { | ||
36 | if childSpec, exists := childSpecs[child.BlockTypeName]; exists { | ||
37 | vars = append(vars, walkVariablesWithHCLDec(child.Node, childSpec)...) | ||
38 | } | ||
39 | } | ||
40 | } | ||
41 | |||
42 | return vars | ||
43 | } | ||
diff --git a/vendor/github.com/hashicorp/hcl2/ext/typeexpr/README.md b/vendor/github.com/hashicorp/hcl2/ext/typeexpr/README.md new file mode 100644 index 0000000..ff2b3f2 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/ext/typeexpr/README.md | |||
@@ -0,0 +1,67 @@ | |||
1 | # HCL Type Expressions Extension | ||
2 | |||
3 | This HCL extension defines a convention for describing HCL types using function | ||
4 | call and variable reference syntax, allowing configuration formats to include | ||
5 | type information provided by users. | ||
6 | |||
7 | The type syntax is processed statically from a hcl.Expression, so it cannot | ||
8 | use any of the usual language operators. This is similar to type expressions | ||
9 | in statically-typed programming languages. | ||
10 | |||
11 | ```hcl | ||
12 | variable "example" { | ||
13 | type = list(string) | ||
14 | } | ||
15 | ``` | ||
16 | |||
17 | The extension is built using the `hcl.ExprAsKeyword` and `hcl.ExprCall` | ||
18 | functions, and so it relies on the underlying syntax to define how "keyword" | ||
19 | and "call" are interpreted. The above shows how they are interpreted in | ||
20 | the HCL native syntax, while the following shows the same information | ||
21 | expressed in JSON: | ||
22 | |||
23 | ```json | ||
24 | { | ||
25 | "variable": { | ||
26 | "example": { | ||
27 | "type": "list(string)" | ||
28 | } | ||
29 | } | ||
30 | } | ||
31 | ``` | ||
32 | |||
33 | Notice that since we have additional contextual information that we intend | ||
34 | to allow only calls and keywords the JSON syntax is able to parse the given | ||
35 | string directly as an expression, rather than as a template as would be | ||
36 | the case for normal expression evaluation. | ||
37 | |||
38 | For more information, see [the godoc reference](http://godoc.org/github.com/hashicorp/hcl2/ext/typeexpr). | ||
39 | |||
40 | ## Type Expression Syntax | ||
41 | |||
42 | When expressed in the native syntax, the following expressions are permitted | ||
43 | in a type expression: | ||
44 | |||
45 | * `string` - string | ||
46 | * `bool` - boolean | ||
47 | * `number` - number | ||
48 | * `any` - `cty.DynamicPseudoType` (in function `TypeConstraint` only) | ||
49 | * `list(<type_expr>)` - list of the type given as an argument | ||
50 | * `set(<type_expr>)` - set of the type given as an argument | ||
51 | * `map(<type_expr>)` - map of the type given as an argument | ||
52 | * `tuple([<type_exprs...>])` - tuple with the element types given in the single list argument | ||
53 | * `object({<attr_name>=<type_expr>, ...}` - object with the attributes and corresponding types given in the single map argument | ||
54 | |||
55 | For example: | ||
56 | |||
57 | * `list(string)` | ||
58 | * `object({name=string,age=number})` | ||
59 | * `map(object({name=string,age=number}))` | ||
60 | |||
61 | Note that the object constructor syntax is not fully-general for all possible | ||
62 | object types because it requires the attribute names to be valid identifiers. | ||
63 | In practice it is expected that any time an object type is being fixed for | ||
64 | type checking it will be one that has identifiers as its attributes; object | ||
65 | types with weird attributes generally show up only from arbitrary object | ||
66 | constructors in configuration files, which are usually treated either as maps | ||
67 | or as the dynamic pseudo-type. | ||
diff --git a/vendor/github.com/hashicorp/hcl2/ext/typeexpr/doc.go b/vendor/github.com/hashicorp/hcl2/ext/typeexpr/doc.go new file mode 100644 index 0000000..c4b3795 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/ext/typeexpr/doc.go | |||
@@ -0,0 +1,11 @@ | |||
1 | // Package typeexpr extends HCL with a convention for describing HCL types | ||
2 | // within configuration files. | ||
3 | // | ||
4 | // The type syntax is processed statically from a hcl.Expression, so it cannot | ||
5 | // use any of the usual language operators. This is similar to type expressions | ||
6 | // in statically-typed programming languages. | ||
7 | // | ||
8 | // variable "example" { | ||
9 | // type = list(string) | ||
10 | // } | ||
11 | package typeexpr | ||
diff --git a/vendor/github.com/hashicorp/hcl2/ext/typeexpr/get_type.go b/vendor/github.com/hashicorp/hcl2/ext/typeexpr/get_type.go new file mode 100644 index 0000000..a84338a --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/ext/typeexpr/get_type.go | |||
@@ -0,0 +1,196 @@ | |||
1 | package typeexpr | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | |||
6 | "github.com/hashicorp/hcl2/hcl" | ||
7 | "github.com/zclconf/go-cty/cty" | ||
8 | ) | ||
9 | |||
10 | const invalidTypeSummary = "Invalid type specification" | ||
11 | |||
12 | // getType is the internal implementation of both Type and TypeConstraint, | ||
13 | // using the passed flag to distinguish. When constraint is false, the "any" | ||
14 | // keyword will produce an error. | ||
15 | func getType(expr hcl.Expression, constraint bool) (cty.Type, hcl.Diagnostics) { | ||
16 | // First we'll try for one of our keywords | ||
17 | kw := hcl.ExprAsKeyword(expr) | ||
18 | switch kw { | ||
19 | case "bool": | ||
20 | return cty.Bool, nil | ||
21 | case "string": | ||
22 | return cty.String, nil | ||
23 | case "number": | ||
24 | return cty.Number, nil | ||
25 | case "any": | ||
26 | if constraint { | ||
27 | return cty.DynamicPseudoType, nil | ||
28 | } | ||
29 | return cty.DynamicPseudoType, hcl.Diagnostics{{ | ||
30 | Severity: hcl.DiagError, | ||
31 | Summary: invalidTypeSummary, | ||
32 | Detail: fmt.Sprintf("The keyword %q cannot be used in this type specification: an exact type is required.", kw), | ||
33 | Subject: expr.Range().Ptr(), | ||
34 | }} | ||
35 | case "list", "map", "set": | ||
36 | return cty.DynamicPseudoType, hcl.Diagnostics{{ | ||
37 | Severity: hcl.DiagError, | ||
38 | Summary: invalidTypeSummary, | ||
39 | Detail: fmt.Sprintf("The %s type constructor requires one argument specifying the element type.", kw), | ||
40 | Subject: expr.Range().Ptr(), | ||
41 | }} | ||
42 | case "object": | ||
43 | return cty.DynamicPseudoType, hcl.Diagnostics{{ | ||
44 | Severity: hcl.DiagError, | ||
45 | Summary: invalidTypeSummary, | ||
46 | Detail: "The object type constructor requires one argument specifying the attribute types and values as a map.", | ||
47 | Subject: expr.Range().Ptr(), | ||
48 | }} | ||
49 | case "tuple": | ||
50 | return cty.DynamicPseudoType, hcl.Diagnostics{{ | ||
51 | Severity: hcl.DiagError, | ||
52 | Summary: invalidTypeSummary, | ||
53 | Detail: "The tuple type constructor requires one argument specifying the element types as a list.", | ||
54 | Subject: expr.Range().Ptr(), | ||
55 | }} | ||
56 | case "": | ||
57 | // okay! we'll fall through and try processing as a call, then. | ||
58 | default: | ||
59 | return cty.DynamicPseudoType, hcl.Diagnostics{{ | ||
60 | Severity: hcl.DiagError, | ||
61 | Summary: invalidTypeSummary, | ||
62 | Detail: fmt.Sprintf("The keyword %q is not a valid type specification.", kw), | ||
63 | Subject: expr.Range().Ptr(), | ||
64 | }} | ||
65 | } | ||
66 | |||
67 | // If we get down here then our expression isn't just a keyword, so we'll | ||
68 | // try to process it as a call instead. | ||
69 | call, diags := hcl.ExprCall(expr) | ||
70 | if diags.HasErrors() { | ||
71 | return cty.DynamicPseudoType, hcl.Diagnostics{{ | ||
72 | Severity: hcl.DiagError, | ||
73 | Summary: invalidTypeSummary, | ||
74 | Detail: "A type specification is either a primitive type keyword (bool, number, string) or a complex type constructor call, like list(string).", | ||
75 | Subject: expr.Range().Ptr(), | ||
76 | }} | ||
77 | } | ||
78 | |||
79 | switch call.Name { | ||
80 | case "bool", "string", "number", "any": | ||
81 | return cty.DynamicPseudoType, hcl.Diagnostics{{ | ||
82 | Severity: hcl.DiagError, | ||
83 | Summary: invalidTypeSummary, | ||
84 | Detail: fmt.Sprintf("Primitive type keyword %q does not expect arguments.", call.Name), | ||
85 | Subject: &call.ArgsRange, | ||
86 | }} | ||
87 | } | ||
88 | |||
89 | if len(call.Arguments) != 1 { | ||
90 | contextRange := call.ArgsRange | ||
91 | subjectRange := call.ArgsRange | ||
92 | if len(call.Arguments) > 1 { | ||
93 | // If we have too many arguments (as opposed to too _few_) then | ||
94 | // we'll highlight the extraneous arguments as the diagnostic | ||
95 | // subject. | ||
96 | subjectRange = hcl.RangeBetween(call.Arguments[1].Range(), call.Arguments[len(call.Arguments)-1].Range()) | ||
97 | } | ||
98 | |||
99 | switch call.Name { | ||
100 | case "list", "set", "map": | ||
101 | return cty.DynamicPseudoType, hcl.Diagnostics{{ | ||
102 | Severity: hcl.DiagError, | ||
103 | Summary: invalidTypeSummary, | ||
104 | Detail: fmt.Sprintf("The %s type constructor requires one argument specifying the element type.", call.Name), | ||
105 | Subject: &subjectRange, | ||
106 | Context: &contextRange, | ||
107 | }} | ||
108 | case "object": | ||
109 | return cty.DynamicPseudoType, hcl.Diagnostics{{ | ||
110 | Severity: hcl.DiagError, | ||
111 | Summary: invalidTypeSummary, | ||
112 | Detail: "The object type constructor requires one argument specifying the attribute types and values as a map.", | ||
113 | Subject: &subjectRange, | ||
114 | Context: &contextRange, | ||
115 | }} | ||
116 | case "tuple": | ||
117 | return cty.DynamicPseudoType, hcl.Diagnostics{{ | ||
118 | Severity: hcl.DiagError, | ||
119 | Summary: invalidTypeSummary, | ||
120 | Detail: "The tuple type constructor requires one argument specifying the element types as a list.", | ||
121 | Subject: &subjectRange, | ||
122 | Context: &contextRange, | ||
123 | }} | ||
124 | } | ||
125 | } | ||
126 | |||
127 | switch call.Name { | ||
128 | |||
129 | case "list": | ||
130 | ety, diags := getType(call.Arguments[0], constraint) | ||
131 | return cty.List(ety), diags | ||
132 | case "set": | ||
133 | ety, diags := getType(call.Arguments[0], constraint) | ||
134 | return cty.Set(ety), diags | ||
135 | case "map": | ||
136 | ety, diags := getType(call.Arguments[0], constraint) | ||
137 | return cty.Map(ety), diags | ||
138 | case "object": | ||
139 | attrDefs, diags := hcl.ExprMap(call.Arguments[0]) | ||
140 | if diags.HasErrors() { | ||
141 | return cty.DynamicPseudoType, hcl.Diagnostics{{ | ||
142 | Severity: hcl.DiagError, | ||
143 | Summary: invalidTypeSummary, | ||
144 | Detail: "Object type constructor requires a map whose keys are attribute names and whose values are the corresponding attribute types.", | ||
145 | Subject: call.Arguments[0].Range().Ptr(), | ||
146 | Context: expr.Range().Ptr(), | ||
147 | }} | ||
148 | } | ||
149 | |||
150 | atys := make(map[string]cty.Type) | ||
151 | for _, attrDef := range attrDefs { | ||
152 | attrName := hcl.ExprAsKeyword(attrDef.Key) | ||
153 | if attrName == "" { | ||
154 | diags = append(diags, &hcl.Diagnostic{ | ||
155 | Severity: hcl.DiagError, | ||
156 | Summary: invalidTypeSummary, | ||
157 | Detail: "Object constructor map keys must be attribute names.", | ||
158 | Subject: attrDef.Key.Range().Ptr(), | ||
159 | Context: expr.Range().Ptr(), | ||
160 | }) | ||
161 | continue | ||
162 | } | ||
163 | aty, attrDiags := getType(attrDef.Value, constraint) | ||
164 | diags = append(diags, attrDiags...) | ||
165 | atys[attrName] = aty | ||
166 | } | ||
167 | return cty.Object(atys), diags | ||
168 | case "tuple": | ||
169 | elemDefs, diags := hcl.ExprList(call.Arguments[0]) | ||
170 | if diags.HasErrors() { | ||
171 | return cty.DynamicPseudoType, hcl.Diagnostics{{ | ||
172 | Severity: hcl.DiagError, | ||
173 | Summary: invalidTypeSummary, | ||
174 | Detail: "Tuple type constructor requires a list of element types.", | ||
175 | Subject: call.Arguments[0].Range().Ptr(), | ||
176 | Context: expr.Range().Ptr(), | ||
177 | }} | ||
178 | } | ||
179 | etys := make([]cty.Type, len(elemDefs)) | ||
180 | for i, defExpr := range elemDefs { | ||
181 | ety, elemDiags := getType(defExpr, constraint) | ||
182 | diags = append(diags, elemDiags...) | ||
183 | etys[i] = ety | ||
184 | } | ||
185 | return cty.Tuple(etys), diags | ||
186 | default: | ||
187 | // Can't access call.Arguments in this path because we've not validated | ||
188 | // that it contains exactly one expression here. | ||
189 | return cty.DynamicPseudoType, hcl.Diagnostics{{ | ||
190 | Severity: hcl.DiagError, | ||
191 | Summary: invalidTypeSummary, | ||
192 | Detail: fmt.Sprintf("Keyword %q is not a valid type constructor.", call.Name), | ||
193 | Subject: expr.Range().Ptr(), | ||
194 | }} | ||
195 | } | ||
196 | } | ||
diff --git a/vendor/github.com/hashicorp/hcl2/ext/typeexpr/public.go b/vendor/github.com/hashicorp/hcl2/ext/typeexpr/public.go new file mode 100644 index 0000000..e3f5eef --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/ext/typeexpr/public.go | |||
@@ -0,0 +1,129 @@ | |||
1 | package typeexpr | ||
2 | |||
3 | import ( | ||
4 | "bytes" | ||
5 | "fmt" | ||
6 | "sort" | ||
7 | |||
8 | "github.com/hashicorp/hcl2/hcl/hclsyntax" | ||
9 | |||
10 | "github.com/hashicorp/hcl2/hcl" | ||
11 | "github.com/zclconf/go-cty/cty" | ||
12 | ) | ||
13 | |||
14 | // Type attempts to process the given expression as a type expression and, if | ||
15 | // successful, returns the resulting type. If unsuccessful, error diagnostics | ||
16 | // are returned. | ||
17 | func Type(expr hcl.Expression) (cty.Type, hcl.Diagnostics) { | ||
18 | return getType(expr, false) | ||
19 | } | ||
20 | |||
21 | // TypeConstraint attempts to parse the given expression as a type constraint | ||
22 | // and, if successful, returns the resulting type. If unsuccessful, error | ||
23 | // diagnostics are returned. | ||
24 | // | ||
25 | // A type constraint has the same structure as a type, but it additionally | ||
26 | // allows the keyword "any" to represent cty.DynamicPseudoType, which is often | ||
27 | // used as a wildcard in type checking and type conversion operations. | ||
28 | func TypeConstraint(expr hcl.Expression) (cty.Type, hcl.Diagnostics) { | ||
29 | return getType(expr, true) | ||
30 | } | ||
31 | |||
32 | // TypeString returns a string rendering of the given type as it would be | ||
33 | // expected to appear in the HCL native syntax. | ||
34 | // | ||
35 | // This is primarily intended for showing types to the user in an application | ||
36 | // that uses typexpr, where the user can be assumed to be familiar with the | ||
37 | // type expression syntax. In applications that do not use typeexpr these | ||
38 | // results may be confusing to the user and so type.FriendlyName may be | ||
39 | // preferable, even though it's less precise. | ||
40 | // | ||
41 | // TypeString produces reasonable results only for types like what would be | ||
42 | // produced by the Type and TypeConstraint functions. In particular, it cannot | ||
43 | // support capsule types. | ||
44 | func TypeString(ty cty.Type) string { | ||
45 | // Easy cases first | ||
46 | switch ty { | ||
47 | case cty.String: | ||
48 | return "string" | ||
49 | case cty.Bool: | ||
50 | return "bool" | ||
51 | case cty.Number: | ||
52 | return "number" | ||
53 | case cty.DynamicPseudoType: | ||
54 | return "any" | ||
55 | } | ||
56 | |||
57 | if ty.IsCapsuleType() { | ||
58 | panic("TypeString does not support capsule types") | ||
59 | } | ||
60 | |||
61 | if ty.IsCollectionType() { | ||
62 | ety := ty.ElementType() | ||
63 | etyString := TypeString(ety) | ||
64 | switch { | ||
65 | case ty.IsListType(): | ||
66 | return fmt.Sprintf("list(%s)", etyString) | ||
67 | case ty.IsSetType(): | ||
68 | return fmt.Sprintf("set(%s)", etyString) | ||
69 | case ty.IsMapType(): | ||
70 | return fmt.Sprintf("map(%s)", etyString) | ||
71 | default: | ||
72 | // Should never happen because the above is exhaustive | ||
73 | panic("unsupported collection type") | ||
74 | } | ||
75 | } | ||
76 | |||
77 | if ty.IsObjectType() { | ||
78 | var buf bytes.Buffer | ||
79 | buf.WriteString("object({") | ||
80 | atys := ty.AttributeTypes() | ||
81 | names := make([]string, 0, len(atys)) | ||
82 | for name := range atys { | ||
83 | names = append(names, name) | ||
84 | } | ||
85 | sort.Strings(names) | ||
86 | first := true | ||
87 | for _, name := range names { | ||
88 | aty := atys[name] | ||
89 | if !first { | ||
90 | buf.WriteByte(',') | ||
91 | } | ||
92 | if !hclsyntax.ValidIdentifier(name) { | ||
93 | // Should never happen for any type produced by this package, | ||
94 | // but we'll do something reasonable here just so we don't | ||
95 | // produce garbage if someone gives us a hand-assembled object | ||
96 | // type that has weird attribute names. | ||
97 | // Using Go-style quoting here isn't perfect, since it doesn't | ||
98 | // exactly match HCL syntax, but it's fine for an edge-case. | ||
99 | buf.WriteString(fmt.Sprintf("%q", name)) | ||
100 | } else { | ||
101 | buf.WriteString(name) | ||
102 | } | ||
103 | buf.WriteByte('=') | ||
104 | buf.WriteString(TypeString(aty)) | ||
105 | first = false | ||
106 | } | ||
107 | buf.WriteString("})") | ||
108 | return buf.String() | ||
109 | } | ||
110 | |||
111 | if ty.IsTupleType() { | ||
112 | var buf bytes.Buffer | ||
113 | buf.WriteString("tuple([") | ||
114 | etys := ty.TupleElementTypes() | ||
115 | first := true | ||
116 | for _, ety := range etys { | ||
117 | if !first { | ||
118 | buf.WriteByte(',') | ||
119 | } | ||
120 | buf.WriteString(TypeString(ety)) | ||
121 | first = false | ||
122 | } | ||
123 | buf.WriteString("])") | ||
124 | return buf.String() | ||
125 | } | ||
126 | |||
127 | // Should never happen because we covered all cases above. | ||
128 | panic(fmt.Errorf("unsupported type %#v", ty)) | ||
129 | } | ||