评审的差异里全是空白
目标
亲自运行格式判定、结构检查和表达式 console 这三样东西,确认各自能发现什么、发现不了什么的边界。最后亲手把这三者串起来,做成在提交前运行的门禁。
为什么重要
区分“只有应用之后才知道的”和“应用之前就能知道的”,是这个模块的主题。格式由工具把标准答案定为唯一的一个,所以人不必争论——有专门只判定的模式,退出码也不同,直接就能成为门禁。结构检查只读取配置文件,查看引用是否存在、必填参数是否具备、类型是否与 schema 相符。它既不看状态也不看实物,所以发现不了值的错误。不清楚这条边界,就会发生两种事故:一种是以为通过了就安全;另一种是把本可以用静态检查发现的问题拖到计划阶段,白白浪费时间。console 填补了两者之间的空白——不必为了一个容易混淆的表达式而反复应用,一行就能确认值。
步骤
- 创建
/root/tfb-fmt/src/main.tf和/root/tfb-fmt/src/modules/net/main.tf,故意把缩进和等号对齐写得参差不齐。在/root/tfb-fmt/src中运行tofu fmt -check -diff -recursive,把输出保存到/root/tfb-fmt/fmt-before.txt,并在最后一行追加exit=<종료 코드>(占位符为退出码)。 - 在
/root/tfb-fmt/src中用tofu fmt -recursive实际修复,把被修复的文件名列表保存到/root/tfb-fmt/fmt-files.txt。然后再次运行tofu fmt -check -recursive,把输出和exit=<종료 코드>(占位符为退出码)保存到/root/tfb-fmt/fmt-after.txt。 - 在
/root/tfb-fmt/broken/main.tf中放一个缺少值的参数(例如filename =后面什么都没有),使语法损坏。在该目录中运行tofu fmt -check -recursive,把输出和exit=<종료 코드>(占位符为退出码)保存到/root/tfb-fmt/fmt-broken.txt。不要修复这个文件,保持原样。 - 在
/root/tfb-fmt/invalid/main.tf中同时放置引用了未声明变量的资源,以及在字符串位置放了列表的资源,并执行 init。把tofu validate -json的输出保存到/root/tfb-fmt/validate.json,并用从中读取的值,在/root/tfb-fmt/validate-summary.txt中写入errors=<오류 개수>(占位符为错误数量)和summaries=<요약들을 사전순으로 콤마로 이은 것>(占位符为按字典序排列并用逗号连接的各条摘要)两行。 - 在
/root/tfb-fmt/blind/main.tf中放置没有默认值的变量seed_path,以及用file()读取该路径并创建文件的资源,然后执行 init。tofu validate会通过,而tofu plan会因两个原因失败。分别运行不给值的 plan 和给出不存在路径的 plan,把后者的输出保存到/root/tfb-fmt/blind-plan.txt,并在/root/tfb-fmt/blind-report.txt中写入validate=<종료 코드>、plan_novar=<종료 코드>、plan_missing=<종료 코드>(占位符均为退出码)三行。不存在的路径使用./inputs/seed.txt,并且不要创建该文件。 - 对
/root/tfb-fmt/src执行 init,并在/root/tfb-fmt/console-in.txt中写入四行表达式——join("-", [var.env, var.app, "01"])、length(toset(["a", "a", "b"]))、"5" + 5、1 == "1"。把该文件通过管道送入tofu console,把结果保存到/root/tfb-fmt/console-out.txt。 - 在
/root/tfb-fmt/src/name.tf中,把local.name定义得与第 6 步的第一个表达式完全相同,并用把该值写成一行的local_file.name创建name.txt。新文件的格式也必须正确(fmt -check -recursive为 0),validate必须通过,并且要完成 apply。 - 创建
/root/tfb-fmt/precheck.sh。对作为第一个参数传入的目录,先做格式判定,通过后再做结构检查。格式不通过时,输出fmt这个词并以非 0 的码结束;结构不通过时,输出validate并以非 0 的码结束;两者都通过则输出ok并以 0 结束。把对/root/tfb-fmt/src运行的结果保存到/root/tfb-fmt/precheck-src.txt,把对/root/tfb-fmt/broken运行的结果保存到/root/tfb-fmt/precheck-broken.txt,并在每个文件末尾追加exit=<종료 코드>(占位符为退出码)。
参考
tofu fmt的只判定模式和实际修改的模式是不同的。门禁中使用不会修改仓库的一种。tofu validate和tofu console需要 provider 的 schema,所以必须先执行 init。tofu fmt不执行 init 也能运行。- 常见错误:把第 3 步中损坏的文件修好。这一步的任务是留下遇到无法读取的文件时的退出码。
- 常见错误:在第 5 步创建
./inputs/seed.txt使 plan 成功。这一步的任务是展示静态检查看不到的东西。 - tofu fmt · tofu validate · tofu console · 函数列表 · terraform fmt (HashiCorp)
找出格式不一致的目录树
创建 /root/tfb-fmt/src/main.tf 和 /root/tfb-fmt/src/modules/net/main.tf,故意把缩进和等号对齐写得参差不齐。在 /root/tfb-fmt/src 中运行 tofu fmt -check -diff -recursive,把输出保存到 /root/tfb-fmt/fmt-before.txt,并在最后一行追加 exit=<종료 코드>(占位符为退出码)。
这条命令不会修改文件,只做判定。有需要修改的内容时,退出码与 0 不同,这就是它可以用作门禁的原因。-diff 会显示要怎么改。
修复并再次确认
在 /root/tfb-fmt/src 中用 tofu fmt -recursive 实际修复,把被修复的文件名列表保存到 /root/tfb-fmt/fmt-files.txt。然后再次运行 tofu fmt -check -recursive,把输出和 exit=<종료 코드>(占位符为退出码)保存到 /root/tfb-fmt/fmt-after.txt。
修改模式只逐行输出被改动的文件名。如果没有递归到子目录的选项,就只看根目录的文件——两者都必须出现在列表中。
语法损坏的文件,其退出码与格式判定不同
在 /root/tfb-fmt/broken/main.tf 中放一个缺少值的参数(例如 filename = 后面什么都没有),使语法损坏。在该目录中运行 tofu fmt -check -recursive,把输出和 exit=<종료 코드>(占位符为退出码)保存到 /root/tfb-fmt/fmt-broken.txt。不要修复这个文件,保持原样。
要修复格式,首先得能读取文件。无法读取时的退出码,与“有需要修改的内容”又不相同。想一想,在制作门禁时,能否把这两者当作一回事来处理。
validate 能发现的——引用、必填参数、类型
在 /root/tfb-fmt/invalid/main.tf 中同时放置引用了未声明变量的资源,以及在字符串位置放了列表的资源,并执行 init。把 tofu validate -json 的输出保存到 /root/tfb-fmt/validate.json,并用从中读取的值,在 /root/tfb-fmt/validate-summary.txt 中写入 errors=<오류 개수>(占位符为错误数量)和 summaries=<요약들을 사전순으로 콤마로 이은 것>(占位符为按字典序排列并用逗号连接的各条摘要)两行。
validate 看的不是值,而是结构:引用是否存在、必填参数是否具备、放入的值的类型是否与 schema 相符。使用 -json,读取的就不是人,而是 CI。
validate 发现不了的——值
在 /root/tfb-fmt/blind/main.tf 中放置没有默认值的变量 seed_path,以及用 file() 读取该路径并创建文件的资源,然后执行 init。tofu validate 会通过,而 tofu plan 会因两个原因失败。分别运行不给值的 plan 和给出不存在路径的 plan,把后者的输出保存到 /root/tfb-fmt/blind-plan.txt,并在 /root/tfb-fmt/blind-report.txt 中写入 validate=<종료 코드>、plan_novar=<종료 코드>、plan_missing=<종료 코드>(占位符均为退出码)三行。不存在的路径使用 ./inputs/seed.txt,并且不要创建该文件。
静态检查只读取配置文件。变量会传入什么值、该路径上是否有文件,都是配置文件之外的事实,所以无从得知。因此通过 validate 并不意味着“可以应用”。
表达式在 console 中用一行确认
对 /root/tfb-fmt/src 执行 init,并在 /root/tfb-fmt/console-in.txt 中写入四行表达式——join("-", [var.env, var.app, "01"])、length(toset(["a", "a", "b"]))、"5" + 5、1 == "1"。把该文件通过管道送入 tofu console,把结果保存到 /root/tfb-fmt/console-out.txt。
console 会原样读取当前目录的变量和 locals。后两行是故意容易混淆的——加法中发生的类型转换,与相等比较中不发生的类型转换是不同的。
把确认过的表达式放进配置
在 /root/tfb-fmt/src/name.tf 中,把 local.name 定义得与第 6 步的第一个表达式完全相同,并用把该值写成一行的 local_file.name 创建 name.txt。新文件的格式也必须正确(fmt -check -recursive 为 0),validate 必须通过,并且要完成 apply。
把在 console 中确认过的表达式原样搬过来,就不必再为值是否正确而犹豫。因为新建了文件,所以格式判定也必须再次通过——这是在制作门禁之前要养成习惯的顺序。
制作在提交前运行的静态检查门禁
创建 /root/tfb-fmt/precheck.sh。对作为第一个参数传入的目录,先做格式判定,通过后再做结构检查。格式不通过时,输出 fmt 这个词并以非 0 的码结束;结构不通过时,输出 validate 并以非 0 的码结束;两者都通过则输出 ok 并以 0 结束。把对 /root/tfb-fmt/src 运行的结果保存到 /root/tfb-fmt/precheck-src.txt,把对 /root/tfb-fmt/broken 运行的结果保存到 /root/tfb-fmt/precheck-broken.txt,并在每个文件末尾追加 exit=<종료 코드>(占位符为退出码)。
顺序很重要。如果混有无法读取的文件,结构检查也没有意义,所以要先做格式判定。必须使用只判定、不修改的选项,门禁才不会改动仓库。