TT Lab
Get started
Learn Learning paths Courses

Terraform in Practice

Adding a Second Region Left the Module Without a Provider

Continue in TT Lab

Goal

You configure the same provider twice and choose for each resource, pass that configuration to a module, see what blocks you when you leave one out, and then narrow version constraints step by step to confirm for yourself when init passes and when it rejects.

Why it matters

The period during which infrastructure code stays within one region and one account is shorter than you think. A second region for disaster recovery, an account that collects audit logs separately, a public registry and a private registry — either way, you end up with two copies of the same provider with different configurations. Here, which configuration a resource was created with is left not in the code but in the state, and if that record is wrong, you later delete the wrong place. Modules go one step further — if a module creates its own provider configuration itself, that module can no longer be used twice, so you design it to declare only the slot and have the calling side fill it. Version constraints are the foundation of all this. How you write the constraint decides 'whether tomorrow morning's CI may pick up a new release,' and the lock file leaves the basis for that decision.

Steps

  1. In /root/tfa-alias/main.tf, put required_version = ">= 1.6.0" and random in required_providers (source = "hashicorp/random", version = "3.9.0"), declare one default provider configuration block and random_pet.root (length 2), then init and apply.
  2. Create /root/tfa-alias/aliased.tf and declare a second random provider configuration with alias = "seeded" and random_pet.aliased (length 4), which is created with that configuration. Leave random_pet.root in main.tf on the default configuration as is. After applying, check in the state file how the provider addresses of the two resources differ.
  3. In /root/tfa-alias/modules/site/main.tf, declare configuration_aliases = [random.east, random.west], and put random_pet.east and random_pet.west (both length 2) created with each of them and two outputs of the same names. In /root/tfa-alias/site.tf, call that module and fill the providers map with random.east = random and random.west = random.seeded, lift the module's two outputs up as root outputs site_east and site_west, then init and apply.
  4. In the providers map of /root/tfa-alias/site.tf, delete only the random.west line, run tofu init, and save the error to /root/tfa-alias/missing-provider.txt (it must not succeed). Then restore the deleted line and init and apply again so that you finish with a clean plan.
  5. In /root/tfa-alias/probe/too-new/main.tf, put only required_providers that constrains random to version = ">= 4.0.0", and run init in that directory. Save the output to /root/tfa-alias/probe/too-new/init.txt. No lock file must be created in this directory.
  6. Constrain the same provider differently in two directories and compare the results. In /root/tfa-alias/probe/minor/main.tf, set the random constraint to ~> 3.8. In /root/tfa-alias/probe/patch/main.tf, set the random constraint to ~> 3.8.0. After running init on both, write the two lines minor and patch in /root/tfa-alias/probe/verdict.tsv with three tab-separated columns (<이름>, ok or fail, the installed version or none), where the first column is the directory name.
  7. Put only required_version = ">= 999.0.0" in /root/tfa-alias/probe/core/main.tf, run init and save the error to /root/tfa-alias/probe/core/init.txt, and write the Pod's actual current core version (only the number on the first line of tofu version, in the form like 1.2.3) on one line in /root/tfa-alias/probe/core/version.txt.
  8. In /root/tfa-alias/report.tsv, write four lines with two tab-separated columns. lock_version = the version of random written in the root lock file, lock_constraints = the constraint string written in that lock file, provider_configs = the number of random provider configuration blocks in the root configuration, aliased_resources = the number of resources in the state that were created with an alias configuration (count those inside modules too).

Notes

Pin the versions of the core and the provider

In /root/tfa-alias/main.tf, put required_version = ">= 1.6.0" and random in required_providers (source = "hashicorp/random", version = "3.9.0"), declare one default provider configuration block and random_pet.root (length 2), then init and apply.

required_version constrains the version of the core (OpenTofu itself), and the version in required_providers constrains the version of the plugin. They are different things, so init works even if you write only one of them. After apply, open the lock file to see what it records.

Configure the same provider twice

Create /root/tfa-alias/aliased.tf and declare a second random provider configuration with alias = "seeded" and random_pet.aliased (length 4), which is created with that configuration. Leave random_pet.root in main.tf on the default configuration as is. After applying, check in the state file how the provider addresses of the two resources differ.

The meta-argument that chooses an alias configuration on a resource is provider (not providers — that one is for modules). Each resource in the state file has a string recording which provider configuration it was created with, and only the side with an alias ends differently.

Pass the provider configuration to a module

In /root/tfa-alias/modules/site/main.tf, declare configuration_aliases = [random.east, random.west], and put random_pet.east and random_pet.west (both length 2) created with each of them and two outputs of the same names. In /root/tfa-alias/site.tf, call that module and fill the providers map with random.east = random and random.west = random.seeded, lift the module's two outputs up as root outputs site_east and site_west, then init and apply.

configuration_aliases is a declaration that 'this module receives configurations of these names from the calling side.' The reason not to create a provider block inside a module is that if a module decides its own credentials, reuse becomes impossible. On the left side of the providers map is the name inside the module, and on the right is the root's configuration.

What blocks you if you leave one of the passed items out

In the providers map of /root/tfa-alias/site.tf, delete only the random.west line, run tofu init, and save the error to /root/tfa-alias/missing-provider.txt (it must not succeed). Then restore the deleted line and init and apply again so that you finish with a clean plan.

If the calling side does not fill the configuration slot the module requested, the tool tells you exactly which name it could not receive. See whether that name appears as is in the error message. The output goes to standard error, so you must capture it together with 2>&1.

Ask for a version that is not in the mirror

In /root/tfa-alias/probe/too-new/main.tf, put only required_providers that constrains random to version = ">= 4.0.0", and run init in that directory. Save the output to /root/tfa-alias/probe/too-new/init.txt. No lock file must be created in this directory.

This Pod's provider mirror contains only one release of random. Check whether the tool tells you as is 'what it was looking for and could not find' when no release satisfies the constraint. A failed init does not leave a lock file.

Two tilde constraints allow different things

Constrain the same provider differently in two directories and compare the results. In /root/tfa-alias/probe/minor/main.tf, set the random constraint to ~> 3.8. In /root/tfa-alias/probe/patch/main.tf, set the random constraint to ~> 3.8.0. After running init on both, write the two lines minor and patch in /root/tfa-alias/probe/verdict.tsv with three tab-separated columns (<이름>, ok or fail, the installed version or none), where the first column is the directory name.

A tilde constraint allows only the rightmost position to go up. How many positions you wrote is exactly 'how far it may go up.' You read the installed version from that directory's lock file.

The core version constraint blocks before the plugin

Put only required_version = ">= 999.0.0" in /root/tfa-alias/probe/core/main.tf, run init and save the error to /root/tfa-alias/probe/core/init.txt, and write the Pod's actual current core version (only the number on the first line of tofu version, in the form like 1.2.3) on one line in /root/tfa-alias/probe/core/version.txt.

The core version constraint is checked even before fetching providers — so it blocks even if required_providers is entirely absent. The error message tells you 'the version currently in use' as is, so compare whether it is the same number as version.txt.

Put together a table of what was created with which configuration

In /root/tfa-alias/report.tsv, write four lines with two tab-separated columns. lock_version = the version of random written in the root lock file, lock_constraints = the constraint string written in that lock file, provider_configs = the number of random provider configuration blocks in the root configuration, aliased_resources = the number of resources in the state that were created with an alias configuration (count those inside modules too).

The lock file records the chosen version and the constraint at that time together, so later you can trace back 'why was this release chosen.' Resources created with an alias have a different end on the provider string in the state — count them with jq.