改了锁文件一行,init 就停了
目标
亲手创建依赖锁文件,加上约束来收窄,删除后重新生成,破坏哈希再恢复。亲手确认 init 每次都核对什么,以及为什么只提交这个文件而不提交安装目录。
为什么重要
锁文件不是用来阅读的文件,而是团队的契约。选中了哪个版本(version)、允许了什么(constraints)、这个包与当时的那个是否相同(hashes),这三件事都写在这里,init 每次都会核对这三项。没有契约,任何命令都无法生成计划;校验和一旦不符,安装就会当场被拦住。不提交这个文件的团队,每个人会获取到不同的版本,于是“在我的笔记本上是好的”就会以 provider 为单位出现。相反,安装目录不提交——它是因平台而异的二进制文件,会使仓库膨胀,而且 init 随时可以重新生成。
步骤
- 在
/root/tfb-lock/app/main.tf中不带版本约束地声明 local·random 两个 provider,并放置random_pet.tag(length 2)和把它的 id 写入note.txt的local_file.note。执行 init 后,阅读生成的.terraform.lock.hcl,在/root/tfb-lock/lock-read.txt中写入local_version=<값>、random_version=<값>、local_hashes=<local 블록의 h1 해시 줄 수>三行(占位符依次为 local 的版本、random 的版本、local 块中 h1 哈希的行数)。 - 重新创建
/root/tfb-lock/pinned/main.tf,这次从一开始就在 local 中写上version = "2.9.0",random 不带约束。用local_file.fixed声明一个fixed.txt,然后执行 init。接着从锁文件中读取各 provider 的 constraints 值,在/root/tfb-lock/constraints.txt中写入local=<값>(占位符为该值)和random=none两行。 - 在
/root/tfb-lock/app/.terraform/providers/之下找出实际安装的 provider 包的位置,在/root/tfb-lock/installed.txt中写入local=<버전>/<플랫폼>(占位符依次为版本、平台)和random=<버전>/<플랫폼>(占位符依次为版本、平台)两行。 - 创建
/root/tfb-lock/fresh/,只复制/root/tfb-lock/app/main.tf(不要带来锁文件和.terraform/)。不执行 init,直接运行tofu plan,把错误输出保存到/root/tfb-lock/no-init.txt。在 fresh 中不要执行 init。 - 在
/root/tfb-lock/badpin/main.tf中只使用 local,用version = "2.5.0"固定版本,并放置一个local_file.pinned。运行 init,把失败输出保存到/root/tfb-lock/pin-fail.txt,然后把约束改为2.9.0,使 init 成功。 - 到这时才给
/root/tfb-lock/app/main.tf的 local 加上version = "2.9.0",再次执行 init。确认锁文件的 local 块中是否生成了 constraints 这一行,并在/root/tfb-lock/stale-lock.txt的第一行写入after_init=<그 값, 없으면 none>(占位符为该值,没有则为 none)。然后删除.terraform.lock.hcl,不用 init,而是用tofu providers lock -fs-mirror=/opt/tofu-mirror -platform=linux_amd64重新生成,把它的输出保存到/root/tfb-lock/relock.txt,并在stale-lock.txt的第二行写入after_relock=<지금 값>(占位符为当前的值)。 - 把
/root/tfb-lock/app/.terraform.lock.hcl中 local 的一行哈希改成任意值,删除.terraform/之后执行 init。把校验和错误的输出保存到/root/tfb-lock/tamper.txt。然后删除锁文件,重新执行 init,恢复为正确的哈希。 - 创建
/root/tfb-lock/audit.sh。读取作为第一个参数传入的目录中的.terraform.lock.hcl,每行输出一个没有 constraints 行的 provider 块的名称,只要有一个就以退出码 1 结束,没有则以 0 结束。把对/root/tfb-lock/app运行的结果保存到/root/tfb-lock/audit-app.txt,把对/root/tfb-lock/badpin运行的结果保存到/root/tfb-lock/audit-pin.txt,并在每个文件的最后一行追加exit=<종료 코드>(占位符为退出码)。
参考
- Pod 中的 provider 只来自 /opt/tofu-mirror 文件系统镜像源。镜像源中每个 provider 只有一个版本,平台只有 linux_amd64。
- 所以,从注册表获取其他平台哈希的方式在这个 Pod 中无法使用。取而代之,使用指向镜像源的
-fs-mirror。 - 常见错误:在第 4 步的 fresh 目录中,为了消除错误而执行了 init。这一步的任务就是留下未执行 init 状态下的错误。
- 常见错误:在第 7 步保留错误的哈希,只加上 -upgrade。版本选择不变,会再次被同一项校验和检查拦住。
- 第 6 步的关键在于:init 会“没有需要修改的理由就不写锁文件”。即使用 -upgrade,也不会生成 constraints 这一行。
- Dependency Lock File · tofu init · tofu providers lock · tofu providers · CLI 配置文件
阅读第一次 init 生成的契约
在 /root/tfb-lock/app/main.tf 中不带版本约束地声明 local·random 两个 provider,并放置 random_pet.tag(length 2)和把它的 id 写入 note.txt 的 local_file.note。执行 init 后,阅读生成的 .terraform.lock.hcl,在 /root/tfb-lock/lock-read.txt 中写入 local_version=<값>、random_version=<값>、local_hashes=<local 블록의 h1 해시 줄 수> 三行(占位符依次为 local 的版本、random 的版本、local 块中 h1 哈希的行数)。
锁文件是 HCL,每个 provider 会生成一个 provider 块。块中包含 version,必要时还有 constraints,以及 hashes 列表。现在还不需要执行 apply。
一开始就写上约束,契约中就会同时留下允许范围
重新创建 /root/tfb-lock/pinned/main.tf,这次从一开始就在 local 中写上 version = "2.9.0",random 不带约束。用 local_file.fixed 声明一个 fixed.txt,然后执行 init。接着从锁文件中读取各 provider 的 constraints 值,在 /root/tfb-lock/constraints.txt 中写入 local=<값>(占位符为该值)和 random=none 两行。
只有在配置中写了版本约束时,constraints 这一行才会出现在锁文件中。没写约束的 provider,其块中根本没有这一行。选中了什么(version)和允许了什么(constraints),是不同的信息。
找到锁文件所指向的实物在哪里
在 /root/tfb-lock/app/.terraform/providers/ 之下找出实际安装的 provider 包的位置,在 /root/tfb-lock/installed.txt 中写入 local=<버전>/<플랫폼>(占位符依次为版本、平台)和 random=<버전>/<플랫폼>(占位符依次为版本、平台)两行。
安装路径按注册表地址、命名空间、名称、版本、平台的顺序逐级变深。这个目录由 init 重新生成,所以不提交;而锁文件必须由人评审,所以要提交。
没有契约,连计划都生成不了
创建 /root/tfb-lock/fresh/,只复制 /root/tfb-lock/app/main.tf(不要带来锁文件和 .terraform/)。不执行 init,直接运行 tofu plan,把错误输出保存到 /root/tfb-lock/no-init.txt。在 fresh 中不要执行 init。
这是人们在刚拿到的仓库中最先遇到的错误。想一想消息在要求做什么,以及为什么在计划阶段就把它拦住。没有 provider,就无法生成计划。
固定一个镜像源中没有的版本,init 会拒绝
在 /root/tfb-lock/badpin/main.tf 中只使用 local,用 version = "2.5.0" 固定版本,并放置一个 local_file.pinned。运行 init,把失败输出保存到 /root/tfb-lock/pin-fail.txt,然后把约束改为 2.9.0,使 init 成功。
这个 Pod 的 provider 只来自离线镜像源。如果要求镜像源中没有的版本,工具会说没有可选的。在实际工作中,只使用公司内部镜像源的组织也会遇到同样的消息。
后来才加上的约束,不会自动反映到契约中
到这时才给 /root/tfb-lock/app/main.tf 的 local 加上 version = "2.9.0",再次执行 init。确认锁文件的 local 块中是否生成了 constraints 这一行,并在 /root/tfb-lock/stale-lock.txt 的第一行写入 after_init=<그 값, 없으면 none>(占位符为该值,没有则为 none)。然后删除 .terraform.lock.hcl,不用 init,而是用 tofu providers lock -fs-mirror=/opt/tofu-mirror -platform=linux_amd64 重新生成,把它的输出保存到 /root/tfb-lock/relock.txt,并在 stale-lock.txt 的第二行写入 after_relock=<지금 값>(占位符为当前的值)。
如果已经选中的版本仍然符合约束,init 就不会重新选择,因此也不会重新写锁文件。要把允许范围反映到契约中,就必须让该条目重新创建。providers lock 不安装包,只计算并写入校验和。
改动哈希,安装就会被拦住
把 /root/tfb-lock/app/.terraform.lock.hcl 中 local 的一行哈希改成任意值,删除 .terraform/ 之后执行 init。把校验和错误的输出保存到 /root/tfb-lock/tamper.txt。然后删除锁文件,重新执行 init,恢复为正确的哈希。
锁文件中的哈希,是用来确认包与当时的那个是否相同的机制。被改动过的哈希与任何包都不匹配,所以安装会停止。保留错误的哈希,只加上 -upgrade,是解决不了的。
编写找出没有约束的 provider 的检查
创建 /root/tfb-lock/audit.sh。读取作为第一个参数传入的目录中的 .terraform.lock.hcl,每行输出一个没有 constraints 行的 provider 块的名称,只要有一个就以退出码 1 结束,没有则以 0 结束。把对 /root/tfb-lock/app 运行的结果保存到 /root/tfb-lock/audit-app.txt,把对 /root/tfb-lock/badpin 运行的结果保存到 /root/tfb-lock/audit-pin.txt,并在每个文件的最后一行追加 exit=<종료 코드>(占位符为退出码)。
锁文件逐行读取即可。以 provider 开头的行是新块的开始,只需要记住其后是否出现 constraints 这一行。必须在 awk 的 END 块中也判定最后一个块,才不会漏掉一个。