github.com/hashicorp/hcl/v2@v2.20.0/ext/dynblock/README.md (about) 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 possible, 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.