Terraform Modules
Introduction
This note follows from the previous note.
A Terraform module cannot see variables from the parent automatically. The parent must explicitly pass values into it. It works a bit l ike a function call.
E.g. in Python:
def replica(name, port, server_id):
print(name, port, server_id);
We can not call the function without providing some arguments first:
replica("server4", 3314, 4)
Terraform modules behave the same way.
Calling a module
In the root terraform file, a module can be called like this:
module "replica" {
source = "./modules/mariadb-replica"
for_each = local.servers
name = each.key
port = each.value.port
server_id = each.value.server_id
docker_image = var.docker_image
network_name = docker_network.lab.name
}
This basically says, run mariadb-replica module once for every server, and then pass those variables into it.
Module Contents
It is usually nice to keep variable definitions in a separate file. Much like the case for the top-level terraform files, files in a module also get unified together into a single file when being interpreted.
So in the module directory, we can have a variables.tf file like:
variable "name" {
type = string
}
variable "docker_image" {
type = string
}
variable "port" {
type = number
}
variable "server_id" {
type = number
}
variable "network_name" {
type = string
}
Basically, here we just want to let terraform know what variables exist, their types, and whether they are required.
Now, we can build the module’s main.tf:
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
}
}
}
resource "docker_container" "server" {
name = var.name
image = var.docker_image
hostname = var.name
env = [
"MARIADB_PORT=${var.port}",
"MARIADB_SERVER_ID=${var.server_id}",
]
ports {
internal = var.port
external = var.port
}
networks_advanced {
name = var.network_name
}
labels {
label = "deployed_by"
value = "terraform"
}
}
Everything works just like it did before, but the main difference now is that we need to reference the existence of this module in the top level’s main.tf file as shown earlier.
It is important to also clarify the expected providers a module may need to use, similarly to how it works in the top-level.
Outputs
Optionally, a module can pass information back to the parent. This is basically like return of a function:
def create_container():
container_id = "abc123"
return container_id
The terraform analogy is:
output "container_id" {
value = docker_container.server.id
}
Otherwise, the parent can not see or reference resources created internally within a child-module. Much like how variables in functions tend to be locally scoped in many programming languages.
Conclusion
The takeaway is that modules are useful, just don’t try to turn it into something more complicated than “making things easier to read”.