Terraform Associate 004 #6 Modules: Source Addresses, Version Pinning, and Input/Output Syntax

4 min read

The module domain is narrow in scope but dense with questions. Three clusters repeat: source address syntax, the cases where version works and where it does not, and output access syntax. Here we compress the material from Basics #8 into exam form.

Definition: every configuration is already inside a module #

The definition the exam expects goes like this. A module is a container (a directory) of configuration files holding resources that are used together; the directory where you run Terraform is the root module, and anything called through a module block is a child module. The phrase “a configuration that does not use modules” cannot exist, because every configuration is a root module. This framing — “every configuration has at least one module” — appears on the exam as is.

Source addresses: three types #

The source in a module block is interpreted by the format of the string.

main.tf
# 1. Registry: <namespace>/<name>/<provider> format
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 6.0"
}

# 2. Git: detected by prefix, version selected with ref
module "app" {
  source = "git::https://github.com/my-org/tf-app.git?ref=v1.2.0"
}

# 3. Local path: starts with ./ or ../
module "network" {
  source = "../modules/network"
}

The exam point is the condition under which the version argument works. version is valid only for registry sources. A Git source selects its version through the ref query in the URL, and a local path has no version concept — you get whatever the files are at that moment. An option that says “specify version on a local module” is wrong, and the correct-answer direction is that omitting version on a registry module fetches the latest and breaks reproducibility — the “always pin version” rule from Basics #8. And a perennial companion belongs in the same bundle: adding or changing a module requires init.

Inputs and outputs: the module interface #

  • Inputs: you pass values into the variables a child module declares, as arguments of the module block.
  • Outputs: only the outputs a child module declares can be read, as module.<name>.<output>.
main.tf
module "network" {
  source   = "../modules/network"
  vpc_cidr = "10.0.0.0/16"          # input: into the module's variable
}

resource "aws_instance" "web" {
  subnet_id = module.network.private_subnet_id   # output: module.<name>.<output>
}

The principle the exam targets here is encapsulation. A caller cannot reach directly into the resources inside a child module. An address like module.network.aws_subnet.private.id does not exist; only the values the module exports as outputs can be read. When the question is “you need a value from inside the module but there is no output for it”, the answer is to add an output to the module. The reverse direction — the module reading values from its caller — likewise happens only through variables.

The public registry: the verified badge and docs #

The exam points about public modules on the Terraform Registry are two, and light. Modules from partners that HashiCorp has verified carry a verified badge, and a registry page provides input, output, and dependency documentation along with usage examples, so evaluating a module happens through those docs. An option to the effect of “modules without verified cannot be used” is wrong — the badge is a trust signal, not a usage restriction.

Module design common-sense questions #

The correct-answer direction for design-principle questions also matches the criteria from Basics #8. Group resources that are created together and disappear together into a module, avoid thin wrapper modules around a single resource, and open only the inputs you need so the interface stays narrow. The modules and envs structure you built in Practice #9 is a live example of these principles, and recalling that structure solves most of the design questions.

Recap #

What we covered in this post:

  • The directory you run in is the root module, anything called through a module block is a child module, and every configuration is at least one module
  • Sources are distinguished by the registry format, the git:: prefix, and ./ or ../ local paths
  • The version argument is registry-only. Git uses ref, local paths have no version concept, and after changing a module comes init
  • Modules are encapsulated: no direct access to inner resources, and only exported output values are readable as module..
  • The verified badge is a trust signal, not a usage restriction

In the next post (#7 Writing Configuration) we cover variables and outputs, functions and expressions, and the custom conditions strengthened in 004 — validation, precondition, and postcondition. It is a part that reinforces material the track passed over lightly.

X