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.