Indexers using count and for_each
Maeztro recognizes the attributes count
and for_each
on any resource block
as a way of producing 0, 1, or more instances indexed by a number or element in a map or collection.
The syntax is exactly as in Terraform:
count = NUMBER
will result inNUMBER
instances, indexed from 0; the index can be accessed elsewhere in the resource definition usingcount.index
for_each = MAP_SET_OR_LIST
where theMAP_SET_OR_LIST
is iterated through to give the instances; the index key and value can be accessed aseach.key
andeach.value
In Maeztro, this is called the “index definition” for “indexed instances”.
As with Terraform, a for_each
map must have string keys.
A set of strings is treated as an identity map, with both each.key
and each.value
returning the string.
In addition, Maeztro permits lists and sets containing other types,
and in these cases each.value
contains the element and each.key
contains the numeric position of the element, starting at 0.
To facilitate consistent coding, Maeztro also sets each.index
to the numeric position of the element,
and for count
resources, each.{index,key,value}
is set the same as count.index
.
As with Terraform, the indexed instances are referred to by appending [i]
to the usual name for the resource,
where i
is the value of each.key
.
resource maeztro "demo" {
for_each = ["a","b"]
config {
k = each.key
v = each.value
i = each.index
}
}
For example, the code above will create resources maeztro.demo[0]
and maeztro.demo[1]
,
with maeztro.demo[1].k = 1
(and v = "b"
, i = 1
).
If instead for_each = toset(["a","b"])
,
resources maeztro.demo["a"]
and maeztro.demo["b"]
will be created,
with maeztro.demo["b"].k = "b"
(with v
and i
as before).
Maeztro also explicitly represents the “indexer” where the keyword count
or for_each
is defined,
as a resource e.g. maeztro.demo
. The indexed instances, e.g. maeztro.demo["a"]
are treated as members of the indexer.
An index resource
block can be used to define behavior for the indexer, distinct from the indexed resources.
This makes it easy in Maeztro to write workflows at the indexer resource which run over all indexed resources,
via self.members
, as shown below:
resource maeztro "demo" {
for_each = ["a","b","c"]
name = "Indexed Instance: ${upper(each.value)}"
effector "sample" {
steps = [ "return ${each.index}:${each.value}" ]
}
index resource {
name = "Indexer"
effector "sample_everywhere" {
steps = [ "foreach indexed_resource in ${self.members} do invoke-effector sample on ${indexed_resource}" ]
}
}
}
This will create maeztro.demo
as a top-level resource called “Indexer”,
and 3 resources underneath it: maeztro.demo[0]
named “Indexed Instance: A”,
and similar for 1 (B) and 2 (C).
The 3 indexed instances will each have an effector sample
returning e.g. 0:a
,
and the indexer wiill have an effector sample_everywhere
which invokes sample
at each indexed instance.
This makes it easy to apply operations across all indexed resources, with a concurrency level and error checking,
as described here.
Where a resource comes from Terraform, and count
or for_each
is specified there,
Maeztro automatically creates indexer resources in its model.
As with normal Terraform resources, maeztro extend
can be used to extend the definition,
and an index resource
block can be used, and the parent
can be specified for both the indexer and for the indexed instances:
// assume TF code defines aws_instance.vms with a count or for_each
maeztro extend resource "aws_instance.vms" {
name = "VM #${each.index + 1}"
parent = each.index==0 ? maeztro.first : maeztro.others
effector "restart" {
steps = [
{
step: "ssh sudo reboot now",
on-error: "no-op"
}
]
}
index resource {
parent = maeztro.front_end
name = "Indexer"
effector "restart_all" {
steps = [
"invoke-effector restart_first"
{
step: "invoke-effector restart_others"
concurrency: 5
}
]
}
effector "restart_first" {
steps = [ "foreach vm in ${maeztro.first.children} do invoke-effector restart on ${vm}" ]
}
effector "restart_others" {
steps = [ "foreach vm in ${maeztro.others.children} do invoke-effector restart on ${vm}" ]
}
}
}
resource maeztro "front_end" {}
resource maeztro "first" {
parent = "aws_instance.vms"
}
resource maeztro "others" {
parent = "aws_instance.vms"
}
This results in the following arrangement:
Calling restart_all
will restart the first machine and then restart the others 5 at a time.
Other Indexing Types
The Maeztro discovery
and group
types can also create indexed resources,
the partitions in both cases, and the discovered items in the case of the former.
In some ways, these are similar to the use of the count
and for_each
attribute,
but for cases where there there are steps to discovering the resources and/or where there
is heterogeneity or categorization within the items being indexed.
The address notation is identical.
Where it is necessary to distinguish between these different modes of indexing resources, the following vocabulary is used:
- A keyword-based indexer is one where
count
orfor_each
defines the index - A discovery-based indexer is one where the type –
group
ordiscovery
– defines how the resources are indexed - The indexed instances are the resources created by the indexer and their addresses are formed by appending the key in brackets after the indexer’s address
Advanced Details
In some cases it is necessary to understand whether attributes and blocks in the definition apply to the indexer or to the indexed resources or both.
The attributes count
and for_each
apply to the indexer only, as they define the set of indexed instances,
as does the index resource
block if present.
If an index resource
block is not present,
the attribute parent
applies to the indexer (with the indexed instances parented by the indexer),
and name
applies to both.
All other blocks and attributes set in the definition – such as effector
and config
– always apply to the instances and not the indexer.
If the index resource
is present, its content applies only to the indexer, including things like effector
and config
and optionally a name
different to the name of the indexed instances.
When using an index resource
, any parent
for the indexer must be specified in that block,
and the instances can be explicitly parented somewhere else by also using a parent
attribute in the outer definition.
The count
and each
variables can only be used at indexed instances, as they are not be defined for the indexer.
Thus if using them to specify the parent
or name
of indexed instances,
the value for the indexer should be set in the index resource
(or using the can
or try
function).
This pattern can be used to create indexed instances as children resources underneath other indexed or dynamically discovered resources.
Some other best practices and edge cases are:
-
The expression
self.members
is recommended as a universal way to get all indexed instances from a keyword-based indexer, becauseself.children
may include other children (if other resources are assigned the indexer as a parent) and may exclude the indexed instances (if they are explicitly assigned a differentparent
). -
If an
index resource
block is present, it is an error to specify aparent
in the outer definition without also specifying aparent
in theindex resource
. The variablemodule_root
can be used to refer to the root of the module. -
Where Maeztro extends a Terraform resource, it is not permitted to use
for_each
orcount
in Maeztro.