| --- |
| page_title: setproduct - Functions - Configuration Language |
| 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 |
| |
| 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 pass 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`](/language/meta-arguments/for_each) |
| and |
| [`dynamic` block](/language/expressions/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 is 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, to declare all of the _subnets_ with a single `resource` block, you 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 the variables are both maps |
| # so 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. Refer to 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 project it into a map |
| # where each key is unique. 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 `network_subnets` list in the example above creates one subnet instance per combination of network and subnet elements in the input variables. So for this example input: |
| |
| ```hcl |
| networks = { |
| a = { |
| base_cidr_block = "10.1.0.0/16" |
| } |
| b = { |
| base_cidr_block = "10.2.0.0/16" |
| } |
| } |
| subnets = { |
| a = { |
| number = 1 |
| } |
| b = { |
| number = 2 |
| } |
| c = { |
| number = 3 |
| } |
| } |
| ``` |
| |
| The `network_subnets` output would look similar to the following: |
| |
| ```hcl |
| [ |
| { |
| "cidr_block" = "10.1.16.0/20" |
| "network_id" = "vpc-0bfb00ca6173ea5aa" |
| "network_key" = "a" |
| "subnet_key" = "a" |
| }, |
| { |
| "cidr_block" = "10.1.32.0/20" |
| "network_id" = "vpc-0bfb00ca6173ea5aa" |
| "network_key" = "a" |
| "subnet_key" = "b" |
| }, |
| { |
| "cidr_block" = "10.1.48.0/20" |
| "network_id" = "vpc-0bfb00ca6173ea5aa" |
| "network_key" = "a" |
| "subnet_key" = "c" |
| }, |
| { |
| "cidr_block" = "10.2.16.0/20" |
| "network_id" = "vpc-0d193e011f6211a7d" |
| "network_key" = "b" |
| "subnet_key" = "a" |
| }, |
| { |
| "cidr_block" = "10.2.32.0/20" |
| "network_id" = "vpc-0d193e011f6211a7d" |
| "network_key" = "b" |
| "subnet_key" = "b" |
| }, |
| { |
| "cidr_block" = "10.2.48.0/20" |
| "network_id" = "vpc-0d193e011f6211a7d" |
| "network_key" = "b" |
| "subnet_key" = "c" |
| }, |
| ] |
| ``` |
| |
| ## Related Functions |
| |
| - [`contains`](/language/functions/contains) tests whether a given list or set contains |
| a given element value. |
| - [`flatten`](/language/functions/flatten) is useful for flattening hierarchical data |
| into a single list, for situations where the relationships between two |
| object types are defined explicitly. |
| - [`setintersection`](/language/functions/setintersection) computes the _intersection_ of |
| multiple sets. |
| - [`setsubtract`](/language/functions/setsubtract) computes the _relative complement_ of two sets |
| - [`setunion`](/language/functions/setunion) computes the _union_ of multiple |
| sets. |