Infrastructure as Code, शाळेच्या इमारतीच्या आराखड्यांसारखे शिकवलेले: बांधकाम कार्यालय हाताने खोल्या बांधणे थांबवते आणि लिखित आराखडे ठेवते, आणि कंत्राटदार (Terraform किंवा OpenTofu) ते जागेच्या नोंदवहीशी (state) आणि खऱ्या campus शी तुलना करतो, एक plan दाखवतो आणि फक्त फरक बांधतो. प्रत्येक धडा म्हणजे एक शाळेची गोष्ट, त्यासोबत काढलेली आकृती आणि एक lab — आणि कंत्राटदार repo मध्येच आहे: शुद्ध Python मधील एका बनावट campus वर चालणारे एक छोटे, deterministic, Terraform सारखे engine (iac/, शून्य dependencies, cloud account नको).
# the 60-second wow — one set of plans, one register, one contractor:
git clone https://github.com/BaluRaut/learn-terraform-school.git && cd learn-terraform-school
python3 iac/demo.py # 12 lessons: plans, diffs, registers, locks, drift, pipelines
python3 iac/test_iac.py # 12 checks across the lessons
तुम्हाला काय दिसायला हवे (छाटलेले — यश असे दिसते):
═══ why ═══
── the works office builds the same campus 3 times BY HAND (8 fields per site, 10% chance of a slip per field)
dev 8 fields typed · 5 objects · slips: none
staging 8 fields typed · 5 objects · slips: none
prod 8 fields typed · 5 objects · slips: floor typed 20 instead of 2
do the 3 hand-built sites match? False
── the same campus from WRITTEN PLANS (a config), applied to 3 empty sites
each site: Apply complete! Resources: 5 added, 0 changed, 0 destroyed.
do the 3 plan-built sites match? True
── apply the same plans to the dev site again → No changes. Your infrastructure matches the configuration.
plans are text: reviewed in a pull request, kept in git, repeatable — clicks are none of these
═══ resources ═══
── the plans, in HCL (the lab keeps the same thing as a Python dict):
resource "school_room" "lab" {
name = "chem-lab"
floor = 2
seats = 30
}
── the provider's schema for school_room (what the plans may say, and what the campus fills in):
name required · string
floor default 1 · number · forces a new room if changed
seats default 30 · number
tags default {} · map
id computed · string
── apply → Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
the provider called the campus API: ['POST /rooms → room-01']
school_room.lab is now id room-01 · attributes {'name': 'chem-lab', 'floor': 2, 'seats': 30, 'tags': {}, 'id': 'room-01'}
═══ plan ═══
── version 1 of the plans: lab, art room, main gate → Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
── version 2: lab seats 30 → 40 · art room moves to floor 3 · the gate is removed · a library is added
~ school_room.lab seats: 30 → 40
-/+ school_room.art floor: 1 → 3 # forces replacement
+ school_room.library (create)
- school_gate.main (destroy)
Plan: 2 to add, 1 to change, 2 to destroy.
── apply → Apply complete! Resources: 2 added, 1 changed, 2 destroyed.
the art room is a NEW object: room-02 → room-03 · the lab kept its id room-01
~ update in place keeps the object · -/+ replace destroys it and builds a new one (floor is ForceNew)
═══ variables ═══
── defaults (env=dev, seats=30) → Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
output lab_name = "dev-school-lab"
output locker_ids = ["locker-01", "locker-02", "locker-03"]
── -var env=prod -var seats=40 → ~ school_room.lab name: 'dev-school-lab' → 'prod-school-lab' · seats: 30 → 40
── count: remove 'b' from [a, b, c] → ~ school_locker.row[1] label: 'b' → 'c' · - school_locker.row[2] (destroy) → Plan: 0 to add, 1 to change, 1 to destroy.
── for_each over [a, b, c] makes school_locker.row["a"], school_locker.row["b"], school_locker.row["c"]
── for_each: remove 'b' → - school_locker.row["b"] (destroy) → Plan: 0 to add, 0 to change, 1 to destroy.
count keys by POSITION, so removing the middle item shifts the rest · for_each keys by NAME
═══ state ═══
── apply the campus → Apply complete! Resources: 5 added, 0 changed, 0 destroyed.
output lab_id = "room-01"
output code_a = <sensitive>
── the register (state): version 4 · serial 1 · lineage campus-3f9a · 5 resources
{"school_locker.a": {"type": "school_locker", "id": "locker-01", "attrs": {"room_id": "room-01", "label": "a", "size": "m", "id": "locker-01", "combination": "6872"}, "deps": ["school_room.lab"]}}
search the state for 'combination' → ['school_locker.a: 6872', 'school_locker.b: 4434', 'school_locker.c: 4123']
sensitive = true hides a value in plan and output — the state still holds it in plain text
── the register is lost; plan with an empty one → Plan: 5 to add, 0 to change, 0 to destroy.
after apply the campus has 2 rooms named ['chem-lab'] and 10 objects — duplicates, not the same campus
═══ graph ═══
── edges (a → b means a must exist first):
school_room.lab → school_gate.main
school_room.lab → school_locker.row[0]
school_room.library → school_gate.back
school_gate.main → school_gate.back (explicit depends_on)
school_room.lab → school_locker.row[1..3] (same as row[0])
── parallelism 10: 3 steps for 8 resources → [2, 5, 1] per step
step 1: school_room.lab, school_room.library
step 2: school_gate.main, school_locker.row[0], school_locker.row[1], school_locker.row[2], school_locker.row[3]
step 3: school_gate.back
── parallelism 2: 4 steps for 8 resources → [2, 2, 2, 2] per step
── parallelism 1: 8 steps for 8 resources
── destroy runs the graph backwards:
step 1: school_locker.row[0], school_locker.row[1], school_locker.row[2], school_locker.row[3], school_gate.back
step 2: school_room.library, school_gate.main
step 3: school_room.lab
── a gate that needs a locker that needs the gate → Error: Cycle: school_gate.g, school_locker.l
═══ modules ═══
── one module (modules/wing: a hall, a gate, N lockers), called twice:
module "east" { source = "./modules/wing", wing = "east" }
module "west" { source = "./modules/wing", wing = "west", lockers = 3 }
output "east_gate" { value = module.east.gate_id }
output "west_hall" { value = module.west.hall_id }
Plan: 9 to add, 0 to change, 0 to destroy.
module.east: school_room.this, school_gate.this, school_locker.row[0], school_locker.row[1]
module.west: school_room.this, school_gate.this, school_locker.row[0], school_locker.row[1], school_locker.row[2]
── apply → Apply complete! Resources: 9 added, 0 changed, 0 destroyed.
output east_gate = "gate-01"
output west_hall = "room-02"
── west asks for 4 lockers → + module.west.school_locker.row[3] (create) → Plan: 1 to add, 0 to change, 0 to destroy.
the module is written once; each call gets its own inputs, addresses and outputs
═══ locking ═══
── NO lock · the register holds the lab (serial 1) · Katrina adds a gym, Dipika adds a library, at the same time
both read serial 1 · Katrina: Plan: 1 to add, 0 to change, 0 to destroy. · Dipika: Plan: 1 to add, 0 to change, 0 to destroy.
both wrote serial 2; Dipika wrote last → the register knows ['school_room.lab', 'school_room.library']
the campus has 3 rooms, the register knows 2 → orphan: ['gym'] — built, paid for, managed by nobody
── WITH lock · the register holds the lab (serial 1) · Katrina adds a gym, Dipika adds a library, at the same time
Katrina takes the lock (lock-0001) and plans + applies
Dipika → Error acquiring the state lock · Lock Info: ID lock-0001 · Path campus.tfstate · Operation OperationTypeApply · Who katrina@works-office
Katrina is done (serial 2); Dipika gets lock-0002, reads the NEW register → Plan: 1 to add, 0 to change, 1 to destroy. ← her branch lacks the gym
she pulls Katrina's change first → Plan: 1 to add, 0 to change, 0 to destroy.
Apply complete! Resources: 1 added, 0 changed, 0 destroyed. serial 3 · campus rooms 3 = register 3
one register in one shared place, one writer at a time
═══ drift ═══
── Dipika clicks in the console: room-01 seats 30 → 45 (no plans changed, no register changed)
plan -refresh-only → ['school_room.lab has changed outside of Terraform: seats 30 → 45']
plan → ~ school_room.lab seats: 45 → 30 → Plan: 0 to add, 1 to change, 0 to destroy.
with lifecycle ignore_changes = [seats] → No changes. Your infrastructure matches the configuration.
apply the plan → Apply complete! Resources: 0 added, 1 changed, 0 destroyed. · seats are 30 again
── someone built a back gate by hand: gate-02 — the register has never heard of it
plan with the gate in the plans but no import → Plan: 1 to add, 0 to change, 0 to destroy. (a second gate)
add an import block → ← school_gate.back will be imported (id gate-02)
Plan: 1 to import, 0 to add, 0 to change, 0 to destroy.
Apply complete! Resources: 1 imported, 0 added, 0 changed, 0 destroyed.
── rename school_room.lab → school_room.science in the plans
without a moved block → Plan: 3 to add, 0 to change, 3 to destroy. (the lab AND both gates would be rebuilt)
with moved { from = school_room.lab, to = school_room.science } → school_room.lab has moved to school_room.science · Plan: 0 to add, 0 to change, 0 to destroy.
═══ environments ═══
── one set of plans, two workspaces, one .tfvars file each
workspace dev + dev.tfvars {'seats': 10, 'lockers': 1} → Apply complete! Resources: 2 added, 0 changed, 0 destroyed. → register at env:/dev/campus.tfstate
workspace prod + prod.tfvars {'seats': 40, 'lockers': 3} → Apply complete! Resources: 4 added, 0 changed, 0 destroyed. → register at env:/prod/campus.tfstate
the backend now holds ['env:/dev/campus.tfstate', 'env:/prod/campus.tfstate'] · the campus has 6 objects
── Katrina forgets `workspace select dev` and plans with dev.tfvars while prod is selected:
~ school_room.lab seats: 40 → 10
- school_locker.row[1] (destroy)
- school_locker.row[2] (destroy)
Plan: 0 to add, 1 to change, 2 to destroy. ← prod shrinks to dev's size
── three ways to keep environments apart:
workspaces same code, same backend, a state per workspace · quick, but one wrong select hits prod
directories envs/dev and envs/prod call the same modules, each with its own backend key · clear, a little copying
accounts separate cloud accounts + separate state + separate CI roles · the strongest wall between dev and prod
═══ policy ═══
── validate (no campus needed) on a broken config:
school_room.gym: Inappropriate value for attribute "seats": a number is required.
school_room.gym: An argument named "colour" is not expected here.
school_room.gym: The argument "name" is required, but no definition was found.
school_gate.g: Reference to undeclared resource "school_room.pool".
── plan → Plan: 5 to add, 0 to change, 0 to destroy. · policy check (dev):
DENY school_room.gym: every room needs an owner tag (tags has no 'owner')
DENY school_gate.night: gates must close at night (open_hours = '00-24')
DENY school_locker.big: no xl lockers outside prod (size = 'xl' in dev)
── cost estimate: ₹0/month → ₹2830/month (+2830) — made-up prices, the shape of an Infracost comment
── tests (like `terraform test` with command = plan):
run "the lab has 30 seats"... pass
run "the lab gets 2 lockers"... pass
run "gates close at night"... fail
Error: Test assertion failed — a gate is open 00-24
Failure! 2 passed, 1 failed.
validate checks the plans' SHAPE · tests check what they MEAN · policy checks what is ALLOWED · cost checks what it COSTS
═══ pipeline ═══
── who may do what: the CI job gets a short-lived OIDC token, no stored cloud keys
pull_request sub=repo:school/campus-infra:pull_request
→ campus-plan: granted, read-only (plan), session 3600 s
→ campus-apply: AccessDenied — sub 'repo:school/campus-infra:pull_request' does not match
push sub=repo:school/campus-infra:ref:refs/heads/main
→ campus-plan: granted, read-only (plan), session 3600 s
→ campus-apply: granted, read-write (apply), session 3600 s
PR #42: campus-plan: granted, read-only (plan), session 3600 s
PR #42: validate → Success! The configuration is valid.
PR #42: plan (state serial 1) → Plan: 1 to add, 0 to change, 0 to destroy.
PR #42: policy → 0 denied
PR #42: cost → ₹1200/month → ₹2400/month (+1200)
PR #42: comment posted with the plan · ready for review
PR #43: campus-plan: granted, read-only (plan), session 3600 s
PR #43: validate → Success! The configuration is valid.
PR #43: plan (state serial 1) → Plan: 1 to add, 0 to change, 0 to destroy.
PR #43: policy → 0 denied
PR #43: cost → ₹1200/month → ₹1500/month (+300)
PR #43: comment posted with the plan · ready for review
merge #43: campus-apply: granted, read-write (apply), session 3600 s
merge #43: Apply complete! Resources: 1 added, 0 changed, 0 destroyed. (state serial 2)
merge #42: campus-apply: granted, read-write (apply), session 3600 s
merge #42: Saved plan is stale: the state was changed by another operation after the plan was created.
merge #42: re-plan (state serial 2) → Plan: 1 to add, 0 to change, 1 to destroy.
merge #42: the new plan is NOT the plan that was reviewed → stop, nothing applied
PR #42 (rebased on main): plan (state serial 2) → Plan: 1 to add, 0 to change, 0 to destroy.
merge #42: campus-apply: granted, read-write (apply), session 3600 s
merge #42: Apply complete! Resources: 1 added, 0 changed, 0 destroyed. (state serial 3)
── main = campus: register serial 3 · 3 resources · campus 3 objects
── the whole picture: plans in git → validate, test, policy, cost → plan on the PR → review → apply on merge with OIDC
→ one locked remote register → refresh finds drift → import and moved keep the register honest → the same plans for every site
✅ done — the campus matches the plans
school_room, school_gate, school_locker) Terraform च्या workflow चे एक deterministic शिकवणी model आहे, खुद्द Terraform नाही. खऱ्या terraform commands फक्त खऱ्या account वर असे लिहिलेल्या blocks मध्ये येतात (Terraform ≥ 1.5; terraform test साठी ≥ 1.6). हे कुठे बसते: AWS शाळा हा तो campus आहे जो हे आराखडे सहसा बांधतात; CI/CD शाळा ते apply करणारी pipeline चालवते.संपूर्ण कोर्स एका कॅनव्हासवर. 4K आवृत्तीसाठी क्लिक करा.
एक git branch = एक कल्पना; branch 04 मध्ये धडे 01–04 आहेत. iac/ चा प्रत्येक run सारखाच असतो — randomness seeded आहे.
lesson-01-why-iacधडा वाचा →आकृती पहा ↗lesson-02-resources-providersधडा वाचा →आकृती पहा ↗lesson-03-plan-applyधडा वाचा →आकृती पहा ↗lesson-04-variables-loopsधडा वाचा →आकृती पहा ↗कंत्राटदार आपण काय बांधले ते कसे लक्षात ठेवतो, कामाचा क्रम कसा ठरवतो, designs पुन्हा कसे वापरतो आणि एकच नोंदवही सुरक्षितपणे कशी वाटून घेतो.
lesson-05-stateधडा वाचा →आकृती पहा ↗lesson-06-dependency-graphधडा वाचा →आकृती पहा ↗lesson-07-modulesधडा वाचा →आकृती पहा ↗lesson-08-remote-state-lockingधडा वाचा →आकृती पहा ↗Drift, environments, checks आणि pipelines: आराखडे खरोखर, अनेक जागांसाठी, एका team सोबत चालवणे.
lesson-09-drift-importधडा वाचा →आकृती पहा ↗lesson-10-environmentsधडा वाचा →आकृती पहा ↗lesson-11-testing-policyधडा वाचा →आकृती पहा ↗lesson-12-iac-cicdधडा वाचा →आकृती पहा ↗प्रत्येक धडा एका क्रमांकित बॉक्स-आणि-बाण आकृतीत — स्वतंत्र पानावरही.
Clicks विरुद्ध लिखित आराखडे — आराखड्यांवरून बांधलेला campus पुन्हा बांधता, review करता आणि नव्याने उभारता का येतो.
शाळेला तीन गावांत एकसारखी science wing हवी आहे. कतरिना प्रत्येक wing हाताने बांधते आणि दर वेळी 8 गोष्टी type करते. तिसऱ्या site वर ती floor 2 ऐवजी 20 type करते. आता wings एकसारख्या नाहीत. म्हणून दीपिका एकदाच आराखडा लिहून ठेवते. Contractor आराखड्यावरून बांधतो, आणि तिन्ही wings जुळतात. तो पुन्हा आला की म्हणतो: काहीच करायचे नाही.
एक clerk प्रत्येक site web console मध्ये click करून बांधत असे: प्रत्येक site ला 8 fields हाताने, आणि का ते कुठेच लिहिलेले नाही.
Infrastructure as code: works office campus चा आराखडा git मध्ये लिहून ठेवते, आणि contractor त्यावरून बांधकाम करतो.
हाताने बांधताना prod ला floor 2 ऐवजी 20 पडला, म्हणून 3 sites जुळत नाहीत. आराखड्यातून: प्रत्येकी 5 added, आणि त्या जुळतात.
आराखडा pull request मध्ये तपासता येतो, git मध्ये ठेवता येतो आणि पुन्हा वापरता येतो. dev वर दुसरे apply 'No changes.' म्हणते.
पुढचा धडा आराखडा उघडतो: एका resource block मध्ये काय लिहिता येते, आणि provider व campus काय भरतात.
Resources, providers आणि schemas — आराखड्यांत काय लिहिता येते आणि campus काय भरतो.
कतरिना आराखड्यात एक ओळ लिहिते: chem-lab नावाची खोली, floor 2 वर, 30 seats. Campus ओळखणारा एक मदतनीस ती वाचतो. त्याच्याकडे खोल्यांचे नियम-पुस्तक आहे. नियम सांगतो की खोलीला नाव हवेच. Campus खोली बांधतो आणि तिला एक नंबर देतो: room-01. कतरिना तो नंबर स्वतः कधीच ठरवत नाही.
प्रत्येक console form चे fields आणि नियम वेगळे होते; 'create' दाबण्याआधी खोलीची settings कोणी तपासू शकत नसे.
Resource म्हणजे बांधायची एक गोष्ट, जसे school_room.lab. Provider plugin ला त्याचे schema आणि campus API माहीत असते.
Lab साठी chem-lab, floor 2, 30 seats मागितले. Provider POST /rooms करतो आणि campus id room-01 देतो.
Schema सांगते: name required आहे, floor चा default 1 आणि बदलला तर नवी खोली, आणि id computed आहे: आराखडा तो कधीच ठरवत नाही.
पुढच्या धड्यात आराखडा बदलतो आणि diff वाचतो: कोणत्या ओळी जागीच update होतात आणि कोणत्या पुन्हा बांधल्या जातात.
Diff: create, जागीच update, replace, destroy — आणि नवीन object बनवायला भाग पाडणारे arguments.
ऐश्वर्या आराखडा बदलते. Lab ला 30 ऐवजी 40 seats. Art room floor 3 वर जाते. Gate जाते, आणि library येते. बांधण्याआधी contractor एक यादी दाखवतो. अधिक म्हणजे बांधा, नागमोडी खूण म्हणजे जागीच दुरुस्त करा, आणि वजा म्हणजे पाडा. खोली दुसऱ्या floor वर नेणे म्हणजे अगदी नवी खोली. ऐश्वर्या आधी यादी वाचते, मग हो म्हणते.
बदल थेट campus वर केले जात; एखादा बदल खोली पाडेल का हे आधी कोणालाच कळत नसे.
Plan आराखडा, नोंदवही आणि campus यांची तुलना करतो: + create, ~ update, -/+ replace आणि - destroy.
Version 2: lab seats 30 → 40 (~), art floor 1 → 3 (-/+), library (+), gate नाही (-). Plan: 2 to add, 1 to change, 2 to destroy.
Replace म्हणजे update नव्हे: art room नवी वस्तू झाली, room-02 → room-03, पण lab ने आपला id room-01 ठेवला.
पुढचा धडा inputs, outputs आणि loops आणतो: एकाच block मधून अनेक lockers, count किंवा for_each ने ओळखलेले.
Variables, locals, outputs, count आणि for_each — आणि मधली item काढणे का महत्त्वाचे ठरते.
Office ला तीन lockers हवे आहेत: a, b आणि c. तीन blocks लिहिण्याऐवजी दीपिका एक block आणि एक loop लिहिते. count मध्ये lockers ना 0, 1 आणि 2 म्हणतात. b काढला तर c जागा 1 वर सरकतो, म्हणून c च्या locker चे label बदलते. for_each मध्ये lockers ना नावाने ओळखतात. b काढला तर फक्त b जातो. बाकी काहीच हलत नाही.
प्रत्येक locker साठी copy-paste केलेला block होता, आणि dev व prod चे आराखडे वेगळ्या files मध्ये हळूहळू वेगळे होत गेले.
Variables आराखड्यात मूल्ये देतात, locals नावे ठरवतात, outputs ids सांगतात, आणि count किंवा for_each अनेक प्रती बांधतात.
Defaults मुळे lab_name dev-school-lab आणि 3 lockers. count ने b काढला: 1 to change, 1 to destroy. for_each ने: फक्त 1 to destroy.
count lockers ना जागेनुसार ओळखतो, म्हणून मधला काढला की बाकी सरकतात; for_each त्यांना नावाने ओळखतो.
पुढचा धडा नोंदवहीच उघडतो: कोणता address कोणती खरी वस्तू आहे हे contractor कसे लक्षात ठेवतो.
जागेची नोंदवही: कोणता address कोणता खरा object आहे — आणि त्यात secrets का असतात.
Works office एक नोंदवही ठेवते. प्रत्येक ओळ सांगते की आराखड्यातील कोणते नाव कोणती खरी वस्तू आहे: lab म्हणजे room-01. Contractor प्रत्येक plan आधी ती वाचतो. नोंदवहीत locker codes पण असतात, जसे 6872, साध्या अक्षरात. Screen वर code लपवला तरी नोंदवहीत तो लपत नाही. एके दिवशी नोंदवही हरवते. Contractor सगळे पुन्हा बांधतो, आणि आता दोन chem-labs आहेत.
Contractor ला काहीच आठवत नसे: बांधकामानंतर 'आराखड्यातील lab' आणि campus वरील room-01 यांचा संबंध कुठेच नव्हता.
State म्हणजे site ची नोंदवही: version 4, serial 1, lineage campus-3f9a, 5 addresses आणि 5 खऱ्या वस्तूंची जोडणी.
sensitive = true मुळे code_a <sensitive> दिसते, तरीही state मध्ये 6872, 4434 आणि 4123 साध्या अक्षरात असतात.
नोंदवही हरवली की plan 5 to add म्हणतो; apply नंतर chem-lab नावाच्या 2 खोल्या आणि 10 वस्तू, म्हणजे duplicates.
पुढचा धडा विचारतो कोणत्या क्रमाने बांधायचे: gates आधी खोल्या, आणि एका वेळी किती.
फाटकांआधी खोल्या: implicit आणि explicit क्रम, parallel पायऱ्या, cycles.
खोली नसताना तिचे gate लावता येत नाही. Contractor बाण काढतो: आधी खोली, मग gate. ज्यांच्यामध्ये बाण नाही त्या गोष्टी एकाच वेळी बांधता येतात. 10 जणांच्या crew ला आठ गोष्टींसाठी 3 steps लागतात. 1 जणाला 8 steps. पाडताना उलट: आधी lockers, शेवटी खोल्या. दोन गोष्टी एकमेकींची वाट पाहत असतील तर कोणीच सुरू करू शकत नाही.
बांधकाम करणारे file मधल्या क्रमाने checklist पाळत आणि gate त्याच्या खोलीच्या आधी येणार नाही अशी आशा करत.
room_id = school_room.lab.id सारखा reference graph मधील एक edge आहे; depends_on हाताने edge जोडतो.
8 resources: parallelism 10 मध्ये 3 steps [2, 5, 1], parallelism 2 मध्ये 4 steps, parallelism 1 मध्ये 8 steps.
Destroy graph उलटा चालतो, आधी lockers, शेवटी lab; locker ला gate आणि gate ला locker हवा असेल तर Cycle error येतो.
पुढचा धडा hall, gate आणि lockers एका wing च्या आराखड्यात बांधतो आणि तो दोनदा वापरतो.
एका wing चे design, दोनदा बांधलेले: inputs आणि outputs असलेले modules.
शाळेला east wing आणि west wing हवी आहे. प्रत्येक wing मध्ये एक hall, एक gate आणि काही lockers आहेत. दीपिका एकच wing आराखडा काढते आणि तो दोनदा वापरते. East ला 2 lockers आणि west ला 3. एकूण 9 गोष्टी. West ने चौथा locker मागितला की फक्त west मध्ये एक locker वाढतो. East wing ला अजिबात धक्का लागत नाही.
प्रत्येक नवीन wing मागच्या wing चे blocks हाताने copy करत असे, आणि एका copy मधली दुरुस्ती इतरांपर्यंत पोहोचत नसे.
Module म्हणजे inputs (wing, lockers) आणि outputs (gate_id, hall_id) असलेला एक wing आराखडा, हवा तितक्या वेळा वापरता येतो.
East ला 2 lockers आणि west ला 3: Plan: 9 to add. Outputs east_gate = gate-01 आणि west_hall = room-02.
West ने 4 lockers मागितले तर फक्त module.west.school_locker.row[3] वाढतो: 1 to add, east ला धक्का लागत नाही.
पुढचा धडा नोंदवही एका सामायिक जागी नेतो, कारण दोन लोकांकडे दोन प्रती असल्या की गोंधळ होतो.
एकाच सामायिक जागी एक नोंदवही, एका वेळी एकच लिहिणारा.
सोमवारी कतरिना आणि दीपिका दोघी नोंदवहीची प्रत घेतात. त्यात लिहिले आहे: एक lab. कतरिना gym बांधते. दीपिका library बांधते. कतरिना आपली प्रत परत लिहिते, मग दीपिका आपली प्रत त्यावर लिहिते. आता नोंदवही gym विसरते. Gym उभे आहे पण त्याची काळजी कोणी घेत नाही. म्हणून office कुलूप आणते: कतरिनाकडे कुलूप असताना दीपिकाने थांबायचे.
नोंदवही एका laptop वर किंवा git मध्ये होती, आणि दोन लोक एकाच वेळी plan करून ती लिहू शकत.
Remote state एकच नोंदवही सामायिक backend मध्ये ठेवते, आणि कुलूप असल्याने एका वेळी एकच लिहिणारी काम करते.
कुलूप नाही: कतरिना आणि दीपिका दोघी serial 1 वाचतात आणि serial 2 लिहितात; दीपिका शेवटी लिहिते, म्हणून gym orphan होते.
कुलूप असताना दीपिकाला 'Error acquiring the state lock' येतो, ती कतरिनाचा बदल घेते, आणि serial 3: 3 खोल्या = नोंदवहीत 3.
पुढचा धडा कोणीही न लिहिलेले बदल शोधतो: console click, हाताने बांधलेले gate आणि नाव बदललेली खोली.
कोणीतरी console मध्ये click केले: refresh, drift, ignore_changes, import आणि moved blocks.
दीपिका console मध्ये click करून lab ला 45 seats देते. आराखड्यात अजून 30 आहेत. Contractor हे ओळखतो आणि ते परत 30 करायला सांगतो. मग ऐश्वर्याला कोणीतरी हाताने बांधलेले back gate सापडते. ती import ओळ जोडते, म्हणजे दुसरे gate न बांधता आराखडा तेच gate स्वीकारतो. खोलीचे नाव बदलले तर moved ओळ सांगते की ती तीच खोली आहे.
कोणीतरी console मध्ये खोली बदलली आणि पुढच्या बांधकामाने ती गुपचूप परत बदलेपर्यंत कोणाला कळले नाही.
Drift म्हणजे campus नोंदवहीपेक्षा वेगळा असणे; import हाताने बांधलेली वस्तू स्वीकारतो, moved address चे नाव बदलतो.
दीपिका room-01 चे seats 30 → 45 करते; plan ~ seats 45 → 30 दाखवतो, आणि ignore_changes = [seats] ने No changes.
Import शिवाय gate-02 च्या शेजारी दुसरे gate बनते; moved शिवाय नाव बदलल्याने 0 ऐवजी 3 to add आणि 3 to destroy.
पुढचा धडा तेच आराखडे dev आणि prod साठी चालवतो, आणि चुकीची site किती सहज बदलते ते दाखवतो.
त्याच आराखड्यांवरून dev आणि prod: workspaces विरुद्ध directories विरुद्ध वेगळे accounts.
शाळा एक लहान सरावाचा campus आणि खरा campus चालवते. दोघांसाठी एकच आराखडा. प्रत्येकाची स्वतःची नोंदवही आणि स्वतःची settings file. Dev मध्ये 10 seats आणि 1 locker. Prod मध्ये 40 seats आणि 3 lockers. एके दिवशी कतरिनाने prod उघडलेले असते पण ती dev file वापरते. Plan prod लहान करू पाहतो. सुदैवाने ती plan वाचते आणि नाही म्हणते.
एकच environment होते, म्हणून प्रत्येक बदल थेट production मध्ये तपासला जाई, किंवा dev ही हाताने केलेली प्रत होती.
एकच आराखडा, प्रत्येक environment साठी एक workspace, आणि प्रत्येकी एक .tfvars file: dev.tfvars आणि prod.tfvars.
Dev (10 seats, 1 locker) मध्ये 2 added, prod (40 seats, 3 lockers) मध्ये 4: नोंदवह्या env:/dev आणि env:/prod, 6 वस्तू.
prod निवडलेले असताना कतरिना dev.tfvars ने plan करते: seats 40 → 10 आणि 2 lockers destroy. Prod dev एवढे लहान होते.
पुढच्या धड्यात बांधकामाआधी तपासनीस येतात: validate, tests, policy आणि cost सगळे plan वाचतात.
Validate, tests, policy आणि cost — काहीही बांधण्याआधी plan वाचणारे निरीक्षक.
काहीही बांधण्याआधी चार तपासनीस आराखडा वाचतात. पहिली लिखाण तपासते आणि तिला 4 चुका सापडतात. दुसरी छोट्या tests चालवते: 2 pass, 1 fail. तिसरी शाळेचे नियम तपासते आणि रात्रभर उघडे राहणारे gate थांबवते. चौथी खर्च मोजते: महिन्याला 2830 रुपये. हे सगळे कागदावरच होते.
चुका बांधकामानंतर सापडत: उघडे gate, owner नसलेली खोली, महिन्याच्या शेवटी मोठे बिल.
चार तपासनीस आधी आराखडा वाचतात: validate आकार, tests अर्थ, policy काय परवानगी आहे, आणि cost किंमत तपासते.
Validate ला 4 errors सापडतात; policy 3 बदल नाकारते; cost ₹0 → ₹2830/month; tests: 2 passed, 1 failed.
00-24 उघडे राहणारे night gate कागदावरच थांबते, कोणतेही gate बांधण्याआधी किंवा एकही रुपया खर्च होण्याआधी.
पुढचा धडा सगळे एका conveyor belt वर ठेवतो: pull request वर plan, merge वर apply, keys ऐवजी OIDC.
Pull request वर plan, merge वर apply, keys ऐवजी OIDC — आणि संपूर्ण चित्र.
आता एक conveyor belt काम करतो. दीपिका बदलाची विनंती उघडते, आणि belt ती तपासून plan दाखवतो. ऐश्वर्या तो तपासते. Merge झाला की belt बांधकाम करतो. Belt कडे साठवलेली किल्ली नसते: तो एक तास टिकणारा pass दाखवतो. दोन बदल शर्यत लावतात तेव्हा belt जुना plan नाकारतो आणि नवी तपासणी मागतो.
कोणी laptop वरून apply चालवत असे, आणि सगळ्या jobs मध्ये वाटलेली long-lived cloud key CI secret मध्ये ठेवलेली असे.
CI प्रत्येक pull request वर plan आणि merge वर apply चालवते, साठवलेल्या keys ऐवजी short-lived OIDC tokens वापरून.
pull_request token ला campus-plan (3600 s) मिळते पण campus-apply ला AccessDenied; फक्त main apply करू शकते.
#43 apply झाल्यावर #42 चा saved plan stale झाला: re-plan ला 1 to destroy हवे होते, म्हणून थांबला; rebase नंतर serial 3.
पुढे: AWS school हा campus आहे जो हे आराखडे सहसा बांधतात, आणि CI/CD school pipeline चालवते.