TT Lab
Get started
Learn Learning paths Courses

Terraform in Practice

Building a Child Module and Calling It Several Times

Continue in TT Lab

Goal

You build a small child module in the standard structure, call the same module several times to add instances, and get the hang of the flow of taking values inside the module out as outputs.

Why it matters

If you learn the reason for using modules only as "removing code duplication," you will soon hit a wall. The real reason is limiting the radius of change. With one implementation, there is one place to fix, and differences between environments show up only as the values you pass. So the most important decision in module design is "what to open up as variables" — if you open too many, the module is just another name for the resources, and if you open too few, nobody can use it. One more thing to remember is that a module is a capsule. Resources inside a module cannot be referenced directly from outside and come out only through output. This constraint looks annoying, but thanks to it, even if you overhaul the module's interior, the code on the using side does not break. Finally, do not put a provider block inside a child module. It is the cause of an accident that is hard to undo, in which you can no longer delete that module call from the code later.

Steps

  1. Copy /opt/lab/fixtures/terraform/modules-starter/modules/filebox to /root/tf/modules/modules/filebox. That directory must have the three files main.tf, variables.tf, and outputs.tf. variables.tf must have variable "dir" and variable "name", and in the outputs.tf you create, put output "path" (whose value is local_file.box.filename). You must not put a provider "..." block inside the module.
  2. Declare module "primary" in /root/tf/modules/main.tf and specify source = "./modules/filebox". Pass /root/tf/modules/out to dir, primary to name, and any string to body. After tofu init, create /root/tf/modules/out/primary.txt with tofu apply.
  3. In /root/tf/modules/outputs.tf, create a root output primary_path that exports module.primary.path, and save the result of tofu output -json to /root/tf/modules/out/outputs.json. The value of primary_path must be /root/tf/modules/out/primary.txt.
  4. Call the same source once more as module "secondary". Pass secondary for name and a string different from primary's for body, so that the contents of /root/tf/modules/out/secondary.txt differ from primary.txt. Do not copy the module directory — the number of directories under /root/tf/modules/modules, including the bundle you will create later, must not exceed 2.
  5. Save the result of tofu state list to /root/tf/modules/out/state-list.txt. There must be addresses starting with module.primary. and module.secondary. respectively, and there must not be a separate address local_file.box at the root.
  6. Create /root/tf/modules/modules/bundle/main.tf and call filebox twice from inside it with source = "../filebox" (for example, module "left" and module "right", with the names bundle-left and bundle-right). If you call it from the root as module "bundle" and apply, two or more addresses of the form module.bundle.module.left.... appear in the state.
  7. Add a validation block to the name variable in /root/tf/modules/modules/filebox/variables.tf and write an error_message with it (for example, allow only lowercase letters, digits, and hyphens). Then briefly put a rule-violating value into the name of one of the calls, and save the output of tofu plan, including standard error, to /root/tf/modules/out/module-error.txt. The saved content must show both the validation failure message and the filebox path. When you are done checking, restore the value.
  8. Add an all_paths output to /root/tf/modules/outputs.tf. It is a map with the three keys primary, secondary, and bundle, and the primary value must be /root/tf/modules/out/primary.txt. In the bundle module too, create output "paths" holding the two inner paths and put it under the bundle key. At the end, save tofu output -json again to /root/tf/modules/out/outputs.json.

Notes

Split the module into three files for inputs, implementation, and outputs

Copy /opt/lab/fixtures/terraform/modules-starter/modules/filebox to /root/tf/modules/modules/filebox. That directory must have the three files main.tf, variables.tf, and outputs.tf. variables.tf must have variable "dir" and variable "name", and in the outputs.tf you create, put output "path" (whose value is local_file.box.filename). You must not put a provider "..." block inside the module.

A module is just a directory. By convention, put variables in variables.tf, resources in main.tf, and values to export in outputs.tf. Do not put a provider configuration block inside a child module.

Call the module and apply

Declare module "primary" in /root/tf/modules/main.tf and specify source = "./modules/filebox". Pass /root/tf/modules/out to dir, primary to name, and any string to body. After tofu init, create /root/tf/modules/out/primary.txt with tofu apply.

Put source and the variable values in a module "이름" block (where the placeholder is the call name). If you add a module or change a source, you must run init again.

Lift module outputs up to the root

In /root/tf/modules/outputs.tf, create a root output primary_path that exports module.primary.path, and save the result of tofu output -json to /root/tf/modules/out/outputs.json. The value of primary_path must be /root/tf/modules/out/primary.txt.

Resources inside a module cannot be referenced directly from outside. The root's output must receive the child's output again for it to appear in tofu output.

Call the same module twice with different inputs

Call the same source once more as module "secondary". Pass secondary for name and a string different from primary's for body, so that the contents of /root/tf/modules/out/secondary.txt differ from primary.txt. Do not copy the module directory — the number of directories under /root/tf/modules/modules, including the bundle you will create later, must not exceed 2.

If you copy the directory, you go back to the copy-paste problem. Call the same source under a different name, and make the values you pass different from each other.

Check the addresses of the resources that belong to modules

Save the result of tofu state list to /root/tf/modules/out/state-list.txt. There must be addresses starting with module.primary. and module.secondary. respectively, and there must not be a separate address local_file.box at the root.

Resources inside a module get the prefix module.<호출이름>. (where the placeholder is the call name). Pull the state list and see whether the prefix was actually attached.

Call a module from inside a module

Create /root/tf/modules/modules/bundle/main.tf and call filebox twice from inside it with source = "../filebox" (for example, module "left" and module "right", with the names bundle-left and bundle-right). If you call it from the root as module "bundle" and apply, two or more addresses of the form module.bundle.module.left.... appear in the state.

When a child module calls a sibling module, use a relative path. When nested, the state address also stacks in two layers.

Block bad values at the module boundary

Add a validation block to the name variable in /root/tf/modules/modules/filebox/variables.tf and write an error_message with it (for example, allow only lowercase letters, digits, and hyphens). Then briefly put a rule-violating value into the name of one of the calls, and save the output of tofu plan, including standard error, to /root/tf/modules/out/module-error.txt. The saved content must show both the validation failure message and the filebox path. When you are done checking, restore the value.

A validation inside a variable block requires both a condition and an error_message. Deliberately put in a rejected value once and see the error with your own eyes.

Gather the outputs of three modules into one map

Add an all_paths output to /root/tf/modules/outputs.tf. It is a map with the three keys primary, secondary, and bundle, and the primary value must be /root/tf/modules/out/primary.txt. In the bundle module too, create output "paths" holding the two inner paths and put it under the bundle key. At the end, save tofu output -json again to /root/tf/modules/out/outputs.json.

You can build an object directly as the value of a root output. The key names and count are the grading criteria, so match them exactly.