I frequently come across Terraform code that's riddled with fixed values. Initially, it might get the job done, even on large-scale projects, but when we need to tweak, add, or scrap those values, it's a real headache. To make matters worse, the absence of standardized coding practices complicates the situation. Making a simple tweak can turn into a wild goose chase, especially when we've got the same value scattered all over the place in multiple files.
Here an example of terraform code to create a simple virtual machine in GCP and AWS respectively.
# GCP-VM
# terraform code with hard-coded values
provider "google" {
project = "default"
region = "us-west1"
zone = "us=west1-a"
}
resource "google_compute_instance" "vm" {
name = "my-vm"
machine_type = "e2-micro"
zone = "us-west1-a"
boot_disk {
initialize_params {
image = "debian-11-bullseye-v20231010"
}
}
network_interface {
network = "default"
}
}
# AWS-VM
# terraform code with hard-coded values
provider "aws" {
profile = "default"
region = "us-west-1"
}
resource "aws_instance" "vm" {
instance_type = "t2.micro"
availability_zone = "us-west-1a"
ami = "ami-0d289675b4e4750f6"
tags = {
Name = "my-vm"
}
}
Every experienced developer knows that depending on hard-coded values can lead to unexpected issues. Yet, the question remains: why do so many people still choose this route? While I may not have a definitive answer, I do have a solution for doing things more effectively. When we separate data from logic, we not only tackle the earlier problems but also create opportunities for seamless code replacement. This means we can reuse the code in different environments and even with various cloud providers.
Instead of relying on Terraform variables, we opt for a YAML document to specify the necessary values, which offers a more straightforward understanding. Simultaneously, we make sure to separate our logic from data, paving the way for code that's both reusable and easy to test.
We will use the examples introduced earlier to demonstrate how to create reusable code by effectively separating logic from data.
We will look at more advanced versions here
# AWS-VM
# terraform code with hard-coded values
locals { config = yamldecode(file("./config.yml")) }
provider "aws" {
profile = local.config.project.profile
region = local.config.project.region
}
resource "aws_instance" "vm" {
instance_type = local.config.vm.type
availability_zone = local.config.profile.zone
ami = local.config.vm.image
tags = {
Name = local.config.vm.name
}
}
---
# config.yml
# AWS-VM
project:
id : my-vpc
profile : default
region : us-west-1
zone : us-west-1a
vm:
name : my-vm
type : e2.micro
image: ami-0d289675b4e4750f6
We will look at more advanced versions here
# GCP-VM
# terraform code with hard-coded values
locals { config = yamldecode(file("./config.yml")) }
provider "google" {
project = local.config.project.id
region = local.config.project.region
zone = local.config.project.zone
}
resource "google_compute_instance" "vm" {
name = local.config.vm.name
machine_type = local.config.vm.type
zone = local.config.project.zone
boot_disk {
initialize_params {
image = local.config.vm.image
}
}
network_interface {
network = "default"
}
}
---
# config.yml
# GCP-VM
project:
id : my-vpc
region : us-west1
zone : us-west1-a
# GCP-VM
vm:
name : vm-1
type : e2-micro
image: debian-11-bullseye-v20231010