diff options
Diffstat (limited to 'vendor/github.com/hashicorp/hcl2/ext/dynblock/README.md')
-rw-r--r-- | vendor/github.com/hashicorp/hcl2/ext/dynblock/README.md | 184 |
1 files changed, 184 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. | ||