Can you / should you pass archive providers?

I’m passing aws providers with aliases and all is well with the world

module a {
  source = "git::"
  providers = {
      aws = aws
      aws.whatever = aws.whatever
  }
}

Where module a takes

terraform {
   required_providers {
     aws = {
       source = "hashicorp/aws"
      configuration_aliases = [
          aws.whatever = aws.whatever
       ]
   }
  }
}

I’m donig some work to bring some old broken spaghetti tf up to stable and part of that is refactoring some modules. The modules take “archive” and “template” providers (I know template is deprecated but stick with me here).

Can you / should you also pass these providers i.e.:

provider "template" = {}
provider "archive" = {}

module a {
  source = "git::"
  providers = {
      aws = aws
      aws.whatever = aws.whatever
      template = template
      archive = archive
  }
}

Where module a takes

terraform {
   required_providers {
     aws = {
       source = "hashicorp/aws"
      configuration_aliases = [
          aws.whatever = aws.whatever
       ]
   }
  template = {
     source = "hashicorp/template"
     version = "2.2.0"
  }
  archive = {
     source = "hashicorp/archive"
     version = "2.2.0"
  }
  }
}

I ask because archive is a data provider and template is deprecated. I’m using tf 0.15.5 and will be upgrading but I’ve run into an issue where terraform init says

Warning: Provider template is undefined 

on main.tf line 72, in module "a":
72:        template = template

No provider named template has been declared in the root module
If you wish to refer to the template provider within the module, add a provider configuration, or an entry in the required_providers block.

Warning: Provider archive is undefined 

on main.tf line 73, in module "a":
73:        archive = archive

No provider named archive has been declared in the root module
If you wish to refer to the archive provider within the module, add a provider configuration, or an entry in the required_providers block.

Which obviously I’ve done. What am I missing? My plan was to get this running clean and then deprecate the use of the template provider all together but this has really stumped me.

Solved this this morning.

provider "template" = {}
provider "archive" = {}

Is wrong

terraform {
   required_providers {
     aws = {
       source = "hashicorp/aws"
       version = "yourversion"
   }
     archive = {
       source = "hashicorp/archive"
       version = "yourversion"
   }
     template = {
       source = "hashicorp/template"
       version = "yourversion"
   }
  }
}

Because I was coming from tf 0.12 to 0.13 to 0.15 the jump from 0.12 to 0.13 updates how providers work going forward.

Hi @b0bu,

It looks like you already found a solution so in my response here I’m aiming to add a bit of “why is it so?” to this topic, in case you or some other future reader finds it useful.

I think what you have here is an interesting consequence of how implicit vs. explicit provider passing works in Terraform.

When you have a module block which doesn’t have a providers argument, Terraform aims to behave as if you had written a providers argument which passes in all of the default (that is, “un-aliased”) provider configurations from the calling module.

For example, let’s assume that the following is the entire definition of a calling module (there aren’t any other .tf files in the same directory bringing other provider configurations into play):

terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
    }
    archive = {
      source = "hashicorp/archive"
    }
  }
}

provider "aws" {
  region = "us-west-2"
}

provider "aws" {
  alias = "use1"

  region = "us-east-1"
}

provider "archive" {}

module "example" {
  source = "(anything)"
}

The module block at the end of this example does not include a providers argument, so Terraform will study both the child module and the calling module to see which required_providers they have in common. If both the calling module and the called module both make use of both hashicorp/aws and hashicorp/archive then Terraform will look for all of the provider configurations for both of those providers that don’t have aliases, and behaves as if those were passed in with providers, like this:

  providers = {
    archive = archive
    aws     = aws
  }

As long as both of these modules remain unchanged, there’s no material difference between leaving the providers argument omitted or explicitly passing in the default configurations as I showed here.

However, one interesting implication of leaving them all implied is that if this child module begins using a new provider for some reason later then – assuming that the calling module already has a default provider configuration for that provider – the implied provider argument would automatically include that default configuration, whereas the explicit provider argument would need to have the new provider’s default provider configuration added explicitly.

Since provider aliases are namespaced on a per-module basis, Terraform will never automatically pass an aliased configuration and so for any module where there’s at least one required aliased configuration you’d have no option but to explicitly set providers, which means you would need to explicitly set archive = archive in the situation you described.

This rule interacts with a few other implicit behaviors in Terraform which are good to keep in mind too:

  • If a provider doesn’t have any required configuration arguments then Terraform will allow omitting the provider block that would define its provider configuration, and will behave as if you had written an empty block.

    This is true of the hashicorp/archive provider, since it has no configuration arguments at all, and so the provider "archive" {} block I wrote in the example above was actually unnecessary; if I had omitted it, Terraform would’ve assumed it automatically.

    An implied empty provider block still counts as a default provider configuration for the purposes of the rule I described above: Terraform will still automatically pass in the empty default provider configuration for hashicorp/archive if you omit the providers argument in the module block.

  • Many modules that were written for older versions of Terraform don’t include explicit required_providers blocks for providers that belong to the hashicorp/ namespace. If Terraform finds a configuration for provider "aws" and the required_providers block doesn’t include a definition of what “aws” means then it will assume you meant hashicorp/aws, for backward compatibility with older versions of Terraform that only supported HashiCorp-published providers and therefore had no need for this required_providers syntax.

    Therefore in my above example it would’ve worked to omit the required_providers block altogether; both of these providers belong to the hashicorp/ namespace.

    When matching provider requirements between modules to decide how to build the implied providers argument, Terraform uses the global provider source addresses like hashicorp/aws or hashicorp/archive to match them, rather than the local names chosen in the required_providers block. This means that in unusual cases where a module needs to use a different local name for a provider (if, for example, a module for some reason needed to use both hashicorp/archive and example/archive at the same time, such that they can’t both have the local name “archive”) it only matters that the source addresses agree, not that the local names agree.

1 Like