mirror of
https://github.com/opentffoundation/opentf.git
synced 2026-03-27 11:00:18 -04:00
A very common question since we launched the two repetition constructs is how to deal with situations where the input data structure doesn't match one-to-one with the desired configuration. This adds some full worked examples of two common situations that have come up in questions. To avoid adding a lot of extra content to the already-large "expressions" and "resources" pages, the main bulk of this new content lives with the relevant functions themselves as a full example of one thing they are good for, and then we'll link to them from the two general documentation sections where folks are likely to be reading when they encounter the problem.
229 lines
6.1 KiB
Markdown
229 lines
6.1 KiB
Markdown
---
|
|
layout: "functions"
|
|
page_title: "setproduct - Functions - Configuration Language"
|
|
sidebar_current: "docs-funcs-collection-setproduct"
|
|
description: |-
|
|
The setproduct function finds all of the possible combinations of elements
|
|
from all of the given sets by computing the cartesian product.
|
|
---
|
|
|
|
# `setproduct` Function
|
|
|
|
-> **Note:** This page is about Terraform 0.12 and later. For Terraform 0.11 and
|
|
earlier, see
|
|
[0.11 Configuration Language: Interpolation Syntax](../../configuration-0-11/interpolation.html).
|
|
|
|
The `setproduct` function finds all of the possible combinations of elements
|
|
from all of the given sets by computing the
|
|
[Cartesian product](https://en.wikipedia.org/wiki/Cartesian_product).
|
|
|
|
```hcl
|
|
setproduct(sets...)
|
|
```
|
|
|
|
This function is particularly useful for finding the exhaustive set of all
|
|
combinations of members of multiple sets, such as per-application-per-environment
|
|
resources.
|
|
|
|
```
|
|
> setproduct(["development", "staging", "production"], ["app1", "app2"])
|
|
[
|
|
[
|
|
"development",
|
|
"app1",
|
|
],
|
|
[
|
|
"development",
|
|
"app2",
|
|
],
|
|
[
|
|
"staging",
|
|
"app1",
|
|
],
|
|
[
|
|
"staging",
|
|
"app2",
|
|
],
|
|
[
|
|
"production",
|
|
"app1",
|
|
],
|
|
[
|
|
"production",
|
|
"app2",
|
|
],
|
|
]
|
|
```
|
|
|
|
You must past at least two arguments to this function.
|
|
|
|
Although defined primarily for sets, this function can also work with lists.
|
|
If all of the given arguments are lists then the result is a list, preserving
|
|
the ordering of the given lists. Otherwise the result is a set. In either case,
|
|
the result's element type is a list of values corresponding to each given
|
|
argument in turn.
|
|
|
|
## Examples
|
|
|
|
There is an example of the common usage of this function above. There are some
|
|
other situations that are less common when hand-writing but may arise in
|
|
reusable module situations.
|
|
|
|
If any of the arguments is empty then the result is always empty itself,
|
|
similar to how multiplying any number by zero gives zero:
|
|
|
|
```
|
|
> setproduct(["development", "staging", "production"], [])
|
|
[]
|
|
```
|
|
|
|
Similarly, if all of the arguments have only one element then the result has
|
|
only one element, which is the first element of each argument:
|
|
|
|
```
|
|
> setproduct(["a"], ["b"])
|
|
[
|
|
[
|
|
"a",
|
|
"b",
|
|
],
|
|
]
|
|
```
|
|
|
|
Each argument must have a consistent type for all of its elements. If not,
|
|
Terraform will attempt to convert to the most general type, or produce an
|
|
error if such a conversion is impossible. For example, mixing both strings and
|
|
numbers results in the numbers being converted to strings so that the result
|
|
elements all have a consistent type:
|
|
|
|
```
|
|
> setproduct(["staging", "production"], ["a", 2])
|
|
[
|
|
[
|
|
"staging",
|
|
"a",
|
|
],
|
|
[
|
|
"staging",
|
|
"2",
|
|
],
|
|
[
|
|
"production",
|
|
"a",
|
|
],
|
|
[
|
|
"production",
|
|
"2",
|
|
],
|
|
]
|
|
```
|
|
|
|
## Finding combinations for `for_each`
|
|
|
|
The
|
|
[resource `for_each`](/docs/configuration/resources.html#for_each-multiple-resource-instances-defined-by-a-map-or-set-of-strings)
|
|
and
|
|
[`dynamic` block](/docs/configuration/expressions.html#dynamic-blocks)
|
|
language features both require a collection value that has one element for
|
|
each repetition.
|
|
|
|
Sometimes your input data comes in separate values that cannot be directly
|
|
used in a `for_each` argument, and `setproduct` can be a useful helper function
|
|
for the situation where you want to find all unique combinations of elements in
|
|
a number of different collections.
|
|
|
|
For example, consider a module that declares variables like the following:
|
|
|
|
```hcl
|
|
variable "networks" {
|
|
type = map(object({
|
|
base_cidr_block = string
|
|
}))
|
|
}
|
|
|
|
variable "subnets" {
|
|
type = map(object({
|
|
number = number
|
|
}))
|
|
}
|
|
```
|
|
|
|
If the goal is to create each of the defined subnets per each of the defined
|
|
networks, creating the top-level networks can directly use `var.networks`
|
|
because it's already in a form where the resulting instances match one-to-one
|
|
with map elements:
|
|
|
|
```hcl
|
|
resource "aws_vpc" "example" {
|
|
for_each = var.networks
|
|
|
|
cidr_block = each.value.base_cidr_block
|
|
}
|
|
```
|
|
|
|
However, in order to declare all of the _subnets_ with a single `resource`
|
|
block, we must first produce a collection whose elements represent all of
|
|
the combinations of networks and subnets, so that each element itself
|
|
represents a subnet:
|
|
|
|
```hcl
|
|
locals {
|
|
# setproduct works with sets and lists, but our variables are both maps
|
|
# so we'll need to convert them first.
|
|
networks = [
|
|
for key, network in var.networks : {
|
|
key = key
|
|
cidr_block = network.cidr_block
|
|
}
|
|
]
|
|
subnets = [
|
|
for key, subnet in var.subnets : {
|
|
key = key
|
|
number = subnet.number
|
|
}
|
|
]
|
|
|
|
network_subnets = [
|
|
# in pair, element zero is a network and element one is a subnet,
|
|
# in all unique combinations.
|
|
for pair in setproduct(local.networks, local.subnets) : {
|
|
network_key = pair[0].key
|
|
subnet_key = pair[1].key
|
|
network_id = aws_vpc.example[pair[0].key].id
|
|
|
|
# The cidr_block is derived from the corresponding network. See the
|
|
# cidrsubnet function for more information on how this calculation works.
|
|
cidr_block = cidrsubnet(pair[0].cidr_block, 4, pair[1].number)
|
|
}
|
|
]
|
|
}
|
|
|
|
resource "aws_subnet" "example" {
|
|
# local.network_subnets is a list, so we must now project it into a map
|
|
# where each key is unique. We'll combine the network and subnet keys to
|
|
# produce a single unique key per instance.
|
|
for_each = {
|
|
for subnet in local.network_subnets : "${subnet.network_key}.${subnet.subnet_key}" => subnet
|
|
}
|
|
|
|
vpc_id = each.value.network_id
|
|
availability_zone = each.value.subnet_key
|
|
cidr_block = each.value_cidr_block
|
|
}
|
|
```
|
|
|
|
The above results in one subnet instance per combination of network and subnet
|
|
elements in the input variables.
|
|
|
|
## Related Functions
|
|
|
|
* [`contains`](./contains.html) tests whether a given list or set contains
|
|
a given element value.
|
|
* [`flatten`](./flatten.html) is useful for flattening heirarchical data
|
|
into a single list, for situations where the relationships between two
|
|
object types are defined explicitly.
|
|
* [`setintersection`](./setintersection.html) computes the _intersection_ of
|
|
multiple sets.
|
|
* [`setunion`](./setunion.html) computes the _union_ of multiple
|
|
sets.
|