Module 8 · Infrastructure as Code with Terraform
Drift and imports
Find drift (real infrastructure that no longer matches Terraform's state) and resources Terraform doesn't manage, then decide for each whether to import it, change it back or delete it.
About 25 minutes
The problem
Terraform only works well if it's the only way infrastructure changes. At Tallybook, it isn't. During a busy week someone resized a worker server in the console. During a 2025 migration someone added firewall rules by hand. Developers create their own servers.
Every manual change creates drift: the real world no longer matches what Terraform believes. The next apply might undo someone's urgent fix, or fail, or, worse, everyone stops trusting the plan.
The concept
Two kinds of mismatch
| Kind | Example | Found by |
|---|---|---|
| Attribute drift | a managed server resized in the console | terraform plan shows a change nobody wrote |
| Unmanaged resources | a server or rule created by hand | comparing the cloud's inventory with state |
Decide for each
- Keep it, and bring it under Terraform: write the code and import it.
- Change it back: let Terraform's next apply restore the coded value.
- Accept the change: update the code to match reality.
- Delete it: if nobody needs it.
Importing
Terraform (HCL)
import {
to = aws_security_group_rule.office_admin
id = "sgr-r08"
}With an import block (Terraform 1.5 and later) and matching resource code, the next plan shows the resource being imported rather than created.
Example
Attribute drift: compare each managed server's instance type in state with the cloud inventory. The inventory uses size names; map them to instance types first:
import json
from urllib.request import urlopen
import pandas as pd
with urlopen("https://academy.cloudtechanalytics.com/datasets/terraform/terraform.tfstate") as f:
state = json.load(f)
inventory = pd.read_csv("https://academy.cloudtechanalytics.com/datasets/cloud/resources.csv")
SIZE_TO_TYPE = {"small": "t3.small", "medium": "t3.medium", "large": "m5.xlarge", "xlarge": "m5.2xlarge"}
in_state = pd.DataFrame([
{"address": f"{r['type']}.{r['name']}[{i['index_key']}]", "resource_id": i["attributes"]["id"], "state_type": i["attributes"]["instance_type"]}
for r in state["resources"] if r["type"] == "aws_instance" for i in r["instances"]
])
compare = in_state.merge(inventory[["resource_id", "name", "size"]], on="resource_id")
compare["real_type"] = compare["size"].map(SIZE_TO_TYPE)
compare[compare["state_type"] != compare["real_type"]]address resource_id state_type name size real_type
12 aws_instance.worker[2] r-0025 m5.large prod-worker-03 large m5.xlargeOne server drifted: worker-03 is really an m5.xlarge, but Terraform still believes it's an m5.large. Its next plan for this server would shrink it back, quietly undoing the fix someone made during a busy week. Now unmanaged firewall rules: the Linux course's rule list against the rules in state.
rules = pd.read_csv("https://academy.cloudtechanalytics.com/datasets/linux/firewall.csv")
managed_rule_ids = {i["attributes"]["id"] for r in state["resources"] if r["type"] == "aws_security_group_rule" for i in r["instances"]}
rules["in_terraform"] = ("sgr-" + rules["rule_id"].str.lower()).isin(managed_rule_ids)
rules.loc[~rules["in_terraform"], ["rule_id", "port_from", "source", "description"]]rule_id port_from source description
2 R03 22 0.0.0.0/0 SSH - temporary, for the 2025 migration
7 R08 8080 0.0.0.0/0 Admin panel
9 R10 5432 0.0.0.0/0 Postgres - for the reporting toolThe three rules that aren't in Terraform are exactly the three open to the whole internet that the Linux course found: SSH, the admin panel and the database. They were never reviewed because they never went through code. Two should be deleted and one restricted, then all firewall changes should go through Terraform.
Walkthrough
- Run the cells. For worker-03, decide: change it back, or update the code? What would you check first?
- Write the resource code and
importblock for a restricted version of R08 (admin panel from the office only). - List every unmanaged server from lesson 1 and decide import or delete for each group.
- Write the drift policy (the task below).
Practice
Practice
How many firewall rules in firewall.csv are not managed by Terraform?
Task
6 minWrite Tallybook's drift policy, one rule per line starting with a dash: at least four rules covering how drift is detected (and how often), what happens to manual changes, emergency changes, and unmanaged resources.
Your work is checked for
- At least four rules, each starting with -
- Scheduled detection (daily, nightly, scheduled plan)
- Manual changes (console, by hand, not allowed)
- Emergencies (emergency, incident, break-glass)
- Unmanaged resources (import or delete)
Check your understanding
Answer every question to check.