Terragrunt - Multiple conditional providers


I was recently thinking of how I can make my terragrunt.hcl files and directory structure neater…

Using the Helm provider to provision a few deployments on newly created EKS clusters, I was adding this generate block to each file, so that the kubeconfig file could be read from its appropriate location:

generate "provider-helm" {
  path      = "providers_helm.tf"
  if_exists = "overwrite"
  contents  = <<EOF
provider "helm" {
  kubernetes {
    config_path = "~/.kube/config"
  }
}
EOF
}

But adding >10 lines each time I wanted to overwrite a provider configuration was bothering me. And I did not want to define the provider in the root terragrunt.hcl file because it would be included in all resources, even if they did not need it.

…Or would it?

I got a nice idea reading this github discussion on gruntwork’s page, and remembering I can use directives. This opens the way to defining and loading Terraform providers conditionally.

Here’s what I did and satisfied my need for slimmer resource files.

First, in the root terragrunt.hcl file, I put this, overwriting any generate blocks:

# We define which providers to load, on top of the module's providers,
generate "provider" {
  path      = "providers.tf"
  if_exists = "overwrite"
  contents  = <<-EOF
%{if contains(local.providers, "aws")}
provider "aws" {
  region              = "${local.aws_region}"
  allowed_account_ids = ["${local.account_id}"]
  default_tags {
    tags = {
      environment = "${local.environment}"
      terraform   = "true"
      name        = "${basename(get_terragrunt_dir())}"
    }
  }
}
%{endif}
%{if contains(local.providers, "helm")}
provider "helm" {
  kubernetes {
    config_path = "~/.kube/config"
  }
}
%{endif}
EOF
}

So, in this excerpt I conditionally define the aws and helm Terraform providers, along with the configuration that I want to overwrite.

Then, assuming a directory structure where all of the Helm releases live under a folder called helm-releases and the AWS resources live under a folder called infrastructure, we can do the following.

So, let’s assume a simple tree structure of (similar to terragrunt-infrastructure-live-example):


aws-account-A
├── account.hcl
└── eu-west-1
    ├── infrastructure
    │   ├── provider.hcl
    │   ├── rds
    │   │   └── grafana-rds
    │   │       └── terragrunt.hcl
    │   ├── security-groups
    │   │   └── postgresql-kubernetes
    │   │       └── terragrunt.hcl
    │   └── vpc
    │       └── vpcs
    │           └── my-new-vpc
    │               └── terragrunt.hcl
    ├── kubernetes
    │   └── helm-releases
    │       ├── falco
    │       │   └── terragrunt.hcl
    │       ├── grafana
    │       │   └── terragrunt.hcl
    │       ├── prometheus
    │       │   └── terragrunt.hcl
    │       └── provider.hcl
    └── region.hcl

We introduce a new file called provider.hcl, which will contain the Terraform providers that the resources in the children folders will use.

For example, the kubernetes/helm-releases/provider.hcl file contains:

# Set providers
locals {
  providers = ["helm"]
}

…while the infrastructure/provider.hcl file contains:

# Set providers
locals {
  providers = ["aws"]
}

And, of course, getting back to the root terragrunt.hcl file, we need to add the provider locals, as we already do for the account and region ones, so we add to the locals{} map:

# Automatically load provider variables
provider_vars = read_terragrunt_config(find_in_parent_folders("provider.hcl"))

# Extract the variables we need for easy access
providers = local.provider_vars.locals.providers

And there it is. Now, provider.hcl files need to exist, and should include any Terraform providers that we want to conditionally include for any children resources.

Following this, we can easily handle multiple providers and their resources, while keeping the .hcl files slim.

If this helped you, please feel free to give me a tip! https://ko-fi.com/kostavro