幂等的资源保障脚本
目标
编写能够保证目录、文件、文本行和权限状态的幂等脚本,连续应用两次,并以 changed 变为 0 来证明幂等性。最后再加入状态文件、漂移检测与锁。
为什么重要
幂等性是指多次执行同一操作,结果仍然相同;它保证自动化任务可以安全地重新运行。证明方法是连续应用两次,观察第二次 plan 是否没有变更。破坏幂等性的地方几乎总是固定的:不用专用模块,而是通过 shell 或 command 直接执行命令;以及用 >> 追加文本行。因此,本实验中的所有脚本都采用相同骨架:“修改之前先检查”。最后添加的状态文件与锁,可以让你亲自理解真实 IaC 工具为何需要这些附属机制。
步骤
- 创建
/root/iac2/ensure_dir.sh <경로>。目录不存在时创建并输出changed,已存在时输出unchanged。两种情况的退出码都为 0。输出必须是只包含该单词的一行(评分会把unchanged与整行精确比较)。 - 创建
/root/iac2/ensure_file.sh <경로> <내용>。文件不存在或内容不同时写入内容并输出changed,内容已经相同时输出unchanged。 - 创建
/root/iac2/ensure_line.sh <파일> "<줄>"。该行已经精确存在时输出unchanged;存在同一键(行中第一个单词)的其他行时,在原位置替换为新行并输出changed;完全不存在时追加到末尾并输出changed。无论执行多少次,该行在文件中都必须恰好存在一份,其他行则保持不变。 - 创建
/root/iac2/ensure_mode.sh <파일> <8진수모드>。通过stat -c %a读取当前权限;不同时执行chmod并输出changed,相同时输出unchanged。文件不存在时返回非 0 退出码。 - 创建
/root/iac2/run.sh。使用上述四个脚本恰好保证 4 个资源(例如目录 1 个 + 文件 2 个 + 权限 1 个)。最后一行按unchanged=<수> changed=<수>的顺序输出汇总。把第一次运行输出保存到/root/iac2/run1.txt,紧接着把第二次运行输出保存到/root/iac2/run2.txt。第一次运行的 changed 必须不小于 1,第二次运行的汇总必须是unchanged=4 changed=0。 - 创建
/root/iac2/state.json。在resources数组中记录刚才保证的资源(至少 2 个),每个元素包含path、type(file或dir)和sha256。type不是dir的项目必须对应真实存在的文件,且sha256值必须与sha256sum <경로>输出的第一个字段完全相同。顶层还要包含applied_at(应用时间)。 - 创建
/root/iac2/drift.sh <상태파일>。读取参数指定的状态文件,验证其中记录的资源。全部一致时退出码为 0;内容变化或文件消失时,输出不一致的路径并返回非 0 退出码。对于type为dir的项目,只检查是否存在。 - 创建
/root/iac2/apply-lock.sh。锁文件路径从LOCK_FILE环境变量读取,未设置时使用/root/iac2/.lock。如果没有锁,就通过条件创建获得锁;完成工作并正常退出时必须将其删除(退出码为 0,不能残留锁文件)。已经锁定时不要等待,而应立即以退出码 2 失败,并输出包含lock一词的消息。此时绝不能删除其他进程的锁文件。
参考
- 第 5 步的汇总必须先写
unchanged=,后写changed=。评分会把grep -oE 'changed=[0-9]+'的最后一个匹配项视为总数,而unchanged=4中也包含changed=4字符串。 - 锁的条件创建应使用
set -o noclobber后执行: > "$LOCK_FILE"等方式。先通过[ -e ]检查再创建,会让两个进程同时通过。 - 只有真正获得锁后,才能设置用于清理的
trap。如果提前设置,获取失败的进程会删除其他进程的锁。 - 默认等待时间为 0 秒,也就是立即失败,因为在 CI 中悄无声息地挂起,不如快速失败后让人查看情况。
- 常见错误:使用
>>追加文本行(每次运行都会增加)、无条件覆盖文件而始终报告 changed、在 run.sh 之前创建 state.json 导致哈希不一致。
保证目录状态
创建 /root/iac2/ensure_dir.sh <경로>。目录不存在时创建并输出 changed,已存在时输出 unchanged。两种情况的退出码都为 0。输出必须是只包含该单词的一行(评分会把 unchanged 与整行精确比较)。
ensure_dir.sh <경로> 应在目录不存在时创建并输出 changed,已存在时输出 unchanged。两种情况的退出码都为 0。输出必须是只包含该单词的一行。评分会把 unchanged 与整行精确比较。
保证文件内容
创建 /root/iac2/ensure_file.sh <경로> <내용>。文件不存在或内容不同时写入内容并输出 changed,内容已经相同时输出 unchanged。
使用 ensure_file.sh <경로> <내용>。写入前先与当前内容比较。不存在或不同时写入并输出 changed,相同时不要修改,输出 unchanged。如果无条件写入,就会始终报告 changed,无法证明幂等性。
保证文本行且不重复
创建 /root/iac2/ensure_line.sh <파일> "<줄>"。该行已精确存在时输出 unchanged;存在同一键(行中第一个单词)的其他行时,在原位置替换为新行并输出 changed;完全不存在时追加到末尾并输出 changed。无论执行多少次,该行在文件中都必须恰好存在一份,其他行保持不变。
使用 ensure_line.sh <파일> "<줄>"。如果用 >> 追加,每次执行都会累积文本行。相同行已经存在时输出 unchanged;存在相同键(第一个单词)的其他行时,在原位置替换并输出 changed;不存在时追加到末尾并输出 changed。其他行必须保持不变。
保证权限状态
创建 /root/iac2/ensure_mode.sh <파일> <8진수모드>。通过 stat -c %a 读取当前权限;不同时执行 chmod 并输出 changed,相同时输出 unchanged。文件不存在时返回非 0 退出码。
ensure_mode.sh <파일> <8진수모드> 使用 stat -c %a 读取并比较当前权限。不同时执行 chmod 后输出 changed,相同时输出 unchanged。不要尝试对不存在的文件执行 chmod 后仍报告成功,应先检查文件是否存在。
运行两次以证明幂等性
创建 /root/iac2/run.sh。使用上述四个脚本恰好保证 4 个资源(例如目录 1 个 + 文件 2 个 + 权限 1 个)。最后一行按 unchanged=<수> changed=<수> 的顺序输出汇总。把第一次运行输出保存到 /root/iac2/run1.txt,紧接着把第二次运行输出保存到 /root/iac2/run2.txt。第一次运行的 changed 必须不小于 1,第二次运行的汇总必须是 unchanged=4 changed=0。
用 run.sh 恰好保证 4 个资源,并在最后一行按 unchanged=<수> changed=<수> 的顺序输出汇总。顺序很重要。把第一次运行输出保存到 /root/iac2/run1.txt,紧接着把第二次运行输出保存到 /root/iac2/run2.txt。
保存状态文件
创建 /root/iac2/state.json。在 resources 数组中记录刚才保证的资源(至少 2 个),每个元素包含 path、type(file 或 dir)和 sha256。type 不是 dir 的项目必须对应真实存在的文件,且 sha256 值必须与 sha256sum <경로> 输出的第一个字段完全相同。顶层还要包含 applied_at(应用时间)。
在 /root/iac2/state.json 中记录刚刚应用的资源。每个元素包含 path、type、sha256,文件项目的哈希必须与 sha256sum <경로> 输出的第一个字段完全相同。顶层还要添加 applied_at。必须在运行 run.sh 之后创建,哈希才会一致。
检测漂移
创建 /root/iac2/drift.sh <상태파일>。读取参数指定的状态文件并验证其中记录的资源。全部一致时退出码为 0;内容变化或文件消失时,输出不一致的路径并返回非 0 退出码。对于 type 为 dir 的项目,只检查是否存在。
drift.sh <상태파일> 必须读取参数传入的状态文件。如果硬编码路径,就无法读取评分程序创建的临时状态文件。既要检测内容变化,也要检测文件删除;同时输出不一致的路径,让人知道应该检查什么。
状态锁
创建 /root/iac2/apply-lock.sh。锁文件路径从 LOCK_FILE 环境变量读取,未设置时使用 /root/iac2/.lock。如果没有锁,就通过条件创建获得锁;完成工作并正常退出时必须删除它(退出码为 0,不能残留锁文件)。已经锁定时不要等待,而应立即以退出码 2 失败,并输出包含 lock 一词的消息。此时绝不能删除其他进程的锁文件。
apply-lock.sh 必须遵循 LOCK_FILE 环境变量。没有锁时通过条件创建获得锁,并在正常退出时必须删除它(退出码 0)。已经锁定时不要等待,而应立即以退出码 2 失败,并通过消息说明原因是锁。绝不能删除其他进程的锁文件。