[BEST PRACTICE] Where should I set the provider version?

Hi

I started the migration of my modules to use the Terraform 0.12 and I realize that most of my code is “repeating” the configuration to set the provider version as well as the tf required version between others.

Something like this:

terraform {
required_version = “~> 0.11.7”
backend “azurerm” {}
}

Configure the Microsoft Azure Provider

provider “azurerm” {
client_id = “{var.acct_client_id}" client_secret = "{var.acct_client_secret}”
tenant_id = “{var.acct_tenant_id}" subscription_id = "{var.acct_subscription_id}”
version = “<= 1.25”
}

provider “template” {
version = “~> 2.1.2”
}

Most of my code has exactly this setup before the module declaration.

My doubt is simple but I want to make sure that I’m following the best practice, so I’d like to know if should I keep declaring the versions along with the TF code and leaving the modules dealing only with the resources or should I include the version requirements into my modules?

Regards,

Flamarion

Hi @flamarion,

Ideally each separate Terraform module should state the minimum version of each provider it requires. Unfortunately prior to Terraform 0.12 the syntax for declaring module versions made that difficult, because the provider "template" block with version set in it would also implicitly create a separate provider configuration in the child module, which is not recommended.

Terraform 0.12 introduced a new way to declare provider version constraints that keeps them separate from provider configurations, in the special terraform block:

terraform {
  required_providers = {
    template = "~> 2.1.2"
    azurerm = "<= 1.25"
  }
}

However, because all of the modules’ constraints are combined together to decide which provider version to install, overly-restrictive constraints can lead to excluding all versions, and thus create situations where two modules can effectively not be used together. Because of this, I’d suggest the following guidelines for how to write version constraints:

  • In a root module, use a ~> constraint for each provider to specify which major (and optionally minor) version of each provider should be used for that configuration. The ~> constraint type imposes both a lower and upper bound on versions, and thus reduces the risk of accidentally selecting a version with a breaking change.
  • For any shared (non-root) module, use a => constraint to specify the minimum known supported version of each provider. This sets only a lower bound, which reduces the risk that two different modules will make conflicting requirements. If the module has a minimum version that is greater than the maximum implied by the root module constraint, terraform init will return an error to ask the user to make a decision about how to resolve the situation. (For example, they might change the version constraint in the root module to select a newer major version, after reviewing any notes about breaking changes in the provider changelog.)
  • If a particular shared module is known not to work after a certain provider version – perhaps due to breaking changes in the provider that the module author does not tend to accommodate yet – then the shared module might also temporarily specify a < constraint to record that. However, this is not a good situation to be in for an extended period because it may leave callers of that module in a situation where no single provider version can support all of the modules in the configuration. The module author should ideally soon release a new version of the module that works with newer provider versions, and then revert to having only a >= constraint again.
6 Likes