Verification Tools
เครื่องมือทุกตัวด้านล่างรันจริงกับ Terraform 1.16.4 / Terragrunt 1.1.0 และ provider hashicorp/aws 6.66.0 ที่ pin ไว้ พร้อม Docker image ghcr.io/terraform-linters/tflint, bridgecrew/checkov, และ openpolicyagent/conftest ที่ pin ไว้ของคอร์สนี้ — ไม่เคยรันกับ AWS จริงเลย (provider config ทุกตัวในคอร์สนี้ใช้ access_key = "mock" + skip_credentials_validation = true, และ terraform plan ไม่ติดต่อ AWS เลยเมื่อตั้งค่าแบบนั้น) นี่คือบันไดตามลำดับที่จับสิ่งต่างๆ ได้จริง ถูกและเร็วที่สุดก่อน
flowchart TD A["fmt -check<br/>แค่ formatting"] --> B["validate<br/>schema + type error + deprecation warning"] B --> C["plan -detailed-exitcode<br/>diff จริงกับ provider schema จริง (mock)"] C --> D["show -json + jq<br/>plan แบบ machine-readable ที่ CI/policy อ่านจริง"] D --> E["terraform test + mock_provider<br/>assertion กับ plan, ไม่มี infra จริง"] E --> F["tflint / checkov<br/>rule catalog จริง, finding เฉพาะ cloud"] F --> G["conftest<br/>Rego policy กำหนดเองกับ show -json"] G --> H["terragrunt hcl validate/render/run --all<br/>live tree, offline"]
terraform fmt -check — แค่ formatting ไม่ใช่ความถูกต้อง
หัวข้อที่มีชื่อว่า “terraform fmt -check — แค่ formatting ไม่ใช่ความถูกต้อง”$ terraform fmt -check -diff main.tfExit ไม่ใช่ศูนย์และพิมพ์ diff ถ้า formatting ผิด ไม่แก้อะไรเองเลยเว้นแต่รันโดยไม่มี -check จับ bug เชิง semantic จาก catalog นี้ได้ศูนย์ตัว — ไฟล์ที่ format สมบูรณ์แบบยัง hardcode account ID ผิดได้ เป็น CI gate ที่ถูกที่สุด และเป็นตัวแรกที่ harness ของคอร์สนี้รันก่อนอย่างอื่นทั้งหมด บนทุก fence
terraform validate — schema, type, และ (สำคัญมาก) warning
หัวข้อที่มีชื่อว่า “terraform validate — schema, type, และ (สำคัญมาก) warning”$ terraform validate -jsonจับได้: entry 3 (module + count ที่ขัดกัน), entry 11 (for_each บนค่าที่ไม่รู้), entry 13 (argument ที่ hallucinate มา), entry 15 (reference cycle) — ทุกตัวเป็น error จริงหนักๆ จับ entry 2 ด้วย (argument ที่ deprecated) — แต่เป็นแค่ warning ซึ่ง CI step ที่เช็คแค่ terraform validate; echo $? เพิกเฉยไปเงียบๆ CI check ที่ใช้ได้จริงคือ grep JSON:
terraform validate -json | jq -e '[.diagnostics[] | select(.severity=="warning")] | length == 0'validate ไม่เคยติดต่อ provider API จริงเลย — มันเช็ค configuration กับ schema ของ provider ที่ download มาครั้งเดียวตอน init แบบ offline ทั้งหมด
terraform plan -detailed-exitcode — diff จริง ไม่ใช่แค่เช็ค syntax
หัวข้อที่มีชื่อว่า “terraform plan -detailed-exitcode — diff จริง ไม่ใช่แค่เช็ค syntax”$ terraform plan -detailed-exitcodeExit code 0 = ไม่มีการเปลี่ยนแปลง, 1 = error, 2 = มีการเปลี่ยนแปลง — exit code ที่ CI ใช้ branch จริงๆ แทนที่จะ parse plan text ด้วย mock AWS credentials ของคอร์สนี้ plan คำนวณ diff จริงกับ provider schema จริงสำหรับ resource type ส่วนใหญ่ได้จริง (พิสูจน์ตลอด AI Bug Catalog — entry 7, 9, 10, 11 ล้วนเป็น output จริงของ plan ไม่มีการเรียก network ไป AWS เลย) จับได้: การ replace จากการเลื่อน index ของ count ใน entry 10, ขอบเขตของ prevent_destroy ใน entry 9, การแทนที่ของ nullable ใน entry 7 — ไม่มีตัวไหนที่ validate เพียงอย่างเดียวเห็นได้ เพราะปรากฏแค่เป็น diff ไม่ใช่การละเมิด schema
terraform show -json + jq — สิ่งที่ CI และ policy tool อ่านจริงๆ
หัวข้อที่มีชื่อว่า “terraform show -json + jq — สิ่งที่ CI และ policy tool อ่านจริงๆ”$ terraform plan -out=plan.out && terraform show -json plan.out > plan.json$ jq '.resource_changes[] | select(.change.actions[0] == "delete") | .address' plan.jsonนี่คือ schema ที่ conftest/OPA (ด้านล่าง) และ harness ของคอร์สนี้อ่าน — ไม่เคยเป็น plan text ที่มนุษย์อ่าน พิสูจน์ตรงๆ ใน entry 8: default ตรงๆ ของ ephemeral variable ปรากฏใน variables block ของ JSON นี้เป๊ะ แม้จะถูกกันออกจาก terraform.tfstate ได้ถูกต้องแล้วก็ตาม string ที่ดูเหมือน secret ไหนก็ตามที่ plan artifact ของ show -json ที่ archive ไว้อาจรั่วออกมาจับได้ตรงนี้ ไม่ใช่ใน CLI output ที่ถูก redact แล้ว
terraform test กับ mock_provider
หัวข้อที่มีชื่อว่า “terraform test กับ mock_provider”# tests/example.tftest.hcl (fragment — see modules/testing-modules for a full worked example)mock_provider "aws" {}
run "plan_only" { command = plan assert { condition = aws_instance.app.instance_type == "t3.micro" error_message = "unexpected instance_type" }}terraform test (1.6+) กับ mock_provider (1.7+) รัน assertion เต็มรูปแบบระดับ plan โดยไม่มีการเรียก provider จริงเลย — มีประโยชน์มากสำหรับ resource ของ azurerm/google ที่ plan จริงต้องการ subscription/project จริง (ดู Testing Modules สำหรับตัวอย่างครบของ aws เอง รวม override_resource) นี่เป็นขั้นเดียวในบันไดนี้ที่ทำงานได้ดีเหมือนกันทั้งสามคลาวด์ ไม่ว่า plan จริงจะเข้าถึงอะไรได้บ้าง
tflint — finding จริงและปัจจุบันกับ rule catalog จริง
หัวข้อที่มีชื่อว่า “tflint — finding จริงและปัจจุบันกับ rule catalog จริง”$ docker run --rm -v "$(pwd)":/data -v <plugin-cache>:/root/.tflint.d -w /data ghcr.io/terraform-linters/tflint --init$ docker run --rm -v "$(pwd)":/data -v <plugin-cache>:/root/.tflint.d -w /data ghcr.io/terraform-linters/tflintOutput จริงกับ aws_instance เปล่าๆ ที่ไม่มี required_providers/required_version block:
2 issue(s) found:Warning: Missing version constraint for provider "aws" in `required_providers` (terraform_required_providers)Warning: terraform "required_version" attribute is required (terraform_required_version)จับ finding ที่จริงและปัจจุบันได้ — rule ID จริงพิสูจน์กับเอกสารของ tflint-ruleset-aws 0.40.0 ที่ pin ไว้ ไม่ จับ entry 21 (ชื่อ rule ที่คิดขึ้นเองใน .tflint.hcl): tflint ไม่ error เมื่อ config อ้างอิง rule ที่ไม่มีอยู่จริง มันแค่ไม่เคยเอาไปใช้ และการรันข้างบนรายงานว่าสำเร็จบนทุกอย่างที่ config ตั้งใจเช็ค --init ต้อง persist plugin ที่ download ไว้ข้ามการรัน — mount volume จริงสำหรับ ~/.tflint.d ไม่ใช่อันใหม่ทุกครั้งที่ docker run --rm ไม่งั้นทุกการรันจะ download ใหม่ และ cache ที่ค้างอาจปิดบัง plugin-install ที่ล้มเหลวอย่างเงียบๆ
checkov — finding จริงและปัจจุบัน
หัวข้อที่มีชื่อว่า “checkov — finding จริงและปัจจุบัน”$ docker run --rm -v "$(pwd)":/tf bridgecrew/checkov -d /tf --compact --quietOutput จริงกับ aws_s3_bucket เปล่าๆ:
terraform scan results:Passed checks: 9, Failed checks: 7, Skipped checks: 0Check: CKV_AWS_144: "Ensure that S3 bucket has cross-region replication enabled"Check: CKV2_AWS_6: "Ensure that S3 bucket has a Public Access block"Check: CKV_AWS_18: "Ensure the S3 bucket has access logging enabled"Check: CKV_AWS_21: "Ensure all data stored in the S3 bucket have versioning enabled"...ข้อควรระวังเดียวกับ tflint: check ID ของ checkov ที่ไม่มีอยู่จริง (รูปแบบที่ AI hallucinate บ่อยครั้งข้ามทั้งสามคลาวด์ — ดู catalog entry ของ Azure sibling ของคอร์สนี้ที่ CKV_AZURE_59 เทียบกับ CKV_AZURE_35) ถูกข้ามไปเงียบๆ ไม่ถูกปฏิเสธเป็น config ที่ invalid
conftest — Rego policy กำหนดเองกับ show -json
หัวข้อที่มีชื่อว่า “conftest — Rego policy กำหนดเองกับ show -json”$ docker run --rm -v "$(pwd)":/project openpolicyagent/conftest test --policy policy plan.jsonอ่าน output จาก show -json ของขั้นข้างบนตรงๆ — พิสูจน์ตลอด output จริงของ FAIL/PASS ใน entry 20 conftest ปัจจุบัน default เป็น syntax Rego v1 (import rego.v1, contains/if): deny[msg] { ... } ก่อน v1 load ไม่ผ่านเลย ด้วย rego_parse_error จริง ไม่ใช่ rule ที่ไม่เคยทำงานอย่างเงียบๆ
terragrunt hcl validate / render / run --all plan — live tree, offline
หัวข้อที่มีชื่อว่า “terragrunt hcl validate / render / run --all plan — live tree, offline”$ terragrunt hcl validate --working-dir live/prod/vpc # recursive HCL syntax check$ terragrunt render --format json --working-dir live/dev/vpc # resolved config, offline$ terragrunt run --all plan --working-dir . # dependency-ordered, offline with mock_outputshcl validate เช็คแค่ว่า HCL parse ผ่าน — พิสูจน์ตรงๆ ใน entry 17 มันผ่านไฟล์ terragrunt.hcl สองไฟล์ที่ copy-paste เหมือนกันทุกตัวอักษรซึ่ง deploy ทับ account ผิด render resolve include/local/function call ทุกตัวไปเป็นค่าสุดท้ายโดยไม่เคยเรียก terraform เลย — วิธีที่เร็วที่สุดที่จะ เห็น จริงๆ ว่า inputs ที่ resolve แล้วของสอง environment ต่างกันหรือไม่ run --all plan ต้องมี root.hcl (หรือ flag --terragrunt บน harness ของคอร์สนี้เอง) และรันทุกหน่วยตามลำดับ dependency จริง — run-all/plan-all (entry 16) ทั้งคู่เป็นคำสั่งที่ไม่รู้จักตั้งแต่ CLI redesign 1.1 นี่คือตัวแทนจริงและปัจจุบัน
Harness ของคอร์สนี้เอง: tools/verify-snippets.mjs
หัวข้อที่มีชื่อว่า “Harness ของคอร์สนี้เอง: tools/verify-snippets.mjs”ทุก hcl fence ในคอร์สนี้ที่บรรทัดแรกเป็น # <path> ลงท้ายด้วย .tf/.hcl/.tftest.hcl/.tfvars ถูกเก็บเป็นไฟล์จริงที่ path นั้น (relative กับบทเรียนของมัน) และรันผ่าน terraform init -backend=false + validate + fmt -check บวก test/plan/terragrunt hcl validate/render ภายใต้ flag --test/--plan/--terragrunt มีข้อจำกัดจริงสองอย่างที่ต้องพูดตรงๆ:
-backend=falseเสมอ Blockbackend "s3"/"azurerm"/"gcs"ที่บทเรียนไหนประกาศไว้ไม่เคยถูก init จริงเลย — คอร์สนี้ไม่มี backend credential จริงเลย ดังนั้นคำกล่าวอ้างเรื่อง backend-locking ของ entry 5 มาจากเอกสาร ไม่ใช่จากการรันของ harness (ดู entry นั้นสำหรับการ cite ตรงๆ)- azurerm/google
planแบบ offline ไม่ได้กับ resource ส่วนใหญ่ Harness นี้พยายามรันterraform planเสมอและ pattern-match ข้อความ offline-auth-failure ที่รู้จักเพื่อพิมพ์not possible offline: <reason>แทนที่จะเป็น stack trace ดิบๆ — จริงสำหรับ provider สองตัวนี้ ไม่ใช่ workaround mock credential ของ aws (skip_credentials_validation,skip_requesting_account_id,skip_metadata_api_check,skip_region_validation) คือสิ่งที่ทำให้planของ aws รันได้แบบ offline สมบูรณ์ ในขณะที่ azurerm/google ส่วนใหญ่ทำไม่ได้
Fence ที่บรรทัดแรกมี @expect-error (ใช้ตลอด AI Bug Catalog สำหรับทุก snippet ที่พังโดยตั้งใจ) ถูก harness นี้ข้ามไปโดยตั้งใจ — เนื้อหาบทเรียนพก error จริงที่จับมาแล้วแทนที่จะให้ harness derive มันใหม่ทุกครั้งที่รัน
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”บันได verification เรียงตามความถูกและเร็วก่อน:
1. fmt -check → แค่ formatting, ไม่ครอบคลุม semantic เลย2. validate → schema/type error + warning (เช็ค warning ตรงๆ ด้วย!)3. plan -detailed-exitcode → diff จริง — count/for_each churn, ขอบเขตของ lifecycle, การแทนที่ของ nullable4. show -json + jq → สิ่งที่ CI/policy tool อ่านจริง ไม่ใช่ plan text ที่มนุษย์อ่าน5. test + mock_provider → assertion ระดับ plan, ไม่เรียก provider จริงเลย, ใช้ได้ทุกคลาวด์6. tflint / checkov → rule catalog จริง — แต่เงียบกับ rule ID ที่คิดขึ้นเอง7. conftest → policy กำหนดเองกับ show -json — ต้องใช้ syntax Rego v18. terragrunt hcl validate/render/run --all → live tree, offline, เรียงตาม dependency
การผ่านทุกขั้นพิสูจน์ว่า configuration ถูกสร้างมาดีและสอดคล้องกันภายในไม่ได้พิสูจน์ว่า prevent_destroy ป้องกัน resource ที่ถูกต้อง หรือ dev กับ proddeploy ทับ account ต่างกันจริง — เรื่องพวกนั้นต้องการคนอ่าน checklistCommon Misconceptions
หัวข้อที่มีชื่อว่า “Common Misconceptions”- “
terraform validateผ่านแปลว่า configuration ถูกต้อง” มันแปลว่า configuration schema valid เท่านั้น พิสูจน์ตลอด catalog: 15 จาก 23 entry validate และ plan ผ่านสะอาด - “tflint/checkov รันผ่านสีเขียวแปลว่า check ที่ตั้งใจไว้ทุกตัวรันจริง” พิสูจน์ตรงๆ ใน entry 21: rule ID ที่คิดขึ้นเองให้ output “สำเร็จ” เหมือนกันเป๊ะกับตัวที่ตั้งชื่อถูก — เครื่องมือไม่มีทางแยกแยะ “rule นี้ไม่มีอยู่จริง” จาก “rule นี้ไม่เจออะไรให้ flag” ได้เลย
- “
-backend=falseเป็นแค่ข้อจำกัดของ harness ไม่ใช่ความกังวลจริง” มันคือสภาพจริงของ CI step ส่วนใหญ่ที่validate/planก่อนขั้นapplyจริงด้วย — ซึ่งเป็นเหตุผลตรงๆ ที่ drift เรื่อง backend-locking ของ entry 5 มองไม่เห็นจากvalidate/planใน pipeline จริงด้วยเช่นกัน ไม่ใช่แค่ harness ของคอร์สนี้