# Terraform every AWS Region

When managing your AWS infrastructure with Terraform, it is surprisingly hard to create resources in all enabled regions, including [opt-in regions](https://aws.amazon.com/blogs/security/setting-permissions-to-enable-accounts-for-upcoming-aws-regions/) like eu-south-1.

With Terraform 1.2.1, it is **not possible** to do this in a single `apply` phase:

1. Identify all enabled regions, including opt-in regions.
2. Create a resource in each enabled region.

### Use Case

It's a good idea to put a few guardrails in place when setting up new AWS accounts. For example, AWS has an option to [enable default encryption for all new EBS volumes](https://aws.amazon.com/premiumsupport/knowledge-center/ebs-automatic-encryption/). Terraform can manage this setting with the [aws_ebs_encryption_by_default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ebs_encryption_by_default) resource, but the setting only applies to a single region, so it must be enabled one region at a time.

### Get All Regions

It is deceptively simple to identify all enabled regions. This works great.

```
data "aws_regions" "enabled" {
  all_regions = true
  filter {
    name   = "opt-in-status"
    values = ["opt-in-not-required", "not-opted-in"]
  }
}
```

## Approaches

### Looping with meta-arguments

💁‍♂️ Oh, that's easy. I'll just repeat the resource with [for_each](https://www.terraform.io/language/meta-arguments/for_each).

```
resource "aws_ebs_encryption_by_default" "example" {
  for_each = toset(data.aws_regions.enabled.names)
  provider = "aws.${each.value}"
  enabled = true
}
```

💥 A wild error appears! We can't use string interpolation to pick the provider.

```
Error: Invalid provider configuration reference

  on regions.tf line 3, in resource "aws_ebs_encryption_by_default" "example":
  3:   provider = "aws.${each.value}"

A provider configuration reference must not be given in quotes.
```

Note: it's also impossible to declare providers with `for_each`.

### Provider inside Module

🤨 Okay, I'll just declare the provider inside a module, then loop over the module.

I'll create a new `module` directory with `provider.tf` like this:

```
provider "aws" {
  region = var.region
  assume_role {
    role_arn = "arn:aws:iam::${var.account_id}:role/OrganizationAccountAccessRole"
  }
}
```

And then I'll use `for_each` with that module.

```
module "regions" {
  for_each = toset(data.aws_regions.enabled.names)
  source = "./region"
}
```

💥 Another error! Modules can either have internal providers, or be used with `for_each`, but not both.

```
│ Error: Module is incompatible with count, for_each, and depends_on
│
│   on regions.tf line 19, in module "regions":
│   19:   for_each = toset(data.aws_regions.enabled.names)
│
│ The module at module.regions is a legacy module which contains its own local provider configurations, and so calls to it may not use the count, for_each, or depends_on arguments.
│
│ If you also control the module "./region", consider updating this module to instead expect provider configurations to be passed by its caller.
```

### Copy/paste provider blocks

😑 Fine, I'll just copy/paste a provider block for all possible regions.

```
provider "aws" {
  alias = "us-east-1"
  region = "us-east-1"
  assume_role {
    role_arn = "arn:aws:iam::${var.account_id}:role/OrganizationAccountAccessRole"
  }
}

provider "aws" {
  alias = "af-south-1"
  region = "af-south-1"
  assume_role {
    role_arn = "arn:aws:iam::${var.account_id}:role/OrganizationAccountAccessRole"
  }
}

# {etc.}
```

💥 No. If you declare a provider block for a region that is `not-opted-in` then you'll be blocked from using STS in that region, so Terraform won't be able to get a token and it will error out.

```

Error: error configuring Terraform AWS Provider: IAM Role (arn:aws:iam::12345556789:role/OrganizationAccountAccessRole) cannot be assumed.

There are a number of possible causes of this - the most common are:
  * The credentials used in order to assume the role are invalid
  * The credentials do not have appropriate permission to assume the role
  * The role ARN is not valid

Error: operation error STS: AssumeRole, https response error StatusCode: 403, RequestID: d089d6a7-377e-4633-b36a-037108f32cb4, api error InvalidClientTokenId: The security token included in the request is invalid.

  with provider["registry.terraform.io/hashicorp/aws"].af-south-1,
  on test.tf line 9, in provider "aws":
   9: provider "aws" {
```

### Bash script

😓 Ugh fine, I'll just write a bash script.

```
% aws ec2 describe-regions --all-regions \
  --filters "Name=opt-in-status,Values=not-opted-in,opt-in-not-required" \
  --query 'Regions[].RegionName[]' --output text |
  xargs -n1 -I% terraform apply -var="region=%"
```

## Or just ignore opt-in regions

If you don't need to include opt-in regions, this problem actually disappears. Just create `provider` blocks for all of the standard regions.

- 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

