משאב זקוק למספר משתנה של בלוקים מקוננים זהים (לדוגמה, כללי ingress) המונעים על ידי רשימה/מפה.
→השתמש בבלוק `dynamic` שה-`for_each` שלו מבצע איטרציה על האוסף; התייחס לכל אלמנט דרך איטרטור הבלוק (שם ברירת מחדל = תווית בלוק) בתוך `content {}`.
למה: בלוקים דינמיים מייצרים בלוקים מקוננים חוזרים ללא העתק-הדבק; האיטרטור שומר כל בלוק שנוצר מקושר לאלמנט המקור שלו.
מקור↗
צור משאב אחד עבור כל כניסה במפה של אובייקטים, עם מפתח יציב כך ששינוי סדר לעולם לא יכפה החלפה.
→הגדר `for_each = var.objects` (מפה). השתמש ב-`each.key` עבור המפתח היציב וב-`each.value.<attr>` עבור שדות. הימנע מ-`count` כאן – שינויי אינדקס גורמים לתנודתיות.
למה: מפתחות מפה הם זהויות יציבות במצב; אינדקסי רשימה הם עמדות וזזים כאשר אלמנטים מתווספים/מוסרים.
החלט בין `count` לבין `for_each` עבור מספר מופעים.
→השתמש ב-`for_each` כאשר למופעים יש זהויות מובחנות (סט/מפה); השתמש ב-`count` רק עבור N עותקים זהים, שאינם רגישים לסדר. העדף `for_each` לכל דבר שעשוי לגדול/להתכווץ.
למה: `for_each` מתייחס לפי מפתח (resource["key"]); `count` מתייחס לפי אינדקס (resource[0]) אשר משנה את הסדר בהוספות/מחיקות.
משתנה קלט הוא אובייקט שבו חלק מהתכונות הן אופציונליות וזקוקות לערכי ברירת מחדל.
→הגדר את הטיפוס שלו כ-`object({ name = string, size = optional(number, 10) })`. `optional(type, default)` מספק את ערך ברירת המחדל כאשר הקורא משמיט את התכונה.
למה: `optional()` עם ערך ברירת מחדל שומר על קוד קצר עבור הקוראים תוך הבטחת ערך קונקרטי בהמשך הזרם – ללא טיפול ב-null בכל מקום.
מקור↗
אמת הנחה לגבי משאב לפני יישום, או הבטח תוצאה לאחר מכן.
→השתמש ב-`lifecycle { precondition { ... } }` כדי לאמת קלטים לפני יצירה/עדכון, וב-`postcondition` כדי לאמת פלטים לאחר מכן. שניהם מקבלים `condition` + `error_message`.
למה: תנאים מותאמים אישית נכשלים במהירות עם הודעה ברורה במקום לייצר יישום שבור או שגיאה מבלבלת בהמשך הזרם.
מקור↗
משאב חייב להיווצר מחדש בכל פעם שמשאב או תכונה אחרים משתנים.
→הוסף `lifecycle { replace_triggered_by = [aws_x.y.id] }`. כאשר הערך המוצלב משתנה, Terraform כופה החלפה של משאב זה.
למה: מבטא תלות החלפה באופן הצהרתי, ונמנע משימוש ידני ב-`-replace` על כל שינוי קשור.
החלפת משאב גורמת להשבתה מכיוון שהישן מושמד לפני שהחדש נוצר.
→הגדר `lifecycle { create_before_destroy = true }` כך ש-Terraform יספק את ההחלפה תחילה, ואז ישמיד את הישן. וודא שמות ייחודיים/ללא התנגשויות קשות.
למה: החלפה ללא השבתה; אך היזהר מהתנגשויות שמות וממגבלות מכסה כאשר שניהם קיימים לזמן קצר.
דחה ערכי קלט לא חוקיים מוקדם (לדוגמה, סביבה שאינה dev/stage/prod).
→הוסף בלוק `validation { condition = contains(["dev","stage","prod"], var.env), error_message = "..." }` למשתנה.
למה: תופס קלט שגוי בזמן התכנון עם הודעה קריאה במקום להיכשל עמוק בתוך קריאת ספק.
התייחס לערך שאולי לא קיים מבלי לקרוס את התוכנית.
→השתמש ב-`try(local.maybe.value, "default")` כדי לחזור לערך ברירת מחדל במקרה של שגיאות, או ב-`can(expr)` כדי לקבל ערך בוליאני אם ביטוי מצליח.
למה: טיפול חינני בנתונים אופציונליים/בעלי צורה משתנה; מונע "Error: Unsupported attribute" על מפתחות חסרים.
המר רשימה למפה, או סנן/עצב אוסף עבור ארגומנט משאב.
→השתמש בביטוי `for`: `{ for u in var.users : u.name => u.role if u.active }` (מפה) או `[for x in list : upper(x)]` (רשימה).
למה: ביטויי `for` הם הדרך המקובלת לעצב מחדש נתונים; סעיף ה-`if` מסנן, וצורת `k => v` בונה מפות.
משתנה או פלט מכילים סוד שאין להדפיס בפלט של תוכנית/יישום.
→סמן את המשתנה `sensitive = true` (וגם פלטים). Terraform מסתיר אותו בפלט ה-CLI, אם כי הוא עדיין נשמר במצב.
למה: מונע חשיפה מקרית ביומנים/פלט CI; המצב עצמו עדיין חייב להיות מוגן (backend מוצפן, בקרת גישה).
נהל משאבים בשני אזורים/חשבונות בתוך תצורה אחת.
→הצהר על ספקים עם כינויים (`provider "aws" { alias = "west" region = "us-west-2" }`) והגדר `provider = aws.west` על משאבים או העבר למודולים.
למה: כינויים מאפשרים לתצורה אחת למקד מופעי ספק מרובים; מודולים מקבלים אותם במפורש באמצעות ארגומנט ה-`providers`א.
תלות נסתרת (שאינה מבוטאת באמצעות הפניות) גורמת לבעיות סדר.
→הוסף `depends_on = [aws_iam_role_policy.x]` כדי לכפות סדר. השתמש בזה במשורה – העדף תלויות עקיפות באמצעות הפניות לתכונות.
למה: `depends_on` מפורש מטפל בתלויות שהגרף אינו יכול להסיק, אך שימוש יתר יוצר תוכניות שמרניות ואיטיות יותר.
רנדר קובץ תצורה/נתוני משתמש מתבנית עם משתנים מובנים.
→השתמש ב-`templatefile("${path.module}/tpl.tftpl", { items = local.items })`; התבנית משתמשת באינטרפולציה `%{ for }` / `${}`.
למה: `templatefile` שומר על עיבוד טהור/בזמן התכנון (בניגוד לספק התבניות המיושן) ותומך בלולאות/תנאים.
בנה רשימה שטוחה של כל שילוב (subnet, rule) כדי להזין ל-`for_each` יחיד.
→השתמש ב-`setproduct(var.subnets, var.rules)` עבור המכפלה הקרטזית, או ב-`flatten([for ...])` כדי לקפל רשימות מקוננות לאחת.
למה: פונקציות אלו הופכות נתונים מקוננים לאוסף השטוח ובעל המפתחות הייחודיים הנדרש על ידי `for_each`.