Upgrading to Terraform v0.14

Terraform v0.14 is a major release and thus includes some changes that you'll need to consider when upgrading. Current version we use is v0.11, so first upgrade to v0.13.

Using .terragrunt-version and .terraform-version to automatically switch based on folder

tgenv and tfenv support switching versions automatically based on a version file (.terragrunt-version for tgenv and .terraform-version for tfenv).

Explicit Provider Source Locations

In previous version, provider details are mentioned in main.tf. But now terraform requires explicit source information for any providers that are not HashiCorp-maintained, using a new syntax in the required_providers nested block inside the terraform configuration block.

terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.45.0"
}
}
}

provider aws {
profile = var.profile
region  = var.region
}

Filling in remote state settings with Terragrunt

To fill in the settings via Terragrunt, create a terragrunt.hcl file in the root folder, plus one terragrunt.hcl file in each of the Terraform modules:

In your rootterragrunt.hcl file, you can define your entire remote state configuration just once in a remote_state block

remote_state {
  backend = "s3"
  config = {
    encrypt = true
    bucket  = "wego-terraform-state-335280333914"
    profile = "wego-staging-account"
    region  = "ap-southeast-1"
    key     = "aws/staging/ap-southeast-1/${path_relative_to_include()}/state.tf"
  }
  generate = {
    path      = "backend.tf"
    if_exists = "overwrite_terragrunt"
  }
}

In each of the childterragrunt.hcl files, you can tell Terragrunt to automatically include all the settings from the root terragrunt.hcl file

The include block tells Terragrunt to use the exact same Terragrunt configuration from the terragrunt.hcl file specified via the path parameter

include {
path = find_in_parent_folders()
}

You can also specify dependencies in your terragrunt.hcl config files using a dependencies block

dependencies {
    paths = [
      "../../vpc",
      "../../../../modules/databases/elasticache/common",
    ]
}

New feature : Provider Dependency Lock File

Terraform v0.14 introduces a new dependency lock file, which Terraform will generate automatically after running terraform init in the same directory as your configuration's root module.


Challenges we faced while upgrading

Warning: Interpolation-only expressions are deprecated :

an expression like "${var.env}" should be rewritten as just var.env.

subdomain_prefix = "${var.env}"
subdomain_prefix = var.env

Quoted type constraints are deprecated:

In a variable block, a type constraint "string" as just string.

type = "string"
type = string

Quoted references are deprecated:

In the depends_on and ignore_changes meta-arguments, quoted references should be rewritten without the quotes.

ignore_changes = [
"container_definitions",
container_definitions,
]

Working with count on resources :

count-related change is that Terraform now requires count to be assigned a numeric value, and will not automatically convert a boolean value to a number in the interests of clarity. If you wish to use a boolean value to activate or deactivate a particular resource, use the conditional operator to show clearly how the boolean value maps to a number value:

count = var.enabled ? 1 : 0

Referring to List Variables :

In early versions of Terraform, before list support became first-class, we required using seemingly-redundant list brackets around a single expression in order to hint to the language interpreter that a list interpretation was desired:

example = ["${var.any_list}"]

The upgrade version is able to recognize most simple usage of this pattern and rewrite automatically to just refer to the list directly:

example = var.any_list

Provider configuration not found :

After upgrade, when I try to run  terragrunt plan , I got this Provider configuration not found error :

 terraform state replace-provider registry.terraform.io/-/aws hashicorp/aws

State file related issues :

Two issue we face with state file are

  1. State file present in wrong path, which tend to create all the resources
  2. Naming standard issue. Previous it was servicename.tf but now changed to state.tf for uniformity. so we need to rename the file to state.tf and place it right path.

changes done with parameter group name :

Database version keeps changing due to minor versioning enabled. So we included only major version in parameter group name.

resource "aws_db_parameter_group" "wego_fares_db_pg" {
name   = "${var.name}-db-pg-${replace(var.engine_version, ".", "")}"
name   = "${var.name}-db-pg-${element(split(".", var.engine_version),0)}"

Using dynamic block to add multiple listener :

Terraform now includes a first-class feature for dynamically generating nested blocks using expressions, using the special dynamic block type

  dynamic "listener" {
    for_each = var.listeners
    content {
      instance_port      = lookup(listener.value, "instance_port", null)
      instance_protocol  = lookup(listener.value, "instance_protocol", null)
      lb_port            = lookup(listener.value, "lb_port", null)
      lb_protocol        = lookup(listener.value, "lb_protocol", null)
      ssl_certificate_id = lookup(listener.value, "ssl_certificate_id", null)
    } 
  }

Dashboard into Dynamic Block for Datadog

Previously, the dashboard can be created like below:

widget = "${local.variable]}"

but in V0.14, block type as been introduced. Ex:

dynamic widget {
for_each = local.group_definitions

content {
  group_definition {
    layout_type = widget.value.layout_type
    # title       = widget.value.title

    dynamic widget {
      for_each = widget.value.query_widgets

      content {
        query_value_definition {

          title = widget.value.title
          text_align = widget.value.text_align
          autoscale = widget.value.autoscale
          precision = widget.value.precision

          dynamic request {
            for_each = widget.value.request

            content {
              # display_type = request.value.display_type
              q = request.value.q
              aggregator = request.value.aggregator

              dynamic conditional_formats {
                for_each = request.value.conditional_formats

              content {
                comparator = conditional_formats.value.comparator
                value      = conditional_formats.value.value
                palette    = conditional_formats.value.palette
                  }
                }
              }
            }
          }
        }
      }
    }

Listener rule priority changes:

we updated the priority of listener rule via aws cli.

/bin/bash
random_pripority=2

while IFS="," read -r col1 col2 col3 col4
do
# set the actual pripority based on csv file
aws elbv2 set-rule-priorities --profile wego-staging-account --region ap-southeast-1 --rule-priorities RuleArn=$col1,Priority=$col4
aws elbv2 set-rule-priorities --profile wego-staging-account --region ap-southeast-1 --rule-priorities RuleArn=$col2,Priority=$col4
done < alb.csv

Plan and apply in Atlantis :

Atlantis does not support Terraform v0.13. So first need to upgrade Terraform v0.13 in local and push the code to remote. Then plan and apply in Atlantis for Terraform v0.14. We need to remove the terraform lock file before pushing the code to remote.