In this blog post, I’m going to share how to use Input Variables a.k.a variables, and Local Values a.k.a locals, to form a well-maintained infrastructure in Terraform while following the MVC design pattern.
Name | Description (Source) | In Terraform |
---|---|---|
Model | Managing the data of the application | Local Values |
View | Presentation of the model in a particular format | Modules and Resources |
Controller | Receives the input, optionally validates it, and then passes the input to the model | Input Variables |
Mainly used for getting a dynamic input from architects and CI/CD processes. Here’s a classic snippet of how Input Variables are used
variables.tf
variable "app_name" {
type = string
}
variable "region" {
type = string
}
variable "environment" {
type = string
description = "dev, stg, prd"
}
default
values to “information variables”. This enables other architects (or future you) to use the same Infrastructure-as-Code (IaC), without worrying about predefined names. Examples:app_name
region
domain_name
default
values to “infrastructure and application variables”. Examples:desired_tasks = 1
firewall_enabled = true
docker_image = "nginx:1.19.2"
The combination of Input Variables, the default attribute, and the lookup function is amazing. For example, according to a given value, environment
, get the relevant default
value from a map. A more practical example:
variables.tf
variable "environment" {
type = string
description = "dev, stg, prd"
}
variable "cidr_ab" {
type = map
default = {
dev = "10.1"
stg = "10.2"
prd = "10.3"
}
}
main.tf
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
# Later on we'll set cidr to a Local Value
cidr = "${lookup(var.cidr_ab, var.environment)}.0.0/16"
# ... removed other attributes for brevity
}
Any value that has even the slightest chance of changing in the future should be set to a Local Value. This means that most of the IaC will include Local Values. Terraform’s official docs recommendations:
… if overused, Local Values can also make a configuration hard to read by future maintainers by hiding the actual values used … Use local values only in moderation, in situations where a single value or result is used in many places, and that value is likely to be changed in the future. The ability to easily change the value in a central place is the key advantage of local values.
Local Values are “overused” to make the code modular and easier to maintain. To share the true powers of this approach, here’s another practical example:
main.tf
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~>2.0"
name = local.prefix
cidr = local.vpc_cidr
private_subnets = local.private_subnets
public_subnets = local.public_subnets
azs = local.availability_zones
tags = local.tags
}
Do you realize what this means? it’s possible to manage all the values in a single place
variables.tf
# Input Variables (Controller)
variable "app_name" {
type = string
}
variable "region" {
type = string
}
variable "environment" {
type = string
description = "dev, stg, prd"
}
variable "cidr_ab" {
type = map
default = {
dev = "10.1"
stg = "10.2"
prd = "10.3"
}
}
# Local Values (Model)
locals {
prefix = "${var.app_name}-${var.environment}"
vpc_cidr = "${lookup(var.cidr_ab, var.environment)}.0.0/16"
public_subnets = [
"${lookup(var.cidr_ab, var.environment)}.1.0/24",
"${lookup(var.cidr_ab, var.environment)}.2.0/24",
]
private_subnets = [
"${lookup(var.cidr_ab, var.environment)}.10.0/24",
"${lookup(var.cidr_ab, var.environment)}.20.0/24",
]
availability_zones = ["${var.region}a", "${var.region}b"]
tags = {
"Environment" : var.environment,
"Terraform" : "true"
}
}
I’ve created a GitHub repository that implements the described MVC design pattern in Terraform. This repository also includes a CI/CD process for promoting environments. Here it is - unfor19/terraform-multienv
I hope you find this useful, and if you do, don’t forget to clap/heart and share it with your friends and colleagues. Got any questions or doubts? Let’s start a discussion! Feel free to comment below.