aboutsummaryrefslogtreecommitdiff
path: root/content/blog/terraform/tofu_for_each_providers.md
blob: 4c23f9079edec0ac2f2c94184803e84bb82c7746 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
---
title: 'Opentofu provider iteration with `for_each`'
description: 'a much anticipated feature'
date: '2025-01-25'
tags:
- AWS
- OpenTofu
---

## Introduction

The latest release of OpenTofu came with a much anticipated feature: provider
iteration with `for_each`!

My code was already no longer compatible with terraform since OpenTofu added the
much needed variable interpolation in provider blocks feature, so I was more
than ready to take the plunge.

## Usage

A good example will be to rewrite the lengthy code from my [Securing AWS default
vpcs]({{< ref "blog/aws/defaults.md" >}}#iterating-through-all-the-default-regions)
article a few months ago. It now looks like:

``` hcl
locals {
  aws_regions = toset([
    "ap-northeast-1",
    "ap-northeast-2",
    "ap-northeast-3",
    "ap-south-1",
    "ap-southeast-1",
    "ap-southeast-2",
    "ca-central-1",
    "eu-central-1",
    "eu-north-1",
    "eu-west-1",
    "eu-west-2",
    "eu-west-3",
    "sa-east-1",
    "us-east-1",
    "us-east-2",
    "us-west-1",
    "us-west-2",
  ])
}

provider "aws" {
  alias = "all"
  default_tags { tags = { "managed-by" = "tofu" } }
  for_each = concat(local.aws_regions)
  profile  = "common"
  region   = each.key
}

module "default" {
  for_each = local.aws_regions
  providers = { aws = aws.all[each.key] }
  source    = "../modules/defaults"
}
```

Note the use of the `concat()` function in the `for_each` definition of the
providers block. This is needed to silence a warning that tells you it is a bad
idea to iterate through your providers using the same expression in provider
definitions and module definitions.

Though I understand the reason (to allow for resources destructions when the
list we are iterating on changes), it is not a bother for me in this case.

## Modules limitations

The main limitation at the moment is the inability to pass down the whole
`aws.all` to a module. This leads to code that repeats itself a bit, but it is
still better than before.

For example, when creating resources for multiple aws accounts, a common pattern
is to have your DNS manged in a specific account (for me it is named `core`)
that you need to pass around. Let's say you have another account named `common`
with for example monitoring stuff and here is how some module invocation can
look like:

``` hcl
module "base" {
  providers = {
    aws          = aws.all["${var.environment}_${var.region}"]
    aws.common   = aws.all["common_us-east-1"]
    aws.core     = aws.all["core_us-east-1"]
  }
  source = "../modules/base"

  ...
}
```

It would be nice to be able to just pass down aws.all, but alas we cannot yet.

## Cardinality limitation

Just be warned that you cannot go too crazy with this mechanism. I tried to
iterate through a cross-product of all AWS regions and a dozen AWS accounts and
it does not go well: OpenTofu slows down to a crawl and it starts taking a dozen
minutes just to instantiate all providers in a folder, before planning any
resources!

This is because providers are instantiated as separate processes that OpenTofu
then talks to. This model does not scale that well (and consumes a fair bit of
memory), as least for the time being.

## Conclusion

I absolutely love this new feature!