In additional to wrapping the Cloudformation API / workflow, iidy provides an optional YAML pre-processor, which
- allows CloudFormation templates to be abstracted and simplified in ways not possible with vanilla CloudFormation templates
- allows values to be imported from a range of sources, including local files, s3, https, ParameterStore, and environment variables. It parses JSON or YAML imports. The imported data can be stitched into the target template as YAML subtrees, as literal strings, or as single values.
- can be used to define custom resource templates that expand out to a set of real AWS resources. These templates can be parameterized and can even do input validation on the parameters. The template expansion and validation happens as a pre-process step prior to invoking CloudFormation on the rendered output.
It can be used on CloudFormation template files, stack-args.yaml
,
and any other YAML file. The pre-processor is applied automatically to
stack-args.yaml
and optionally to CloudFormation templates by
prefixing the Template value in stack-args.yaml
with render:
as
shown below.
StackName: iidy-demo
Template: "render:./cfn-template.yaml"
Parameters:
Foo: bar
The pre-processor can also be invoked directly on any YAML file via the
iidy render
cli command.
The pre-processor language is valid YAML with some custom
tags. These tags all start with
the characters !$
. There are also a few special map keys, starting
with $
. Both sets of YAML extensions are explained below.
This documentation assumes you already know YAML syntax well. See https://en.wikipedia.org/wiki/YAML#Syntax or http://www.yaml.org/spec/1.2/spec.html for help.
Each YAML document is treated as a module with a distinct namespace.
Values in a namespace are defined via the $defs:
entry at the root
of the document or are imported via the $imports:
entry.
These values can be accessed and stitched into the output using either
the !$
(pronounced "include") custom tag or with handlebarsjs syntax
inside a string entry.
# input document
$defs:
hello: "world"
output:
by-tag: !$ hello
by-handlebars: "{{hello}}"
escaped-handlebars: "\\{{hello}}"
---
# output document
output:
by-tag: "world"
by-handlebars: "world"
escaped-handlebars: "{{hello}}"
Note that the braces used with the handlebars syntax must be enclosed in a quoted string or the YAML parser will treat them as a YAML map node.
If the value being included is a map or list rather than a simple scalar value, it is spliced into the output rather than being collapsed in any way.
# input document
$defs:
a-list:
- 1
- 2
a-map:
k1: v1
k2: v2
a-string: "foo"
a-number: 1
a-bool: false
output:
a-list: !$ a-list
a-map: !$ a-map
a-string: !$ a-string
a-number: !$ a-number
a-bool: !$ a-bool
---
# output document
output:
a-list:
- 1
- 2
a-map:
k1: v1
k2: v2
a-string: "foo"
a-number: 1
a-bool: false
The entries in $defs:
may use values from $imports:
and an import
source may contain handlebars syntax which refers to either values
that have come from $defs
or from previous $imports
.
For example, values
in this set of imports depends on other
:
$imports:
other: "env:other:default"
values: "{{other}}.yaml"
This example is simillar to the previous but other
is defined as a fixed value.
$defs:
other: blah
$imports:
values: "{{other}}.yaml"
Imports are specified per YAML document under the special
document-level key $imports
. Its value is a map of import names to
import sources.
For example, if we had the following file:
# names.yaml
mundi: world
#...
We could import it into another file and use the values it contains:
# input document
$imports:
# <an arbitrary import name>: <importSource>
names: ./names.yaml
output:
hello: !$ names.mundi
---
# output document
outputs:
hello: world
See Importing Data for a complete list of import sources.
If you're using cloudformation dynamic
references such as
{{resolve:secretsmanager:MyRDSSecret:SecretString:username}}
you'll need to escape the double braces with a double
backslash to prevent iidy
from interpreting them as the start of a handlebars expression.
Handlebars string: aYamlKey: "{{blah}}"
Handlebars string: aYamlKey: "\\{{blah}}"
See issue #260 for a longer example.
The string helpers from
handlebars-helpers (such
as titleize
, trim
, reverse
, etc) are included for basic string
manipulation in addition to the following helpers.
$defs:
a:
b: 9
out: '{{toJson a}}'
out: '{"b":9}'
tojson
is an alias of toJson
and is deprecated.
$defs:
a:
b:
c: 9
out: '{{toJsonPretty a}}'
out: '{\n \"b\": {\n \"c\": 9\n }\n}'
tojsonPretty
is an alias of toJsonPretty
and is deprecated.
$defs:
a:
b: 9
out: '{{toYaml a}}'
out: 'b: 9\n'
toyaml
is an alias of toYaml
and is deprecated.
$defs:
a: ABC
out: '{{toLowerCase a}}'
out: 'abc'
$defs:
a: abc
out: '{{toUpperCase a}}'
out: 'ABC'
$defs:
a: abc
out: '{{base64 a}}'
out: 'YWJj'
# !$if { test: bool, then: ~ , else: ~ }
thing: !$if
test: true
then: Do the thing
else: Don't do the thing
# thing: Do the thing
# !$eq [a, b]
thing: !$if
test: !$eq ['thing', 'thing']
then: They're the same
else: They're not the same
# thing: They're the same
# !$not bool
thing: !$if
test: !$not true
then: Don't do the thing
else: Do the thing
# thing: Do the thing
!$concat
concatenate lists of lists
# !$concat [a, b, ...]
things: !$concat
- [a, b]
- [c, d]
- [e, f]
# things:
# - a
# - b
# - c
# - d
# - e
# - f
!$merge
merge a list of maps together, similar to lodash_.merge
# !$merge [{}, {}, ...]
things: !$merge
- { a: 1 }
- { b: 2 }
- { c: 3 }
# things:
# a: 1
# b: 2
# c: 3
!$fromPairs
convert a list of pairs into a map
# !$fromPairs [{ key: a, value: 1}, { key: b, value: 2}]
things: !$fromPairs
- { key: a, value: 1 }
- { key: b, value: 2 }
- { key: c, value: 3 }
# things:
# a: 1
# b: 2
# c: 3
!$map
map a YAML template over a list of input arguments
# !$map { template: {}, items: [], var: 'item', filter: ~ }
people: !$map
items:
- first: Linus
last: Torvalds
- first: Grace
last: Hopper
template:
name: '{{ item.first }} {{ item.last }}'
# people:
# - name: Linus Torvalds
# - name: Grace Hopper
people: !$map
var: person
items:
- first: Linus
last: Torvalds
- first: Grace
last: Hopper
template:
name: '{{ person.first }} {{ person.last }}'
# people:
# - name: Linus Torvalds
# - name: Grace Hopper
people: !$map
filter: !$eq ['Linus', !$ person.first]
var: person
items:
- first: Linus
last: Torvalds
- first: Grace
last: Hopper
template:
name: '{{ person.first }} {{ person.last }}'
# people:
# - name: Linus Torvalds
!$concatMap
same as!$map
followed by!$concat
on the output
# !$concatMap { template: {}, items: [], var: 'item', filter: ~ }
$defs:
letters: [A, B, C]
numbers: [1, 2, 3]
things: !$concatMap
items: !$ letters
var: letter
template: !$map
items: !$ numbers
var: number
template: '{{ letter }}{{ number }}'
# things:
# - A1
# - A2
# - A3
# - B1
# - B2
# - B3
# - C1
# - C2
# - C3
!$mergeMap
same as!$map
followed by!$merge
on the output
# !$mergeMap { template: {}, items: [], var: 'item', filter: ~ }
people: !$mergeMap
items:
- first: Linus
last: Torvalds
- first: Grace
last: Hopper
template:
'{{ item.first }}': '{{ item.last }}'
# people:
# Linus: Torvalds
# Grace: Hopper
# !$mapListToHash { template: { key: ~, value: ~ }, items: [], var: 'item', filter: ~ }
things: !$mapListToHash
items:
- [a, 1]
- [b, 2]
template:
key: !$ item.0
value: !$ item.1
# things:
# a: 1
# b: 2
# !$mapValues { template: {}, items: [], var: 'item', filter: ~ }
things: !$mapValues
items:
a: 1
b: 2
template: !$ item
# things:
# a:
# value: 1
# key: a
# b:
# value: 2
# key: b
!$groupBy
similar to lodash_.merge
# !$groupBy { template: {}, items: [], var: 'item', filter: ~ }
people: !$groupBy
key: !$ item.company
items:
- name: Ken Tompson
company: Bell
- name: Margaret Hamilton
company: NASA
- name: Dennis Ritchie
company: Bell
template: !$ item.name
# people:
# Bell:
# - Ken Tompson
# - Dennis Ritchie
# NASA:
# - Margaret Hamilton
!$split
split a string into a list
# !$split [delimiter, string]
`!$split` can be used in places where CloudFormation does not allow its own `!Split` function, or when working with non-CloudFormation documents.
things: !$split
- ', '
- a, b
# things:
# - a
# - b
!$join
join a list into a string
!$join
can be used in places where CloudFormation does not allow its own !Join
function, or when working with non-CloudFormation documents.
# !$join [delimiter, strings]
things: !$join
- ', '
- - a
- b
# things: a, b
!$parseYaml
parse a string
# !$parseYaml string
things: !$parseYaml "[a,b,c]"
# things:
# - a
# - b
# - c
!$escape
prevent iidy from doing any pre-processing on the child tree
# !$escape {}
things: !$escape { a: b }
# things:
# a: b
things: !$escape '{{ a }}'
# things: '{{ a }}'
things: !$escape
- !$ a
# things:
# - !$ a
!$string
convert data to a YAML string!$toYamlString
is an alias of!$string
# !$string {}
things: !$string
a: b
# things: |
# a: b
!$parseYaml
parse YAML string, opposite of!$string
# !$parseYaml {}
things: !$parseYaml "a: b\n"
# things:
# a: b
!$toJsonString
convert data to a JSON string
# !$toJsonString {}
things: !$toJsonString
a: b
# things: '{"a":"b"}'
!$parseJson
parse JSON string, opposite of!$toJsonString
# !$parseJson {}
things: !$parseJson '{ "a":"b" }'
# things:
# a: b
!$let
local variable binding
# !$let { in: {}, ...bindings }
people: !$let
first: Linus
last: Torvalds
in:
fullName: '{{ first }} {{ last }}'
# people:
# fullName: Linus Torvalds