Problem
When initializing a Terraform configuration, you may encounter the following error indicating that a module is incompatible with meta-arguments like count, for_each, or depends_on.
│ Error: Module is incompatible with count, for_each, and depends_on │ │ on main.tf line 11, in module "test": │ 11: count = 2 │ │ The module at module.test 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 "./test", │ consider updating this module to │ instead expect provider configurations to be passed by its caller.
Cause
This error occurs when you use count, for_each, or depends_on with a legacy module. A legacy module is one that contains its own local provider configuration blocks within its source code. Modern Terraform configurations require passing provider configurations from the root module to child modules, which allows these meta-arguments to function correctly.
Solutions
The appropriate solution depends on your configuration goal. If all resources in the child module should use the same provider configuration for each instance, use Solution 1. If different resources within the child module require different provider configurations, use Solution 2.
Solution 1: Refactor to Use Multiple Module Blocks
This approach involves removing the for_each or count meta-argument and instead declaring a separate module block for each instance you need to create.
Original Configuration
The main.tf file uses for_each to iterate over a legacy module.
## main.tf
variable "azure_credentials" {
type = map(map(string))
default = {
sp1 = {
client_id = "<some_client_id>",
client_secret = "<some_client_secret>",
subscription_id = "<some_subscription_id>"
},
sp2 = {
client_id = "<another_client_id>",
client_secret = "<another_client_secret>",
subscription_id = "<another_subscription_id>"
}
}
}
module "test" {
source = "./test"
for_each = var.azure_credentials
client_id = each.value.client_id
client_secret = each.value.client_secret
subscription_id = each.value.subscription_id
}The module at ./test/main.tf contains a hard-coded provider block.
## ./test/main.tf
provider "azurerm" {
client_id = var.client_id
client_secret = var.client_secret
subscription_id = var.subscription_id
features {}
}
variable "client_id" {}
variable "client_secret" {}
variable "subscription_id" {}
resource "random_pet" "resource_group_suffix" {
length = 2
}
resource "azurerm_resource_group" "test" {
name = random_pet.resource_group_suffix.id
location = "East US"
}Refactored Configuration
The updated main.tf file replaces the for_each loop with two distinct module blocks, one for each service principal.
## main.tf
variable "azure_credentials" {
type = map(map(string))
default = {
sp1 = {
client_id = "<some_client_id>",
client_secret = "<some_client_secret>",
subscription_id = "<some_subscription_id>"
},
sp2 = {
client_id = "<another_client_id>",
client_secret = "<another_client_secret>",
subscription_id = "<another_subscription_id>"
}
}
}
module "test" {
source = "./test"
client_id = var.azure_credentials["sp1"].client_id
client_secret = var.azure_credentials["sp1"].client_secret
subscription_id = var.azure_credentials["sp1"].subscription_id
}
module "test2" {
source = "./test"
client_id = var.azure_credentials["sp2"].client_id
client_secret = var.azure_credentials["sp2"].client_secret
subscription_id = var.azure_credentials["sp2"].subscription_id
}Solution 2: Pass Provider Configurations from the Root Module
This approach refactors the module to remove the embedded provider block and instead receive its provider configuration from the calling root module. This is the modern and recommended pattern for writing reusable modules.
Original Configuration
The main.tf file uses count on a module that contains aliased provider blocks.
## main.tf
variable "primary_subscription_id" {}
variable "dr_subscription_id" {}
module "test" {
source = "./test"
count = 2
primary_subscription_id = var.primary_subscription_id
dr_subscription_id = var.dr_subscription_id
}The module at ./test/main.tf defines its own aliased providers.
## ./test/main.tf
provider "azurerm" {
alias = "primary"
subscription_id = var.primary_subscription_id
features {}
}
provider "azurerm" {
alias = "dr"
subscription_id = var.dr_subscription_id
features {}
}
variable "primary_subscription_id" {}
variable "dr_subscription_id" {}
resource "random_pet" "resource_group_suffix" {
length = 2
}
resource "azurerm_resource_group" "primary" {
provider = azurerm.primary
name = random_pet.resource_group_suffix.id
location = "East US"
}
resource "azurerm_resource_group" "dr" {
provider = azurerm.dr
name = random_pet.resource_group_suffix.id
location = "East US"
}Refactored Configuration
The provider blocks are moved to the root main.tf file. The module call is updated to pass these provider configurations to the child module using the providers meta-argument.
## main.tf
provider "azurerm" {
alias = "primary"
subscription_id = var.primary_subscription_id
features {}
}
provider "azurerm" {
alias = "dr"
subscription_id = var.dr_subscription_id
features {}
}
variable "primary_subscription_id" {}
variable "dr_subscription_id" {}
module "test" {
source = "./test"
count = 2
providers = {
azurerm.primary = azurerm.primary
azurerm.dr = azurerm.dr
}
}The module at ./test/main.tf is updated to declare its provider requirements in a required_providers block instead of defining the providers directly.
## ./test/main.tf
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
configuration_aliases = [azurerm.primary, azurerm.dr]
}
}
}
resource "random_pet" "resource_group_suffix" {
length = 2
}
resource "azurerm_resource_group" "primary" {
provider = azurerm.primary
name = random_pet.resource_group_suffix.id
location = "East US"
}
resource "azurerm_resource_group" "dr" {
provider = azurerm.dr
name = random_pet.resource_group_suffix.id
location = "East US"
}Outcome
After applying one of the refactoring solutions, Terraform will be able to correctly process the module with the count or for_each meta-arguments, and the error will be resolved.