TT Lab
开始
学习 学习路径 课程

Terraform/OpenTofu 基础

类型不是文档,是关卡

在 TT Lab 中继续学习

一句话总结

变量的类型会在边界上拦住错误的值,会悄悄地转换能够转换的值,会不声不响地丢弃声明中没有的东西。必须把这三者区分清楚。

为什么必须写类型

不写类型,配置也能运行。所以着急的时候很容易漏掉,漏掉了,当天也不会有什么事。问题出在错误的值传进来的那一天。

没有类型的话,这个值会直接穿过变量边界。穿过去的值经过 locals、经过函数,一直流到资源参数,最终在某个地方炸掉。那时的消息不是“你给的值是错的”,而是“这个函数调用失败了”。必须从配置文件顶部开始往回追踪这个值,才能找到原因。

如果写了类型,同样的错误就会这样被拦住。

Error: Invalid value for input variable
  on v.tfvars line 1:
   1: tight = 7
number required.

会给出行号,会给出期望的是什么。修复所花的时间完全不同。

工作原理

类型分为几类:基本类型(string·number·bool)、集合类型(list·set·map)、结构类型(object·tuple),以及什么都能接收的 any。

拦住。遇到无法转换的值,当场拒绝。在数字的位置给了列表,元组的个数不对,或者对象缺少必需的属性,连计划都生成不了。

转换。能转换的就转换。在 tfvars 中写 replicas = "3",number 变量里得到的也是 3,debug = "true" 同样会变成真。更悄无声息的是集合的内部。

variable "tags" { type = map(string) }
# tags = { team = "core", rev = 3, tls = true }
# 결과   { "team" = "core", "rev" = "3", "tls" = "true" }

数字 3 变成了字符串“3”,却没有任何人告知。如果以后要用这个值做算术或相等比较,到那时就会出怪事。

丢弃。结构类型还有另一种特性。对象只接收所声明的属性,声明中没有的属性不会报错,而是被丢弃。属性名称写错的话,这个值就会消失,看上去就像应用了默认值。这是类型检查无法发现的典型漏洞。

列表和集合的区别也值得记住。即使给出同样的 ["b", "a", "b"],列表会按顺序保存三个,而集合会丢弃重复,按自己的规则排序,只保存两个。长度不同,所以统计个数的代码会受到影响。

可选属性可以让变量接收更简短的输入。

variable "service" {
  type = object({
    name = string
    port = optional(number, 8080)
    tls  = optional(bool, false)
  })
}

这样设置后,使用方只需给出 name,其余的由默认值补齐。如果不给默认值,这个属性就会变成 null,下游还得再确认,所以尽量把默认值也一起写上。

“不接受 null”的声明有两种表现。有默认值时,传入的 null 会被默认值替换;没有默认值时,则是错误。这是一种把“没有值时会变成什么”钉死在一处的机制。

最后,类型检查与值的检查是两回事。类型看形状,值的范围由 validation 块来看。端口是不是数字,类型看得出来,但这个数字是否在 1024 以上,类型就不知道了。

在现场相遇的样子

最常见的,是把模块输入开放成 any。本意是让使用方方便,结果真正用错的人,看到的却是模块内部的错误。模块边界上的类型既是文档也是契约,所以写得窄一些,对所有人都更好。

第二种是不知道映射内部发生的转换就去使用。把数字放进标签映射,之后用这个值做比较,结果永远是假。如果是要当数字用的值,就把映射的元素类型声明为数字,或者在使用的地方经过转换函数。

第三种是对象的拼写错误。属性名写错了一个,应用却成功了,只有那个值按默认值运行。评审时也不容易看出来。要减少这类问题,养成把实际传入的值通过输出导出来确认一遍的习惯,会有帮助。

下一项实验要做什么

走完八个步骤。确认基本类型中什么会被拦住、什么会被转换;通过输出查看集合内部悄悄发生的转换;比较同样的输入分别用列表、集合、元组接收时有何不同。接着分别确认对象的缺失与多余;用可选属性和默认值接收简短的输入;查看“不接受 null”声明的两种表现。最后两步,把不写类型和窄类型时错误出在哪里并排比较,并用嵌套类型钉死输入规格,确认评分器传入的错误输入会被拒绝。