Building a Child Module and Calling It Several Times
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
- Copy
/opt/lab/fixtures/terraform/modules-starter/modules/fileboxto/root/tf/modules/modules/filebox. That directory must have the three filesmain.tf,variables.tf, andoutputs.tf.variables.tfmust havevariable "dir"andvariable "name", and in theoutputs.tfyou create, putoutput "path"(whose value islocal_file.box.filename). You must not put aprovider "..."block inside the module. - Declare
module "primary"in/root/tf/modules/main.tfand specifysource = "./modules/filebox". Pass/root/tf/modules/outtodir,primarytoname, and any string tobody. Aftertofu init, create/root/tf/modules/out/primary.txtwithtofu apply. - In
/root/tf/modules/outputs.tf, create a root outputprimary_paththat exportsmodule.primary.path, and save the result oftofu output -jsonto/root/tf/modules/out/outputs.json. The value ofprimary_pathmust be/root/tf/modules/out/primary.txt. - Call the same source once more as
module "secondary". Passsecondaryfornameand a string different from primary's forbody, so that the contents of/root/tf/modules/out/secondary.txtdiffer fromprimary.txt. Do not copy the module directory — the number of directories under/root/tf/modules/modules, including thebundleyou will create later, must not exceed 2. - Save the result of
tofu state listto/root/tf/modules/out/state-list.txt. There must be addresses starting withmodule.primary.andmodule.secondary.respectively, and there must not be a separate addresslocal_file.boxat the root. - Create
/root/tf/modules/modules/bundle/main.tfand call filebox twice from inside it withsource = "../filebox"(for example,module "left"andmodule "right", with the namesbundle-leftandbundle-right). If you call it from the root asmodule "bundle"and apply, two or more addresses of the formmodule.bundle.module.left....appear in the state. - Add a
validationblock to thenamevariable in/root/tf/modules/modules/filebox/variables.tfand write anerror_messagewith it (for example, allow only lowercase letters, digits, and hyphens). Then briefly put a rule-violating value into thenameof one of the calls, and save the output oftofu plan, including standard error, to/root/tf/modules/out/module-error.txt. The saved content must show both the validation failure message and thefileboxpath. When you are done checking, restore the value. - Add an
all_pathsoutput to/root/tf/modules/outputs.tf. It is a map with the three keysprimary,secondary, andbundle, and theprimaryvalue must be/root/tf/modules/out/primary.txt. In the bundle module too, createoutput "paths"holding the two inner paths and put it under thebundlekey. At the end, savetofu output -jsonagain to/root/tf/modules/out/outputs.json.
Notes
- The state file for this lab is
/root/tf/modules/terraform.tfstate. Resources inside modules are all recorded in this single state as well. - In this environment,
tofuandterraformare the same command. Providers are fetched from a filesystem mirror in the image, sotofu initworks without the internet. - In the root
main.tf, putterraform { required_providers { local = { source = "hashicorp/local" } } }. Put the provider configuration only in the root. - If you add a new module or change a
source, you must runtofu initagain. 90% of "Module not installed" errors are this. - Common mistake 1: referencing a resource inside a module from the root as
module.primary.local_file.box. No such address exists. The value must pass through anoutput. - Common mistake 2: copying the whole module directory to add instances. Call the same
sourceunder a different name.
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.