ЁЯзк рдзрдбрд╛ 11 тАФ Testing рдЖрдгрд┐ policy: рдЖрдзреА рдЖрд░рд╛рдЦрдбрд╛ рд╡рд╛рдЪрдгрд╛рд░реЗ рддрдкрд╛рд╕рдиреАрд╕
ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 11 ┬╖ рдорд╛рдЧреАрд▓: lesson-10-environments ┬╖ рдкреБрдвреАрд▓: lesson-12-iac-cicd
ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ
рдзрдбреЗ 01тАУ10, рдЖрдгрд┐ рдХрд╛рд╣реАрд╣реА рдмрд╛рдВрдзрдгреНрдпрд╛рдЪреНрдпрд╛ рдЖрдзреА рдЪрд╛рд▓рдгрд╛рд▒реНрдпрд╛ рдЖрдкреЛрдЖрдк рддрдкрд╛рд╕рдгреНрдпрд╛:
validate (рдЖрдХрд╛рд░ рдмрд░реЛрдмрд░ рдЖрд╣реЗ рдХрд╛?), tests (plan рдЖрдкрд▓реНрдпрд╛рд▓рд╛ рдЕрдкреЗрдХреНрд╖рд┐рдд рддреЗрдЪ рдХрд░рддреЛ рдХрд╛?),
policy (рдпрд╛рд▓рд╛ рдкрд░рд╡рд╛рдирдЧреА рдЖрд╣реЗ рдХрд╛?) рдЖрдгрд┐ cost (рдпрд╛рд▓рд╛ рдХрд┐рддреА рдЦрд░реНрдЪ рдпреЗрдИрд▓?). рддрдкрд╛рд╕рдиреАрд╕
plan рд╡рд╛рдЪрддрд╛рдд, campus рдирд╛рд╣реА. policy() рд╣реЗ iac/demo.py рдордзреНрдпреЗ рдЖрд╣реЗ; validate() рд╣реЗ
iac/engine.py рдордзреНрдпреЗ; policy_check(), cost() рдЖрдгрд┐ run_tests() рд╣реЗ
iac/checks.py рдордзреНрдпреЗ.
ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ
рдХреЛрдгрддрд╛рд╣реА plan рдХрдВрддреНрд░рд╛рдЯрджрд╛рд░рд╛рдкрд░реНрдпрдВрдд рдкреЛрд╣реЛрдЪрдгреНрдпрд╛рдЖрдзреА рддреЛ рдЪрд╛рд░ рддрдкрд╛рд╕рдиреАрд╕рд╛рдВрдЪреНрдпрд╛ рд╕рдореЛрд░реВрди рдЬрд╛рддреЛ. ЁЯХ╡я╕ПЁЯХ╡я╕ПЁЯХ╡я╕ПЁЯХ╡я╕П
- Form рддрдкрд╛рд╕рдгрд╛рд░реА рд▓рд┐рдЦрд╛рдг рдкрд╛рд╣рддреЗ: "рдпрд╛ рдЦреЛрд▓реАрд▓рд╛ рдирд╛рд╡ рдирд╛рд╣реА. 'thirty' рд╣рд╛ рдЖрдХрдбрд╛ рдирд╛рд╣реА. рдЦреЛрд▓реАрдЪреНрдпрд╛ form рд╡рд░ colour рдирд╛рд╡рд╛рдЪрд╛ рд░рдХрд╛рдирд╛рдЪ рдирд╛рд╣реА. рдЖрдгрд┐ рд╣реЗ gate рдЕрд╢рд╛ pool рдХрдбреЗ рдмреЛрдЯ рджрд╛рдЦрд╡рддреЗ рдЬреЛ рдЖрд░рд╛рдЦрдбреНрдпрд╛рдд рдирд╛рд╣реАрдЪ." рддрд┐рд▓рд╛ рдХреЛрдгрддреНрдпрд╛рд╣реА рдЬрд╛рдЧреА рдЬрд╛рдгреНрдпрд╛рдЪреА рдЧрд░рдЬ рдирд╕рддреЗ.
- Tester рд╡рд┐рдЪрд╛рд░рддреЗ "plan рддреБрдореНрд╣рд╛рд▓рд╛ рдЕрдкреЗрдХреНрд╖рд┐рдд рддреЗрдЪ рдХрд░рддреЛ рдХрд╛?" "рддреБрдореНрд╣рд╛рд▓рд╛ lab рдордзреНрдпреЗ 30 seats рд╣рд╡реНрдпрд╛ рд╣реЛрддреНрдпрд╛ тАФ рд╣реЛ. 2 lockers тАФ рд╣реЛ. рд░рд╛рддреНрд░реА gates рдмрдВрдж тАФ рдирд╛рд╣реА, рд░рд╛рддреНрд░реАрдЪреЗ gate 00-24 рдЙрдШрдбреЗ рдЖрд╣реЗ."
- рдирд┐рдпрдо рдкрд╛рд│рдгрд╛рд░реА рдХрдбреЗ рд╢рд╛рд│реЗрдЪреЗ рдирд┐рдпрдо рдЕрд╕рддрд╛рдд: рдкреНрд░рддреНрдпреЗрдХ рдЦреЛрд▓реАрд▓рд╛ рдПрдХ owner рд╣рд╡рд╛; рд░рд╛рддреНрд░реА gates рдмрдВрдж рд╡реНрд╣рд╛рдпрд▓рд╛ рд╣рд╡реАрдд; рдЦрд▒реНрдпрд╛ campus рд╢рд┐рд╡рд╛рдп рдЗрддрд░рддреНрд░ extra-large lockers рдирд╛рд╣реАрдд. рддреА рддреАрди рд╡реЗрд│рд╛ DENY рдореНрд╣рдгрддреЗ.
- рд╣рд┐рд╢реЗрдмрдиреАрд╕ рдмреЗрд░реАрдЬ рдХрд░рддреЗ: рд╣рд╛ plan рджрд░ рдорд╣рд┐рдиреНрдпрд╛рд▓рд╛ тВ╣2830 рдЬрд╛рд╕реНрдд рдЦрд░реНрдЪ рдХрд░рддреЛ.
рдЪрд╛рд░рд╣реА рддрдкрд╛рд╕рдгреНрдпрд╛ рдкрд╛рд░ рдХрд░рдгрд╛рд░рд╛ plan рдЪ рдХрдВрддреНрд░рд╛рдЯрджрд╛рд░рд╛рдХрдбреЗ рдЬрд╛рддреЛ.
ЁЯЧ║я╕П рдЖрдХреГрддреА
flowchart LR
plans["ЁЯУЬ plans"] --> v["ЁЯЦКя╕П validate<br/>shape: 4 errors"]
v --> plan["ЁЯУЛ plan<br/>5 to add"]
plan --> t["ЁЯзк tests<br/>2 passed, 1 failed"]
plan --> pol["ЁЯЪл policy<br/>3 DENY"]
plan --> c["ЁЯТ░ cost<br/>+тВ╣2830/month"]
t & pol & c --> gate{"all green?"}
gate -->|"yes"| apply["apply"]
gate -->|"no"| fix["fix the plans"]
ЁЯЧ║я╕П рд░реЗрдЦрд╛рдЯрд▓реЗрд▓реА рдЖрд╡реГрддреНрддреА + рдПрдХ lab: https://school-edh.pages.dev/terraform/lesson-diagrams.html#l11
тЭУ рдХрд╛рдп
terraform fmt -checkтАФ рдлрдХреНрдд formatting. Format рди рдХреЗрд▓реЗрд▓реНрдпрд╛ files рдЕрд╕рддреАрд▓ рддрд░ build fail рдХрд░рддреЗ.terraform validateтАФ configuration provider schemas рдиреБрд╕рд╛рд░ рддрдкрд╛рд╕рддреЗ: рдЕрдиреЛрд│рдЦреА arguments, рдЧрд╣рд╛рд│ required arguments, рдЪреБрдХреАрдЪреЗ types, declare рди рдХреЗрд▓реЗрд▓реЗ references. рдпрд╛рд▓рд╛initрд▓рд╛рдЧрддреЗ (schemas рд╕рд╛рдареА) рдкрдг cloud credentials рдЖрдгрд┐ state рд▓рд╛рдЧрдд рдирд╛рд╣реАрдд.- Linters тАФ TFLint validate рдордзреНрдпреЗ рдирд╕рд▓реЗрд▓реЗ рдирд┐рдпрдо рдЬреЛрдбрддреЗ (рдЙрджрд╛. AWS рд╕рд╛рдареА рдЕрд╡реИрдз instance type).
terraform test(Terraform 1.6+) тАФrunblocks рдЕрд╕рд▓реЗрд▓реНрдпрд╛*.tftest.hcltest files. рдкреНрд░рддреНрдпреЗрдХ runplanрдХрд┐рдВрд╡рд╛applyрдХрд░реВ рд╢рдХрддреЛ (рдЦрд▒реНрдпрд╛ infrastructure рд╡рд░, рдордЧ рддреЗ destroy рдХрд░рддреЛ) рдЖрдгрд┐assert { condition = ... error_message = ... }. Mock providers (1.7+) рдореБрд│реЗ tests cloud рд╢рд┐рд╡рд╛рдп рдЪрд╛рд▓рддрд╛рдд.- Config рдордзреАрд▓ custom conditions тАФ variable
validation(рдзрдбрд╛ 04),lifecycleblocks рдордзреАрд▓preconditionрдЖрдгрд┐postcondition, рдЖрдгрд┐checkblocks (1.5+). - Policy as code тАФ plan JSON рд╡рд░рдЪреЗ рдирд┐рдпрдо (
terraform show -json tfplan): conftest рд╕рд╣ OPA (Rego рдордзреАрд▓ рдирд┐рдпрдо), checkov, Trivy (рдЬреНрдпрд╛рдд tfsec рд╕рд╛рдорд╛рд╡рд▓реЗ), HashiCorp Sentinel (HCP Terraform / Enterprise). рдирд┐рдпрдо рд╕рд╛рдВрдЧрддреЛ рдХреА рдХрд╛рдп рдирд╛рдХрд╛рд░рд▓реЗ рдЬрд╛рддреЗ. - Cost estimation тАФ Infracost plan рдЪреА рдХрд┐рдВрдордд рдХрд╛рдврддреЗ рдЖрдгрд┐ рдорд╣рд┐рдиреНрдпрд╛рдЪрд╛ рдлрд░рдХ pull request рд╡рд░ comment рдореНрд╣рдгреВрди рд▓рд┐рд╣рд┐рддреЗ.
ЁЯдФ рдХрд╛
рдХрд╛рд░рдг plans рд╡рд╛рдЪрдгрд╛рд▒реНрдпрд╛ рдорд╛рдгрд╕рд╛рдВрдХрдбреВрди рдЧреЛрд╖реНрдЯреА рд╕реБрдЯрддрд╛рдд, рдЖрдгрд┐ рдХрд╛рд╣реА рдЪреБрдХрд╛ рдкреНрд░рддреНрдпреЗрдХ рд╡реЗрд│реА рддреНрдпрд╛рдЪ рдЕрд╕рддрд╛рдд:
public bucket, рдЧрд╣рд╛рд│ tag, рд░рд╛рддреНрд░рднрд░ рдЙрдШрдбреЗ gate, dev рдордзреНрдпреЗ рдкреНрд░рдЪрдВрдб instance. рдкреНрд░рддреНрдпреЗрдХ plan рд╡рд░
рдЪрд╛рд▓рдгрд╛рд░реА рддрдкрд╛рд╕рдгреА рддреНрдпрд╛ review рдЖрдзреАрдЪ рдкрдХрдбрддреЗ, рдЖрдгрд┐ рдирд┐рдпрдо рдХреЛрдгрд╛рдЪреНрдпрд╛ рддрд░реА рдбреЛрдХреНрдпрд╛рдд рди рд░рд╛рд╣рддрд╛ code рдордзреНрдпреЗ
рджрд┐рд╕рддрд╛рдд. Plan рд╡рд░рдЪреНрдпрд╛ рддрдкрд╛рд╕рдгреНрдпрд╛ рд╕реНрд╡рд╕реНрдд рдЕрд╕рддрд╛рдд: apply рдирд╛рд╣реА, рд╡рд╛рдЯ рдкрд╛рд╣рдгреЗ рдирд╛рд╣реА, рдирдВрддрд░рдЪреА рд╕рд╛рдлрд╕рдлрд╛рдИ
рдирд╛рд╣реА. command = apply рд╕рд╣ terraform test рдкреБрдвреЗ рдЬрд╛рдКрди рдЦрд▒реНрдпрд╛ рдЧреЛрд╖реНрдЯреА рдмрд╛рдВрдзрддреЗ,
рдЬреЗ рд╣рд│реВ рдЕрд╕рддреЗ рдЖрдгрд┐ рдкреИрд╕реЗ рдЦрд░реНрдЪ рдХрд░рддреЗ, рдореНрд╣рдгреВрди рдмрд╣реБрддреЗрдХ teams рддреЗ modules рд╕рд╛рдареАрдЪ рдареЗрд╡рддрд╛рдд.
ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)
iac/engine.py рдордзреАрд▓ validate(cfg) рдкреНрд░рддреНрдпреЗрдХ block
SCHEMAS рдиреБрд╕рд╛рд░ рддрдкрд╛рд╕рддреЗ, Terraform рдЪреНрдпрд╛рдЪ error рд╢рдмреНрджрд░рдЪрдиреЗрдд. iac/checks.py рдордзреАрд▓
POLICIES рд╣реА (name, rule) рдЪреА рдпрд╛рджреА рдЖрд╣реЗ; рдкреНрд░рддреНрдпреЗрдХ rule рдПрдХ
planned рдмрджрд▓ (рдЖрдгрд┐ environment) рдкрд╛рд╣рддреЛ рдЖрдгрд┐ рдирд╛рдХрд╛рд░рдгреНрдпрд╛рдЪреЗ рдХрд╛рд░рдг рдкрд░рдд рджреЗрддреЛ тАФ Python рдордзреНрдпреЗ рд▓рд┐рд╣рд┐рд▓реЗрд▓реНрдпрд╛ Rego
deny rule рдЪрд╛рдЪ рдЖрдХрд╛рд░. cost(p) plan рдЪреНрдпрд╛ рдЖрдзреА рдЖрдгрд┐ рдирдВрддрд░рдЪреНрдпрд╛ resources рдЪреА
рдХрд╛рд▓реНрдкрдирд┐рдХ рд░реБрдкрдпрд╛рдВрдЪреНрдпрд╛ рдХрд┐рдВрдорддреАрдВрдиреА рдХрд┐рдВрдордд рдХрд╛рдврддреЗ. run_tests() рд░рд┐рдХрд╛рдореНрдпрд╛ рдиреЛрдВрджрд╡рд╣реАрд╡рд░ plan рдХрд░рддреЗ рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ
assertion рддрдкрд╛рд╕рддреЗ, terraform test рдкреНрд░рдорд╛рдгреЗрдЪ Success! рдХрд┐рдВрд╡рд╛ Failure! рдЫрд╛рдкрддреЗ.
ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛
python3 iac/demo.py policy
python3 - <<'EOF'
import sys, copy; sys.path.insert(0, "iac")
from cloud import FakeCloud
from engine import plan
from checks import policy_check, cost, POLICIES
from demo import POLICY
p = plan(POLICY, None, FakeCloud())
for env in ("dev", "prod"): print(f"{env:<5} тЖТ {len(policy_check(p, env))} denied")
fixed = copy.deepcopy(POLICY)
fixed["resource"]["school_room"]["gym"]["tags"] = {"owner": "aishwarya"}
fixed["resource"]["school_gate"]["night"]["open_hours"] = "06-22"
fixed["resource"]["school_locker"]["big"]["size"] = "m"
q = plan(fixed, None, FakeCloud())
print("fixed тЖТ", policy_check(q, "dev"), "┬╖ cost", cost(q))
print(len(POLICIES), "rules")
EOF
тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ
policy рд╣реЗ рдЫрд╛рдкрддреЗ:
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".
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
run "the lab has 30 seats"... pass
run "the lab gets 2 lockers"... pass
run "gates close at night"... fail
Failure! 2 passed, 1 failed.
рддреБрдордЪрд╛ snippet рд╣реЗ рдЫрд╛рдкрддреЛ:
dev тЖТ 3 denied
prod тЖТ 2 denied
fixed тЖТ [] ┬╖ cost (0, 2780)
3 rules
ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ
Validate рд▓рд╛ campus, рдиреЛрдВрджрд╡рд╣реА рдХрд┐рдВрд╡рд╛ plan рд╢рд┐рд╡рд╛рдп 4 рдЪреБрдХрд╛ рд╕рд╛рдкрдбрд▓реНрдпрд╛. рддреЛрдЪ plan dev рдордзреНрдпреЗ 3 рд╡реЗрд│рд╛ рдЖрдгрд┐ prod рдордзреНрдпреЗ 2 рд╡реЗрд│рд╛ рдирд╛рдХрд╛рд░рд▓рд╛ рдЧреЗрд▓рд╛ тАФ policy environment рд╡рд░ рдЕрд╡рд▓рдВрдмреВрди рдЕрд╕реВ рд╢рдХрддреЗ (prod рдордзреНрдпреЗ xl lockers рдирд╛ рдкрд░рд╡рд╛рдирдЧреА рдЖрд╣реЗ). рддреАрди рдПрдХ-рдУрд│реА рджреБрд░реБрд╕реНрддреНрдпрд╛рдВрдирдВрддрд░ рдХрд╛рд╣реАрдЪ рдирд╛рдХрд╛рд░рд▓реЗ рдЧреЗрд▓реЗ рдирд╛рд╣реА, рдЖрдгрд┐ рдЦрд░реНрдЪ тВ╣2830 рд╡рд░реВрди тВ╣2780 рд╡рд░ рдЖрд▓рд╛ рдХрд╛рд░рдг xl locker m рдЭрд╛рд▓рд╛. рдЕрдкрдпрд╢реА test рдиреЗ policy рдиреЗ рдкрдХрдбрд▓реЗрд▓реЗрдЪ рд░рд╛рддреНрд░реАрдЪреЗ gate рдкрдХрдбрд▓реЗ: tests рд╕рд╛рдВрдЧрддрд╛рдд рдХреА рд╣реЗ config рдХрд╛рдп рдХрд░рд╛рдпрд▓рд╛рдЪ рд╣рд╡реЗ; policies рд╕рд╛рдВрдЧрддрд╛рдд рдХреА рдХреЛрдгрддреЗрд╣реА config рдХрд╛рдп рдХрд░реВ рд╢рдХрддреЗ.
тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛
- policy рдлрдХреНрдд
.tfsource рд╡рд░ рдЪрд╛рд▓рд╡рдгреЗ тАФ variables рдЖрдгрд┐ modules рдордзреАрд▓ рдореВрд▓реНрдпреЗ рдлрдХреНрдд plan рдордзреНрдпреЗрдЪ рджрд┐рд╕рддрд╛рдд - рдХреЛрдгрд╛рд▓рд╛рдЪ рд╡рд╛рдЪрддрд╛ рди рдпреЗрдгрд╛рд░реЗ рдирд┐рдпрдо, рдЖрдгрд┐ рд╕рдорд╕реНрдпрд╛ рдХрд╢реА рджреБрд░реБрд╕реНрдд рдХрд░рд╛рдпрдЪреА рд╣реЗ рд╕рд╛рдВрдЧрдгрд╛рд░рд╛ рдХреЛрдгрддрд╛рд╣реА message рдирд╕рдгреЗ
- рдлрдХреНрдд рдЗрд╢рд╛рд░рд╛ рджреЗрдгрд╛рд░реА policy check тАФ рддреЗ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ рдЕрд╕реЗрд▓ рддрд░ merge рдерд╛рдВрдмрд╡рд╛рдпрд▓рд╛рдЪ рд╣рд╡рд╛
- cleanup рд╢рд┐рд╡рд╛рдп, рд╕рд╛рдорд╛рдпрд┐рдХ account рд╡рд░
command = applyрд╕рд╣terraform test - cost estimate рд▓рд╛ рдмрд┐рд▓ рд╕рдордЬрдгреЗ тАФ рддреЛ list prices рдЖрдгрд┐ рд╡рд╛рдкрд░рд╛рдЪреНрдпрд╛ рдЕрдВрджрд╛рдЬрд╛рд╡рд░реВрди рдХрд╛рдврд▓реЗрд▓рд╛ рдЕрдВрджрд╛рдЬ рдЖрд╣реЗ
ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд
рдЦрд▒реНрдпрд╛ account рд╡рд░ тАФ pipeline рдЪрд╛рд▓рд╡рддреЗ рддреНрдпрд╛ рддрдкрд╛рд╕рдгреНрдпрд╛, рдХреНрд░рдорд╛рдиреЗ:
terraform fmt -check -recursive
terraform init -backend=false && terraform validate
tflint --recursive
terraform plan -out=tfplan && terraform show -json tfplan > plan.json
conftest test plan.json --policy policy/ # OPA / Rego rules
checkov -f plan.json # built-in rules for common cloud mistakes
infracost breakdown --path plan.json # monthly cost of the plan
terraform test # runs tests/*.tftest.hcl (Terraform 1.6+)
рдПрдХ test file (tests/lab.tftest.hcl):
run "locker_names_start_with_env" {
command = plan
variables { env = "dev" }
assert {
condition = aws_s3_bucket.locker["a"].bucket == "dev-school-locker-a"
error_message = "locker bucket names must start with the environment"
}
}
рдПрдХ conftest rule (policy/tags.rego, Rego v1 syntax):
package main
deny contains msg if {
rc := input.resource_changes[_]
rc.type == "aws_s3_bucket"
not rc.change.after.tags.owner
msg := sprintf("%s: every bucket needs an owner tag", [rc.address])
}
ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ рдЖрд╣реЗ: рддреБрдордЪреНрдпрд╛ рд╢реЗрд╡рдЯрдЪреНрдпрд╛ рддреАрди incidents рд╢реА рдЬреБрд│рдгрд╛рд▒реНрдпрд╛ рддреАрди policies рдиреЗ рд╕реБрд░реБрд╡рд╛рдд рдХрд░рд╛, рддреНрдпрд╛ merge рдерд╛рдВрдмрд╡рддреАрд▓ рдЕрд╕реЗ рдХрд░рд╛, рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХреАрд▓рд╛ рджреБрд░реБрд╕реНрддреАрдЪрд╛ рд╕реНрдкрд╖реНрдЯ message рджреНрдпрд╛.
тПня╕П рдкреБрдвреЗ
рд╕рдЧрд│реЗ conveyor belt рд╡рд░ рдареЗрд╡рд╛рдпрд▓рд╛ рддрдпрд╛рд░ рдЖрд╣реЗ: рдкреНрд░рддреНрдпреЗрдХ pull request рд╡рд░ plan, merge рд╡рд░ apply, рд╕рд╛рдард╡рд▓реЗрд▓реНрдпрд╛ keys рдирд╛рд╣реАрдд. рдкреБрдвреЗ: CI/CD рдордзреАрд▓ IaC тАФ рдЖрдгрд┐ рд╕рдВрдкреВрд░реНрдг рдЪрд┐рддреНрд░.
git checkout lesson-12-iac-cicd