2018-02-03 23:37:11 +00:00
|
|
|
# `hcldec` spec format
|
|
|
|
|
|
|
|
The `hcldec` spec format instructs [`hcldec`](README.md) on how to validate
|
|
|
|
one or more configuration files given in the HCL syntax and how to translate
|
|
|
|
the result into JSON format.
|
|
|
|
|
|
|
|
The spec format is itself built from HCL syntax, with each HCL block serving
|
|
|
|
as a _spec_ whose block type and contents together describe a single mapping
|
|
|
|
action and, in most cases, a validation constraint. Each spec block produces
|
|
|
|
one JSON value.
|
|
|
|
|
|
|
|
A spec _file_ must have a single top-level spec block that describes the
|
|
|
|
top-level JSON value `hcldec` will return, and that spec block may have other
|
|
|
|
nested spec blocks (depending on its type) that produce nested structures and
|
|
|
|
additional validation constraints.
|
|
|
|
|
|
|
|
The most common usage of `hcldec` is to produce a JSON object whose properties
|
|
|
|
are derived from the top-level content of the input file. In this case, the
|
|
|
|
root of the given spec file will have an `object` spec block whose contents
|
|
|
|
describe how each of the object's properties are to be populated using
|
|
|
|
nested spec blocks.
|
|
|
|
|
|
|
|
Each spec is evaluated in the context of an HCL _body_, which is the HCL
|
|
|
|
terminology for one level of nesting in a configuration file. The top-level
|
|
|
|
objects in a file all belong to the root body of that file, and then each
|
|
|
|
nested block has its own body containing the elements within that block.
|
|
|
|
Some spec types select a new body as the context for their nested specs,
|
|
|
|
allowing nested HCL structures to be decoded.
|
|
|
|
|
|
|
|
## Spec Block Types
|
|
|
|
|
|
|
|
The following sections describe the different block types that can be used to
|
|
|
|
define specs within a spec file.
|
|
|
|
|
|
|
|
### `object` spec blocks
|
|
|
|
|
|
|
|
The `object` spec type is the most commonly used at the root of a spec file.
|
|
|
|
Its result is a JSON object whose properties are set based on any nested
|
|
|
|
spec blocks:
|
|
|
|
|
|
|
|
```hcl
|
|
|
|
object {
|
|
|
|
attr "name" {
|
2019-07-22 19:49:22 +00:00
|
|
|
type = string
|
2018-02-03 23:37:11 +00:00
|
|
|
}
|
|
|
|
block "address" {
|
|
|
|
object {
|
|
|
|
attr "street" {
|
2019-07-22 19:49:22 +00:00
|
|
|
type = string
|
2018-02-03 23:37:11 +00:00
|
|
|
}
|
|
|
|
# ...
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Nested spec blocks inside `object` must always have an extra block label
|
|
|
|
`"name"`, `"address"` and `"street"` in the above example) that specifies
|
|
|
|
the name of the property that should be created in the JSON object result.
|
|
|
|
This label also acts as a default name selector for the nested spec, allowing
|
|
|
|
the `attr` blocks in the above example to omit the usually-required `name`
|
|
|
|
argument in cases where the HCL input name and JSON output name are the same.
|
|
|
|
|
|
|
|
An `object` spec block creates no validation constraints, but it passes on
|
|
|
|
any validation constraints created by the nested specs.
|
|
|
|
|
|
|
|
### `array` spec blocks
|
|
|
|
|
|
|
|
The `array` spec type produces a JSON array whose elements are set based on
|
|
|
|
any nested spec blocks:
|
|
|
|
|
|
|
|
```hcl
|
|
|
|
array {
|
|
|
|
attr {
|
|
|
|
name = "first_element"
|
2019-07-22 19:49:22 +00:00
|
|
|
type = string
|
2018-02-03 23:37:11 +00:00
|
|
|
}
|
|
|
|
attr {
|
|
|
|
name = "second_element"
|
2019-07-22 19:49:22 +00:00
|
|
|
type = string
|
2018-02-03 23:37:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
An `array` spec block creates no validation constraints, but it passes on
|
|
|
|
any validation constraints created by the nested specs.
|
|
|
|
|
|
|
|
### `attr` spec blocks
|
|
|
|
|
|
|
|
The `attr` spec type reads the value of an attribute in the current body
|
|
|
|
and returns that value as its result. It also creates validation constraints
|
|
|
|
for the given attribute name and its value.
|
|
|
|
|
|
|
|
```hcl
|
|
|
|
attr {
|
|
|
|
name = "document_root"
|
|
|
|
type = string
|
|
|
|
required = true
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
`attr` spec blocks accept the following arguments:
|
|
|
|
|
|
|
|
* `name` (required) - The attribute name to expect within the HCL input file.
|
|
|
|
This may be omitted when a default name selector is created by a parent
|
|
|
|
`object` spec, if the input attribute name should match the output JSON
|
|
|
|
object property name.
|
|
|
|
|
|
|
|
* `type` (optional) - A [type expression](#type-expressions) that the given
|
|
|
|
attribute value must conform to. If this argument is set, `hcldec` will
|
|
|
|
automatically convert the given input value to this type or produce an
|
|
|
|
error if that is not possible.
|
|
|
|
|
|
|
|
* `required` (optional) - If set to `true`, `hcldec` will produce an error
|
|
|
|
if a value is not provided for the source attribute.
|
|
|
|
|
|
|
|
`attr` is a leaf spec type, so no nested spec blocks are permitted.
|
|
|
|
|
|
|
|
### `block` spec blocks
|
|
|
|
|
|
|
|
The `block` spec type applies one nested spec block to the contents of a
|
|
|
|
block within the current body and returns the result of that spec. It also
|
|
|
|
creates validation constraints for the given block type name.
|
|
|
|
|
|
|
|
```hcl
|
|
|
|
block {
|
|
|
|
block_type = "logging"
|
|
|
|
|
|
|
|
object {
|
|
|
|
attr "level" {
|
|
|
|
type = string
|
|
|
|
}
|
|
|
|
attr "file" {
|
|
|
|
type = string
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
`block` spec blocks accept the following arguments:
|
|
|
|
|
|
|
|
* `block_type` (required) - The block type name to expect within the HCL
|
|
|
|
input file. This may be omitted when a default name selector is created
|
|
|
|
by a parent `object` spec, if the input block type name should match the
|
|
|
|
output JSON object property name.
|
|
|
|
|
|
|
|
* `required` (optional) - If set to `true`, `hcldec` will produce an error
|
|
|
|
if a block of the specified type is not present in the current body.
|
|
|
|
|
|
|
|
`block` creates a validation constraint that there must be zero or one blocks
|
|
|
|
of the given type name, or exactly one if `required` is set.
|
|
|
|
|
|
|
|
`block` expects a single nested spec block, which is applied to the body of
|
|
|
|
the block of the given type when it is present.
|
|
|
|
|
|
|
|
### `block_list` spec blocks
|
|
|
|
|
|
|
|
The `block_list` spec type is similar to `block`, but it accepts zero or
|
|
|
|
more blocks of a specified type rather than requiring zero or one. The
|
|
|
|
result is a JSON array with one entry per block of the given type.
|
|
|
|
|
|
|
|
```hcl
|
|
|
|
block_list {
|
|
|
|
block_type = "log_file"
|
|
|
|
|
|
|
|
object {
|
|
|
|
attr "level" {
|
|
|
|
type = string
|
|
|
|
}
|
|
|
|
attr "filename" {
|
|
|
|
type = string
|
|
|
|
required = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
`block_list` spec blocks accept the following arguments:
|
|
|
|
|
|
|
|
* `block_type` (required) - The block type name to expect within the HCL
|
|
|
|
input file. This may be omitted when a default name selector is created
|
|
|
|
by a parent `object` spec, if the input block type name should match the
|
|
|
|
output JSON object property name.
|
|
|
|
|
|
|
|
* `min_items` (optional) - If set to a number greater than zero, `hcldec` will
|
|
|
|
produce an error if fewer than the given number of blocks are present.
|
|
|
|
|
|
|
|
* `max_items` (optional) - If set to a number greater than zero, `hcldec` will
|
|
|
|
produce an error if more than the given number of blocks are present. This
|
|
|
|
attribute must be greater than or equal to `min_items` if both are set.
|
|
|
|
|
|
|
|
`block` creates a validation constraint on the number of blocks of the given
|
|
|
|
type that must be present.
|
|
|
|
|
|
|
|
`block` expects a single nested spec block, which is applied to the body of
|
|
|
|
each matching block to produce the resulting list items.
|
|
|
|
|
|
|
|
### `block_set` spec blocks
|
|
|
|
|
|
|
|
The `block_set` spec type behaves the same as `block_list` except that
|
|
|
|
the result is in no specific order and any duplicate items are removed.
|
|
|
|
|
|
|
|
```hcl
|
|
|
|
block_set {
|
|
|
|
block_type = "log_file"
|
|
|
|
|
|
|
|
object {
|
|
|
|
attr "level" {
|
|
|
|
type = string
|
|
|
|
}
|
|
|
|
attr "filename" {
|
|
|
|
type = string
|
|
|
|
required = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
The contents of `block_set` are the same as for `block_list`.
|
|
|
|
|
|
|
|
### `block_map` spec blocks
|
|
|
|
|
|
|
|
The `block_map` spec type is similar to `block`, but it accepts zero or
|
|
|
|
more blocks of a specified type rather than requiring zero or one. The
|
|
|
|
result is a JSON object, or possibly multiple nested JSON objects, whose
|
|
|
|
properties are derived from the labels set on each matching block.
|
|
|
|
|
|
|
|
```hcl
|
|
|
|
block_map {
|
|
|
|
block_type = "log_file"
|
|
|
|
labels = ["filename"]
|
|
|
|
|
|
|
|
object {
|
|
|
|
attr "level" {
|
|
|
|
type = string
|
|
|
|
required = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
`block_map` spec blocks accept the following arguments:
|
|
|
|
|
|
|
|
* `block_type` (required) - The block type name to expect within the HCL
|
|
|
|
input file. This may be omitted when a default name selector is created
|
|
|
|
by a parent `object` spec, if the input block type name should match the
|
|
|
|
output JSON object property name.
|
|
|
|
|
|
|
|
* `labels` (required) - A list of user-oriented block label names. Each entry
|
|
|
|
in this list creates one level of object within the output value, and
|
|
|
|
requires one additional block header label on any child block of this type.
|
|
|
|
Block header labels are the quoted strings that appear after the block type
|
|
|
|
name but before the opening `{`.
|
|
|
|
|
|
|
|
`block` creates a validation constraint on the number of labels that blocks
|
|
|
|
of the given type must have.
|
|
|
|
|
|
|
|
`block` expects a single nested spec block, which is applied to the body of
|
|
|
|
each matching block to produce the resulting map items.
|
|
|
|
|
2018-08-09 23:53:16 +00:00
|
|
|
## `block_attrs` spec blocks
|
|
|
|
|
|
|
|
The `block_attrs` spec type is similar to an `attr` spec block of a map type,
|
|
|
|
but it produces a map from the attributes of a block rather than from an
|
|
|
|
attribute's expression.
|
|
|
|
|
|
|
|
```hcl
|
|
|
|
block_attrs {
|
|
|
|
block_type = "variables"
|
|
|
|
element_type = string
|
|
|
|
required = false
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
This allows a map with user-defined keys to be produced within block syntax,
|
|
|
|
but due to the constraints of that syntax it also means that the user will
|
|
|
|
be unable to dynamically-generate either individual key names using key
|
|
|
|
expressions or the entire map value using a `for` expression.
|
|
|
|
|
|
|
|
`block_attrs` spec blocks accept the following arguments:
|
|
|
|
|
|
|
|
* `block_type` (required) - The block type name to expect within the HCL
|
|
|
|
input file. This may be omitted when a default name selector is created
|
|
|
|
by a parent `object` spec, if the input block type name should match the
|
|
|
|
output JSON object property name.
|
|
|
|
|
|
|
|
* `element_type` (required) - The value type to require for each of the
|
|
|
|
attributes within a matched block. The resulting value will be a JSON
|
|
|
|
object whose property values are of this type.
|
|
|
|
|
|
|
|
* `required` (optional) - If `true`, an error will be produced if a block
|
|
|
|
of the given type is not present. If `false` -- the default -- an absent
|
|
|
|
block will be indicated by producing `null`.
|
|
|
|
|
2018-02-03 23:37:11 +00:00
|
|
|
## `literal` spec blocks
|
|
|
|
|
|
|
|
The `literal` spec type returns a given literal value, and creates no
|
|
|
|
validation constraints. It is most commonly used with the `default` spec
|
|
|
|
type to create a fallback value, but can also be used e.g. to fill out
|
|
|
|
required properties in an `object` spec that do not correspond to any
|
|
|
|
construct in the input configuration.
|
|
|
|
|
|
|
|
```hcl
|
|
|
|
literal {
|
|
|
|
value = "hello world"
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
`literal` spec blocks accept the following argument:
|
|
|
|
|
2018-02-04 18:33:35 +00:00
|
|
|
* `value` (required) - The value to return. This attribute may be an expression
|
2018-02-04 19:05:23 +00:00
|
|
|
that uses [functions](#spec-definition-functions).
|
2018-02-03 23:37:11 +00:00
|
|
|
|
|
|
|
`literal` is a leaf spec type, so no nested spec blocks are permitted.
|
|
|
|
|
|
|
|
## `default` spec blocks
|
|
|
|
|
|
|
|
The `default` spec type evaluates a sequence of nested specs in turn and
|
|
|
|
returns the result of the first one that produces a non-null value.
|
|
|
|
It creates no validation constraints of its own, but passes on the validation
|
|
|
|
constraints from its first nested block.
|
|
|
|
|
|
|
|
```hcl
|
|
|
|
default {
|
|
|
|
attr {
|
|
|
|
name = "private"
|
|
|
|
type = bool
|
|
|
|
}
|
|
|
|
literal {
|
|
|
|
value = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
A `default` spec block must have at least one nested spec block, and should
|
|
|
|
generally have at least two since otherwise the `default` wrapper is a no-op.
|
|
|
|
|
|
|
|
The second and any subsequent spec blocks are _fallback_ specs. These exhibit
|
|
|
|
their usual behavior but are not able to impose validation constraints on the
|
|
|
|
current body since they are not evaluated unless all prior specs produce
|
|
|
|
`null` as their result.
|
|
|
|
|
2018-02-04 17:59:20 +00:00
|
|
|
## `transform` spec blocks
|
|
|
|
|
|
|
|
The `transform` spec type evaluates one nested spec and then evaluates a given
|
|
|
|
expression with that nested spec result to produce a final value.
|
|
|
|
It creates no validation constraints of its own, but passes on the validation
|
|
|
|
constraints from its nested block.
|
|
|
|
|
|
|
|
```hcl
|
|
|
|
transform {
|
|
|
|
attr {
|
|
|
|
name = "size_in_mb"
|
|
|
|
type = number
|
|
|
|
}
|
|
|
|
|
|
|
|
# Convert result to a size in bytes
|
|
|
|
result = nested * 1024 * 1024
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
`transform` spec blocks accept the following argument:
|
|
|
|
|
|
|
|
* `result` (required) - The expression to evaluate on the result of the nested
|
|
|
|
spec. The variable `nested` is defined when evaluating this expression, with
|
|
|
|
the result value of the nested spec.
|
|
|
|
|
2018-02-04 19:05:23 +00:00
|
|
|
The `result` expression may use [functions](#spec-definition-functions).
|
2018-02-04 18:33:35 +00:00
|
|
|
|
2018-02-04 19:05:23 +00:00
|
|
|
## Predefined Variables
|
|
|
|
|
|
|
|
`hcldec` accepts values for variables to expose into the input file's
|
|
|
|
expression scope as CLI options, and this is the most common way to pass
|
|
|
|
values since it allows them to be dynamically populated by the calling
|
|
|
|
application.
|
|
|
|
|
|
|
|
However, it's also possible to pre-define variables with constant values
|
|
|
|
within a spec file, using the top-level `variables` block type:
|
|
|
|
|
|
|
|
```hcl
|
|
|
|
variables {
|
|
|
|
name = "Stephen"
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Variables of the same name defined via the `hcldec` command line with override
|
|
|
|
predefined variables of the same name, so this mechanism can also be used to
|
|
|
|
provide defaults for variables that are overridden only in certain contexts.
|
|
|
|
|
|
|
|
## Custom Functions
|
|
|
|
|
|
|
|
The spec can make arbitrary HCL functions available in the input file's
|
|
|
|
expression scope, and thus allow simple computation within the input file,
|
|
|
|
in addition to HCL's built-in operators.
|
|
|
|
|
|
|
|
Custom functions are defined in the spec file with the top-level `function`
|
|
|
|
block type:
|
|
|
|
|
|
|
|
```
|
|
|
|
function "add_one" {
|
2018-02-04 19:20:42 +00:00
|
|
|
params = [n]
|
2018-02-04 19:05:23 +00:00
|
|
|
result = n + 1
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Functions behave in a similar way to the `transform` spec type in that the
|
|
|
|
given `result` attribute expression is evaluated with additional variables
|
|
|
|
defined with the same names as the defined `params`.
|
|
|
|
|
|
|
|
The [spec definition functions](#spec-definition-functions) can be used within
|
|
|
|
custom function expressions, allowing them to be optionally exposed into the
|
|
|
|
input file:
|
|
|
|
|
|
|
|
```
|
|
|
|
function "upper" {
|
2018-02-04 19:20:42 +00:00
|
|
|
params = [str]
|
2018-02-04 19:05:23 +00:00
|
|
|
result = upper(str)
|
|
|
|
}
|
2018-02-04 19:20:42 +00:00
|
|
|
|
|
|
|
function "min" {
|
|
|
|
params = []
|
|
|
|
variadic_param = nums
|
|
|
|
result = min(nums...)
|
|
|
|
}
|
2018-02-04 19:05:23 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
Custom functions defined in the spec cannot be called from the spec itself.
|
|
|
|
|
|
|
|
## Spec Definition Functions
|
2018-02-04 18:33:35 +00:00
|
|
|
|
|
|
|
Certain expressions within a specification may use the following functions.
|
|
|
|
The documentation for each spec type above specifies where functions may
|
|
|
|
be used.
|
|
|
|
|
|
|
|
* `abs(number)` returns the absolute (positive) value of the given number.
|
|
|
|
* `coalesce(vals...)` returns the first non-null value given.
|
|
|
|
* `concat(lists...)` concatenates together all of the given lists to produce a new list.
|
|
|
|
* `hasindex(val, idx)` returns true if the expression `val[idx]` could succeed.
|
|
|
|
* `int(number)` returns the integer portion of the given number, rounding towards zero.
|
|
|
|
* `jsondecode(str)` interprets the given string as JSON and returns the resulting data structure.
|
|
|
|
* `jsonencode(val)` returns a JSON-serialized version of the given value.
|
|
|
|
* `length(collection)` returns the number of elements in the given collection (list, set, map, object, or tuple).
|
|
|
|
* `lower(string)` returns the given string with all uppercase letters converted to lowercase.
|
|
|
|
* `max(numbers...)` returns the greatest of the given numbers.
|
|
|
|
* `min(numbers...)` returns the smallest of the given numbers.
|
|
|
|
* `reverse(string)` returns the given string with all of the characters in reverse order.
|
|
|
|
* `strlen(string)` returns the number of characters in the given string.
|
|
|
|
* `substr(string, offset, length)` returns the requested substring of the given string.
|
|
|
|
* `upper(string)` returns the given string with all lowercase letters converted to uppercase.
|
|
|
|
|
2018-02-04 19:05:23 +00:00
|
|
|
Note that these expressions are valid in the context of the _spec_ file, not
|
|
|
|
the _input_. Functions can be exposed into the input file using
|
|
|
|
[Custom Functions](#custom-functions) within the spec, which may in turn
|
|
|
|
refer to these spec definition functions.
|
|
|
|
|
2018-02-03 23:37:11 +00:00
|
|
|
## Type Expressions
|
|
|
|
|
|
|
|
Type expressions are used to describe the expected type of an attribute, as
|
|
|
|
an additional validation constraint.
|
|
|
|
|
|
|
|
A type expression uses primitive type names and compound type constructors.
|
|
|
|
A type constructor builds a new type based on one or more type expression
|
|
|
|
arguments.
|
|
|
|
|
|
|
|
The following type names and type constructors are supported:
|
|
|
|
|
|
|
|
* `any` is a wildcard that accepts a value of any type. (In HCL terms, this
|
|
|
|
is the _dynamic pseudo-type_.)
|
|
|
|
* `string` is a Unicode string.
|
|
|
|
* `number` is an arbitrary-precision floating point number.
|
|
|
|
* `bool` is a boolean value (`true` or `false`)
|
|
|
|
* `list(element_type)` constructs a list type with the given element type
|
|
|
|
* `set(element_type)` constructs a set type with the given element type
|
|
|
|
* `map(element_type)` constructs a map type with the given element type
|
|
|
|
* `object({name1 = element_type, name2 = element_type, ...})` constructs
|
|
|
|
an object type with the given attribute types.
|
|
|
|
* `tuple([element_type, element_type, ...])` constructs a tuple type with
|
|
|
|
the given element types. This can be used, for example, to require an
|
|
|
|
array with a particular number of elements, or with elements of different
|
|
|
|
types.
|
|
|
|
|
|
|
|
The above types are as defined by
|
|
|
|
[the HCL syntax-agnostic information model](../../hcl/spec.md). After
|
|
|
|
validation, values are lowered to JSON's type system, which is a subset
|
|
|
|
of the HCL type system.
|
|
|
|
|
|
|
|
`null` is a valid value of any type, and not a type itself.
|