没写类型,报错落在了不相干的行上
目标
逐一声明基本类型、集合类型和结构类型,确认什么会在变量边界被拦住,什么会被悄悄转换。还要查看可选属性与默认值、“不接受 null”的声明,以及不写类型时错误出在哪里。
为什么重要
类型不是文档,而是检查站。写了类型,错误的值会在变量边界连同 tfvars 的行号一起被拦住;不写,这个值就会一路流到深处,以莫名其妙的消息炸掉。同样的失误,修复所花的时间相差十倍。而且类型检查不只是拦截,还会转换。写成字符串的数字会变成数字,把数字放进指定了元素类型的映射,它会变成字符串。这种转换是不报错地发生的,所以如果是之后要做比较或算术的值,就必须事先知道。相反,结构类型会不声不响地丢弃声明中没有的属性——属性名写错了,看上去就像应用了默认值。什么会被拦住、什么会被转换、什么会消失,区分这三者,就是这个模块的全部。
步骤
- 在
/root/tfb-types/prim/main.tf中放置string变量name、number变量replicas、bool变量debug和三个输出。在ok.tfvars中写入name = "api"、replicas = "3"、debug = "true"并 apply,把tofu output -json保存到/root/tfb-types/prim.json。在bad.tfvars中给replicas放入列表,运行 plan,把错误输出保存到/root/tfb-types/prim-bad.txt。 - 在
/root/tfb-types/quiet/main.tf中放置map(string)变量tags和list(string)变量ids,以及两个输出。在v.tfvars中,给tags写入team = "core"、rev = 3、tls = true,给ids写入[1, 2, 10],apply 之后把tofu output -json保存到/root/tfb-types/quiet.json。 - 在
/root/tfb-types/coll/main.tf中放置list(string)变量as_list、set(string)变量as_set、tuple([string, number, bool])变量as_tuple,以及五个输出(三个值,加上列表和集合的长度)。在v.tfvars中,给前两个变量都给出同样的["b", "a", "b"],给元组给出["web", 3, true],apply 之后把tofu output -json保存到/root/tfb-types/coll.json。接着用只给出两个元组元素的bad.tfvars运行 plan,把错误保存到/root/tfb-types/tuple-bad.txt。 - 在
/root/tfb-types/obj/main.tf中放置object({ name = string, port = number })变量svc和一个输出。在ok.tfvars中,除了name·port之外,再写上声明中没有的owner = "team-a"并 apply,把tofu output -json保存到/root/tfb-types/obj.json。在bad.tfvars中去掉name后运行 plan,把错误保存到/root/tfb-types/obj-bad.txt。 - 在
/root/tfb-types/opt/main.tf中把svc变量声明为对象,只让name必填,并把port(默认 8080)、tls(默认 false)、tags(默认空映射)、aliases(默认空列表)声明为可选。在v.tfvars中只给出name并 apply,把tofu output -json保存到/root/tfb-types/opt.json。 - 在
/root/tfb-types/nul/main.tf中放置两个nullable = false的变量——带有默认值"a"的zone,以及没有默认值的must。也放置两个输出。在ok.tfvars中给出zone = null、must = "x"并 apply,把tofu output -json保存到/root/tfb-types/nul.json。在bad.tfvars中只给出must = null并运行 plan,把错误保存到/root/tfb-types/nul-bad.txt。 - 在
/root/tfb-types/late/中放置any类型的变量loose及导出其长度的输出,在/root/tfb-types/tight/中放置形态相同、但类型为list(string)的变量tight。两处都在v.tfvars中给出值7并运行 plan,把输出分别保存到/root/tfb-types/late.txt和/root/tfb-types/tight.txt。然后只提取两个错误的第一行,按late=<첫 오류 줄>和tight=<첫 오류 줄>(占位符均为第一条错误的那一行,只取Error:之后的文字)两行写入/root/tfb-types/blame.txt。 - 在
/root/tfb-types/schema/main.tf中把service变量设成与第 5 步相同的形态(必填name,可选port·tls·tags·aliases),并用它的值创建service.conf。五行分别是name=、port=、tls=、tags=(编码为 JSON)、aliases=(用逗号连接)。在v.tfvars中给出name = "api"、port = "9090"、tags = { team = "core", rev = 3 }、aliases = ["api-1", "api-2"]并 apply。评分器会在这份配置的副本中传入两种错误输入,确认它们会被拒绝。
参考
- 类型看“形状”,validation 块看“值的范围”。两者不能互相代替。
- 输出 JSON 中除了值还有类型,很适合用眼睛确认传入的究竟是什么。
- 通过命令行给值时,基本类型变量会把引号也当作值的一部分。为了避免混淆,本实验只使用 tfvars 文件。
- 常见错误:把第 3 步的 bad.tfvars 改对使其通过。那个文件必须保持是错的,才能确认错误。
- 输入变量 · 类型约束 · 类型与值 · Type Constraints (HashiCorp)
基本类型在变量边界被拦住
在 /root/tfb-types/prim/main.tf 中放置 string 变量 name、number 变量 replicas、bool 变量 debug 和三个输出。在 ok.tfvars 中写入 name = "api"、replicas = "3"、debug = "true" 并 apply,把 tofu output -json 保存到 /root/tfb-types/prim.json。在 bad.tfvars 中给 replicas 放入列表,运行 plan,把错误输出保存到 /root/tfb-types/prim-bad.txt。
即使值带有引号,只要能转换成符合类型的值,工具就会为你转换。输出 JSON 中不仅有值,还有类型,用眼睛确认传入的究竟是什么。
在集合内部,转换是悄悄发生的
在 /root/tfb-types/quiet/main.tf 中放置 map(string) 变量 tags 和 list(string) 变量 ids,以及两个输出。在 v.tfvars 中,给 tags 写入 team = "core"、rev = 3、tls = true,给 ids 写入 [1, 2, 10],apply 之后把 tofu output -json 保存到 /root/tfb-types/quiet.json。
指定了元素类型的集合,会把放入的值转换成那种类型再保存。数字和真假变成字符串也不会报错,所以如果是之后要做比较或算术的值,就必须了解这种转换。
同样的输入用列表、集合、元组接收,留下的结果不同
在 /root/tfb-types/coll/main.tf 中放置 list(string) 变量 as_list、set(string) 变量 as_set、tuple([string, number, bool]) 变量 as_tuple,以及五个输出(三个值,加上列表和集合的长度)。在 v.tfvars 中,给前两个变量都给出同样的 ["b", "a", "b"],给元组给出 ["web", 3, true],apply 之后把 tofu output -json 保存到 /root/tfb-types/coll.json。接着用只给出两个元组元素的 bad.tfvars 运行 plan,把错误保存到 /root/tfb-types/tuple-bad.txt。
列表会原样保留书写的顺序和重复项,集合则丢弃重复项,并按自己的规则排序。元组是每个位置的类型都已确定的固定长度,所以个数不同,就会当场被拦住。
对象缺少属性就拦住,多出属性就悄悄丢弃
在 /root/tfb-types/obj/main.tf 中放置 object({ name = string, port = number }) 变量 svc 和一个输出。在 ok.tfvars 中,除了 name·port 之外,再写上声明中没有的 owner = "team-a" 并 apply,把 tofu output -json 保存到 /root/tfb-types/obj.json。在 bad.tfvars 中去掉 name 后运行 plan,把错误保存到 /root/tfb-types/obj-bad.txt。
对象类型只接受所声明的属性,声明中没有的属性不是错误,而是被丢弃。所以拼错的属性名会悄无声息地消失,看上去就像应用了默认值。
用可选属性和默认值接收简短的输入
在 /root/tfb-types/opt/main.tf 中把 svc 变量声明为对象,只让 name 必填,并把 port(默认 8080)、tls(默认 false)、tags(默认空映射)、aliases(默认空列表)声明为可选。在 v.tfvars 中只给出 name 并 apply,把 tofu output -json 保存到 /root/tfb-types/opt.json。
可选属性用第二个参数接收默认值。如果不给默认值,这个属性就会变成 null,下游还得再确认,所以把默认值一起写上,使用方会更轻松。
“不接受 null”的声明有两种表现
在 /root/tfb-types/nul/main.tf 中放置两个 nullable = false 的变量——带有默认值 "a" 的 zone,以及没有默认值的 must。也放置两个输出。在 ok.tfvars 中给出 zone = null、must = "x" 并 apply,把 tofu output -json 保存到 /root/tfb-types/nul.json。在 bad.tfvars 中只给出 must = null 并运行 plan,把错误保存到 /root/tfb-types/nul-bad.txt。
当声明了不接受 null 时,如果有默认值,null 会被默认值替换;如果没有默认值,就是错误。这是把“没有值时会变成什么”钉死在一处的机制。
用 any 接收,错误会出在很深的地方
在 /root/tfb-types/late/ 中放置 any 类型的变量 loose 及导出其长度的输出,在 /root/tfb-types/tight/ 中放置形态相同、但类型为 list(string) 的变量 tight。两处都在 v.tfvars 中给出值 7 并运行 plan,把输出分别保存到 /root/tfb-types/late.txt 和 /root/tfb-types/tight.txt。然后只提取两个错误的第一行,按 late=<첫 오류 줄> 和 tight=<첫 오류 줄>(占位符均为第一条错误的那一行,只取 Error: 之后的文字)两行写入 /root/tfb-types/blame.txt。
如果不写类型,错误的值就会原样穿过变量边界。于是错误出在使用这个值的地方,消息说的也不是变量,而是函数。把类型收窄之后,同样的失误会连同 tfvars 的行号一起在前面被拦住。
用嵌套类型钉死输入规格
在 /root/tfb-types/schema/main.tf 中把 service 变量设成与第 5 步相同的形态(必填 name,可选 port·tls·tags·aliases),并用它的值创建 service.conf。五行分别是 name=、port=、tls=、tags=(编码为 JSON)、aliases=(用逗号连接)。在 v.tfvars 中给出 name = "api"、port = "9090"、tags = { team = "core", rev = 3 }、aliases = ["api-1", "api-2"] 并 apply。评分器会在这份配置的副本中传入两种错误输入,确认它们会被拒绝。
如果类型写得正确,错误的输入会连同 tfvars 的行号当场被拦住。写成字符串的端口变成数字,以及映射中的数字变成字符串,这两种转换会在同一个输入中同时发生,用眼睛确认结果。