The security rule was copy-pasted to three places and prod was missed
Goal
Turn two copy-pasted environments that are already in operation into module calls with moved and no replacement, pin the module with a git tag so that only dev uses the new version, and then check the precedence of the paths by which variable values come in and the validation of inputs at the module boundary.
Why it matters
Copy-pasted environments create a chance to miss the rest every time you fix one place. Yet if the resource addresses change while you move already-applied code into a module, the tool tries to delete and recreate it, so refactoring has to go together with telling the state about the move. If a module has no version, the moment you fix the module all environments change together, causing "an unrelated environment broke," and if you do not know the rules by which values come in through several paths, one file left behind from experimenting silently changes the production value.
Steps
- In
/root/iac-mod/dev/main.tfand/root/iac-mod/prod/main.tf, put two resources of the same shape.local_file.configis, inout/app.conf, the three linesname=api,env=<환경>,replicas=<수>(the placeholders are the environment and the count; dev 1, prod 3), andlocal_file.banneris, inout/banner.txt, the single lineapi (<환경>). Run init and apply in both directories. - Build a module with
/root/iac-mod/modules/web/main.tfholding the inputsenv(string) andreplicas(number), the same two resources, and the outputconfig_path. dev's main.tf puts areplicasvariable (default 1), callsmodule "web"withsource = "../modules/web", and uses twomovedblocks to move the old addresses tomodule.web.local_file.configandmodule.web.local_file.banner. After init, the plan must have no creations, deletions, or replacements, and you apply it as is. - Change
/root/iac-mod/prod/main.tfto call the same module too (replicasdefault 3, envprod, two moved). The plan must have no creations, deletions, or replacements, and you apply it. Now there is only one place to fix a rule: the module. - Turn
/root/iac-mod/modules/webinto a git repository, commit the current content, and attach the tagv1.0.0. Change the source of dev and prod togit::file:///root/iac-mod/modules/web?ref=v1.0.0and, aftertofu init -upgrade, both plans must be clean. - Add two things to the module, commit, and attach the tag
v2.0.0: one lineowner=platformat the end of the config content, and a validation that checks thatreplicasis between 1 and 10 inclusive. Raise only dev toref=v2.0.0and run init and apply, and leave prod on v1.0.0, and the plan must be clean. - In dev, try putting the
replicasvalue in through several paths, check the actual value withecho var.replicas | tofu console, and write it to/root/iac-mod/precedence.txt. In every case the environment variableTF_VAR_replicas=9is present. The six cases —env_only(no file),with_tfvars(2 in terraform.tfvars),with_auto(adding a.auto.tfvars 3 and b.auto.tfvars 4 to that),with_var_file(adding-var-file=ops.tfvarswith value 6),with_var(in the order-var-file=ops.tfvars -var replicas=5), andvar_then_file(in the order-var replicas=5 -var-file=ops.tfvars) — are written as six lines in the form이름=값(name=value). When done, delete a.auto.tfvars, b.auto.tfvars, and ops.tfvars, leave only terraform.tfvars (2), and apply. - In dev, run
tofu plan -var replicas=0and save the output (including the error) to/root/iac-mod/invalid.txt. The grader checks in a copy of dev that 0 and 11 are rejected, and that in prod (v1.0.0, no check) 0 passes. Leave the plans of dev and prod clean.
Notes
- The Pod has OpenTofu 1.9.0, a local provider mirror, and git, so it runs without internet. The module repository is the local git repository at
/root/iac-mod/modules/web. - If you change a module's source or ref, you have to run
tofu initagain (with-upgradewhen raising the version). - The git in this Pod has no user name or email configured. When committing, pass them like
git -c user.name=… -c user.email=… commit. - Common mistake: setting file paths with path.module inside the module so that a replacement follows the moved. Common mistake: leaving the precedence experiment files (*.auto.tfvars) behind.
- Refactoring(moved) · Module Sources(git, ref) · Input Variables(precedence and validation) · Terraform: Input Variables
Apply the two copy-pasted environments first
In /root/iac-mod/dev/main.tf and /root/iac-mod/prod/main.tf, put two resources of the same shape. local_file.config is, in out/app.conf, the three lines name=api, env=<환경>, replicas=<수> (the placeholders are the environment and the count; dev 1, prod 3), and local_file.banner is, in out/banner.txt, the single line api (<환경>). Run init and apply in both directories.
This step builds the starting point of "copy-pasted code already in operation." Make a note that the only differences between the two files are the environment name and the number of replicas — those become the module's inputs.
Turn dev into a module call without recreating anything
Build a module with /root/iac-mod/modules/web/main.tf holding the inputs env (string) and replicas (number), the same two resources, and the output config_path. dev's main.tf puts a replicas variable (default 1), calls module "web" with source = "../modules/web", and uses two moved blocks to move the old addresses to module.web.local_file.config and module.web.local_file.banner. After init, the plan must have no creations, deletions, or replacements, and you apply it as is.
path.module inside a module is the module directory. The files must be under the calling environment's directory, so use path.root — if this path differs from the old value by even one character, a replacement follows the move. If you add or change a module, you have to run init again.
Move prod to the same module too
Change /root/iac-mod/prod/main.tf to call the same module too (replicas default 3, env prod, two moved). The plan must have no creations, deletions, or replacements, and you apply it. Now there is only one place to fix a rule: the module.
It is the same procedure as dev. The differences must be only the input values — if you feel like putting a branch such as env == "prod" inside the module, it is a signal that the value should be received as an argument.
Attach a version to the module and pin both environments to it
Turn /root/iac-mod/modules/web into a git repository, commit the current content, and attach the tag v1.0.0. Change the source of dev and prod to git::file:///root/iac-mod/modules/web?ref=v1.0.0 and, after tofu init -upgrade, both plans must be clean.
A local-path source has no version, so the moment you fix the module, every environment that calls it changes together. If you pin a tag with the ref of a git address, init downloads a copy from that point in time into .terraform/modules. The git in this Pod has no user information, so commit with git -c user.name=... -c user.email=....
The new version goes to dev first, and prod stays as it is
Add two things to the module, commit, and attach the tag v2.0.0: one line owner=platform at the end of the config content, and a validation that checks that replicas is between 1 and 10 inclusive. Raise only dev to ref=v2.0.0 and run init and apply, and leave prod on v1.0.0, and the plan must be clean.
Because the tag is pinned, prod is not affected even if main of the module repository changes. Check in dev's plan how config changes (it is a content change, so a replacement) and then apply.
When a value comes into the same variable from several places, who wins
In dev, try putting the replicas value in through several paths, check the actual value with echo var.replicas | tofu console, and write it to /root/iac-mod/precedence.txt. In every case the environment variable TF_VAR_replicas=9 is present. The six cases — env_only (no file), with_tfvars (2 in terraform.tfvars), with_auto (adding a.auto.tfvars 3 and b.auto.tfvars 4 to that), with_var_file (adding -var-file=ops.tfvars with value 6), with_var (in the order -var-file=ops.tfvars -var replicas=5), and var_then_file (in the order -var replicas=5 -var-file=ops.tfvars) — are written as six lines in the form 이름=값 (name=value). When done, delete a.auto.tfvars, b.auto.tfvars, and ops.tfvars, leave only terraform.tfvars (2), and apply.
You can remember the precedence as "whatever is read later wins." The environment variable comes first, then terraform.tfvars, then auto.tfvars in file-name order, and -var and -var-file on the command line are read in the order written. If you leave the experiment files behind, the next person's applied value silently changes.
Block wrong values at the module boundary
In dev, run tofu plan -var replicas=0 and save the output (including the error) to /root/iac-mod/invalid.txt. The grader checks in a copy of dev that 0 and 11 are rejected, and that in prod (v1.0.0, no check) 0 passes. Leave the plans of dev and prod clean.
A validation is attached to the module's input variable, so however the value is put in from the root, it is blocked at the module boundary. That the same input passes in prod is because v1 has no check, and that is the two sides of version pinning.