函数详解
Terraform 语言内置了丰富的函数,可以在表达式中调用这些函数来转换和组合值。本章详细介绍所有内置函数的用法。
函数基础
函数调用语法
函数调用由函数名和括号内的参数组成:
# 基本语法
function_name(argument1, argument2, ...)
# 示例
max(5, 12, 9) # 返回 12
在 Terraform Console 中测试
可以使用 terraform console 命令交互式测试函数:
$ terraform console
> max(5, 12, 9)
12
> upper("hello")
"HELLO"
> length(["a", "b", "c"])
3
函数特点
Terraform 不支持用户自定义函数,只能使用内置函数。内置函数涵盖以下类别:
- 数值函数
- 字符串函数
- 集合函数
- 编码函数
- 文件系统函数
- 日期时间函数
- 哈希和加密函数
- IP 网络函数
- 类型转换函数
数值函数
abs
返回绝对值。
abs(12) # 12
abs(-12) # 12
使用场景:需要确保数值为正数时,或计算差值的绝对值。
ceil
向上取整。
ceil(1.2) # 2
ceil(1.0) # 1
ceil(-1.2) # -1
使用场景:计算需要的资源数量时,确保向上取整。
# 根据存储大小计算需要的磁盘数量(每块 100GB)
variable "storage_gb" {
default = 250
}
locals {
disk_count = ceil(var.storage_gb / 100) # 3
}
floor
向下取整。
floor(1.9) # 1
floor(1.0) # 1
floor(-1.9) # -2
log
计算对数。
log(16, 2) # 4(以 2 为底 16 的对数)
log(100, 10) # 2(以 10 为底 100 的对数)
log(2.718, 2.718) # 约 1(自然对数)
max
返回最大值。
max(3, 2, 1) # 3
max(1, 2, 3, 4, 5) # 5
max(-1, -2, -3) # -1
# 可以接受列表
max([1, 5, 3]...) # 5(使用展开操作符)
min
返回最小值。
min(3, 2, 1) # 1
min(1, 2, 3, 4, 5) # 1
min(-1, -2, -3) # -3
# 可以接受列表
min([1, 5, 3]...) # 1
pow
计算幂。
pow(2, 3) # 8(2 的 3 次方)
pow(10, 2) # 100
pow(2, 0.5) # 约 1.414(平方根)
signum
返回符号(-1、0、1)。
signum(-12) # -1
signum(0) # 0
signum(12) # 1
使用场景:判断数值正负,用于条件表达式。
字符串函数
chomp
移除字符串末尾的换行符。
chomp("hello\n") # "hello"
chomp("hello\r\n") # "hello"
chomp("hello\n\n") # "hello"
chomp("hello") # "hello"
使用场景:处理从文件读取的文本,去除多余的换行。
format
格式化字符串,类似于 C 语言的 printf。
format("Hello, %s!", "World") # "Hello, World!"
format("Count: %d", 42) # "Count: 42"
format("Price: %.2f", 19.99) # "Price: 19.99"
format("%s-%03d", "server", 5) # "server-005"
# 常用格式符
# %s - 字符串
# %d - 整数
# %f - 浮点数
# %x - 十六进制
# %% - 百分号字面量
formatlist
对列表中的每个元素进行格式化。
formatlist("server-%s", ["web", "app", "db"])
# ["server-web", "server-app", "server-db"]
formatlist("%s-%03d", ["web", "app"], [1, 2])
# ["web-001", "app-002"]
使用场景:批量生成资源名称。
variable "server_names" {
default = ["web", "api", "worker"]
}
locals {
instance_names = formatlist("%s-${var.environment}", var.server_names)
# ["web-production", "api-production", "worker-production"]
}
indent
为字符串的每一行添加缩进。
indent(2, "hello\nworld")
# " hello\n world"
# 常用于格式化嵌入的 JSON
locals {
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = "s3:*"
Resource = "*"
}
]
})
}
# 在 YAML 中使用
yaml = <<-EOT
policy: |
${indent(2, local.policy)}
EOT
join
将列表元素连接成字符串。
join(", ", ["a", "b", "c"]) # "a, b, c"
join("-", ["2024", "01", "15"]) # "2024-01-15"
join("", ["a", "b", "c"]) # "abc"
使用场景:组合多个值。
# 组合 CIDR 块
variable "vpc_cidrs" {
default = ["10.0.0.0/16", "10.1.0.0/16"]
}
locals {
cidr_string = join(",", var.vpc_cidrs)
}
lower
转换为小写。
lower("HELLO") # "hello"
lower("Hello") # "hello"
lower("hello") # "hello"
upper
转换为大写。
upper("hello") # "HELLO"
upper("Hello") # "HELLO"
replace
替换字符串中的子串。
replace("hello world", "world", "terraform")
# "hello terraform"
replace("a-b-c", "-", "_")
# "a_b_c"
# 使用正则表达式
replace("hello 123 world", "/[0-9]+/", "XXX")
# "hello XXX world"
split
将字符串分割成列表。
split(", ", "a, b, c") # ["a", "b", "c"]
split("/", "a/b/c") # ["a", "b", "c"]
split("", "hello") # ["h", "e", "l", "l", "o"]
使用场景:解析分隔符分隔的数据。
# 从 ARN 提取资源 ID
variable "arn" {
default = "arn:aws:s3:::my-bucket"
}
locals {
arn_parts = split(":", var.arn)
resource = element(local.arn_parts, length(local.arn_parts) - 1)
}
strrev
反转字符串。
strrev("hello") # "olleh"
substr
提取子字符串。
substr("hello world", 0, 5) # "hello"
substr("hello world", 6, 5) # "world"
substr("hello", 0, 100) # "hello"(超出长度不会报错)
substr("hello", -5, 3) # "hel"(负数从末尾开始)
title
将每个单词的首字母大写。
title("hello world") # "Hello World"
title("HELLO WORLD") # "Hello World"
trim
移除字符串首尾的指定字符。
trim(" hello ", " ") # "hello"
trim("!!!hello!!!", "!") # "hello"
trim("abc helloba", "ab ") # "hello"
trimprefix
移除字符串前缀。
trimprefix("hello world", "hello ") # "world"
trimprefix("hello world", "hi") # "hello world"(无变化)
trimsuffix
移除字符串后缀。
trimsuffix("hello.txt", ".txt") # "hello"
trimsuffix("hello.txt", ".doc") # "hello.txt"(无变化)
trimspace
移除字符串首尾的空白字符。
trimspace(" hello ") # "hello"
trimspace("\n\thello\n") # "hello"
集合函数
alltrue
检查列表中所有元素是否都为 true。
alltrue([true, true, true]) # true
alltrue([true, false, true]) # false
alltrue([]) # true(空列表返回 true)
使用场景:验证多个条件。
variable "conditions" {
type = list(bool)
}
locals {
all_valid = alltrue([
var.environment != "",
var.region != "",
length(var.subnets) > 0
])
}
anytrue
检查列表中是否有任一元素为 true。
anytrue([false, false, true]) # true
anytrue([false, false, false]) # false
anytrue([]) # false(空列表返回 false)
chunklist
将列表分割成固定大小的块。
chunklist(["a", "b", "c", "d", "e"], 2)
# [["a", "b"], ["c", "d"], ["e"]]
chunklist(["a", "b", "c", "d"], 2)
# [["a", "b"], ["c", "d"]]
coalesce
返回第一个非空值。
coalesce("a", "b", "c") # "a"
coalesce("", "b", "c") # "b"
coalesce(null, "default") # "default"
coalesce(null, null, "last") # "last"
使用场景:提供默认值。
variable "instance_type" {
default = null
}
locals {
# 如果未指定实例类型,使用默认值
effective_instance_type = coalesce(var.instance_type, "t2.micro")
}
coalescelist
返回第一个非空列表。
coalescelist(["a", "b"], ["c", "d"]) # ["a", "b"]
coalescelist([], ["c", "d"]) # ["c", "d"]
coalescelist([], [], ["e"]) # ["e"]
compact
移除列表中的空字符串。
compact(["a", "", "b", "", "c"]) # ["a", "b", "c"]
compact(["", "", ""]) # []
concat
连接多个列表。
concat(["a", "b"], ["c", "d"]) # ["a", "b", "c", "d"]
concat(["a"], ["b"], ["c"]) # ["a", "b", "c"]
concat([], ["a", "b"]) # ["a", "b"]
使用场景:合并多个列表。
locals {
base_tags = {
Environment = "production"
}
extra_tags = {
Owner = "devops"
}
all_tags = merge(local.base_tags, local.extra_tags)
}
contains
检查列表是否包含某个元素。
contains(["a", "b", "c"], "a") # true
contains(["a", "b", "c"], "d") # false
contains([1, 2, 3], 2) # true
使用场景:条件验证。
variable "environment" {
type = string
}
locals {
is_production = contains(["prod", "production"], var.environment)
}
distinct
移除列表中的重复元素。
distinct(["a", "b", "a", "c", "b"])
# ["a", "b", "c"]
distinct([1, 2, 1, 3, 2])
# [1, 2, 3]
element
按索引获取列表元素(支持负索引和循环)。
element(["a", "b", "c"], 0) # "a"
element(["a", "b", "c"], 2) # "c"
element(["a", "b", "c"], -1) # "c"
element(["a", "b", "c"], 3) # "a"(超出长度会循环)
element(["a", "b", "c"], 4) # "b"
flatten
将嵌套列表展平。
flatten([["a", "b"], ["c", "d"]])
# ["a", "b", "c", "d"]
flatten([["a", ["b", "c"]], "d"])
# ["a", "b", "c", "d"]
使用场景:合并多层嵌套的配置。
variable "subnet_groups" {
default = [
["10.0.1.0/24", "10.0.2.0/24"],
["10.0.3.0/24", "10.0.4.0/24"]
]
}
locals {
all_subnets = flatten(var.subnet_groups)
# ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24", "10.0.4.0/24"]
}
index
查找元素在列表中的位置。
index(["a", "b", "c"], "b") # 1
index(["a", "b", "c"], "a") # 0
keys
获取映射的所有键。
keys({a = 1, b = 2, c = 3})
# ["a", "b", "c"]
keys({name = "web", env = "prod"})
# ["env", "name"](按字母顺序排序)
length
获取列表、映射或字符串的长度。
length(["a", "b", "c"]) # 3
length({a = 1, b = 2}) # 2
length("hello") # 5
length([]) # 0
list
已弃用:在 Terraform 0.12+ 中,直接使用 ["a", "b", "c"] 语法。
lookup
从映射中获取值,支持默认值。
lookup({a = "apple", b = "banana"}, "a", "unknown")
# "apple"
lookup({a = "apple", b = "banana"}, "c", "unknown")
# "unknown"(键不存在时返回默认值)
lookup({a = "apple"}, "a")
# "apple"(键不存在且无默认值会报错)
使用场景:安全地获取可能不存在的键值。
variable "instance_types" {
type = map(string)
default = {
dev = "t2.micro"
prod = "t2.large"
}
}
locals {
# 如果环境不在映射中,使用默认值
instance_type = lookup(var.instance_types, var.environment, "t2.micro")
}
map
已弃用:在 Terraform 0.12+ 中,直接使用 {a = 1, b = 2} 语法。
matchkeys
根据一个列表的值筛选另一个列表。
# 语法:matchkeys(values, keys, search_keys)
matchkeys(["a", "b", "c"], [1, 2, 3], [1, 3])
# ["a", "c"](返回键为 1 和 3 对应的值)
# 实际应用:筛选特定可用区的子网
variable "subnets" {
default = [
{ id = "subnet-1", az = "us-east-1a" },
{ id = "subnet-2", az = "us-east-1b" },
{ id = "subnet-3", az = "us-east-1a" }
]
}
locals {
subnet_ids = [for s in var.subnets : s.id]
subnet_azs = [for s in var.subnets : s.az]
az_subnets = matchkeys(local.subnet_ids, local.subnet_azs, ["us-east-1a"])
}
merge
合并多个映射。
merge({a = 1}, {b = 2})
# {a = 1, b = 2}
merge({a = 1, b = 2}, {b = 3, c = 4})
# {a = 1, b = 3, c = 4}(后面的值会覆盖前面的)
merge({}, {a = 1})
# {a = 1}
使用场景:合并标签。
locals {
default_tags = {
ManagedBy = "Terraform"
Environment = var.environment
}
resource_tags = merge(local.default_tags, var.additional_tags)
}
one
从列表中获取单个元素(用于处理可能为空的列表)。
one(["single"]) # "single"
one([]) # null
one(["a", "b"]) # 报错(列表有多于一个元素)
# 用于条件创建
variable "instance_id" {
default = []
}
locals {
# 如果列表为空,返回 null;否则返回第一个元素
instance = one(var.instance_id)
}
range
生成数字序列。
range(5) # [0, 1, 2, 3, 4]
range(1, 5) # [1, 2, 3, 4]
range(0, 10, 2) # [0, 2, 4, 6, 8]
range(5, 0, -1) # [5, 4, 3, 2, 1]
使用场景:生成资源索引。
# 生成端口号
locals {
ports = range(8080, 8085)
# [8080, 8081, 8082, 8083, 8084]
}
reverse
反转列表。
reverse(["a", "b", "c"]) # ["c", "b", "a"]
reverse([1, 2, 3]) # [3, 2, 1]
setintersection
集合交集。
setintersection(["a", "b", "c"], ["b", "c", "d"])
# ["b", "c"]
setintersection(["a", "b"], ["b", "c"], ["b", "d"])
# ["b"]
setproduct
集合笛卡尔积。
setproduct(["a", "b"], ["1", "2"])
# [["a", "1"], ["a", "2"], ["b", "1"], ["b", "2"]]
# 用于生成组合
locals {
environments = ["dev", "prod"]
regions = ["us-east-1", "us-west-2"]
# 生成所有环境和区域的组合
combinations = setproduct(local.environments, local.regions)
}
setsubtract
集合差集。
setsubtract(["a", "b", "c"], ["b", "c", "d"])
# ["a"]
setsubtract(["a", "b"], ["b", "c"])
# ["a"]
setunion
集合并集。
setunion(["a", "b"], ["b", "c", "d"])
# ["a", "b", "c", "d"]
setunion(["a"], ["b"], ["c"])
# ["a", "b", "c"]
slice
提取列表子集。
slice(["a", "b", "c", "d", "e"], 1, 4)
# ["b", "c", "d"](从索引 1 到 4,不包含 4)
slice(["a", "b", "c"], 0, 2)
# ["a", "b"]
sort
排序列表。
sort(["c", "a", "b"]) # ["a", "b", "c"]
sort(["10", "2", "1"]) # ["1", "10", "2"](按字符串排序)
sort([3, 1, 2]) # [1, 2, 3](数字列表)
sum
求和。
sum([1, 2, 3, 4, 5]) # 15
sum([10, 20, 30]) # 60
sum([]) # 0
transpose
转置映射的键和值。
transpose({
a = ["1", "2"]
b = ["2", "3"]
})
# {
# "1" = ["a"]
# "2" = ["a", "b"]
# "3" = ["b"]
# }
values
获取映射的所有值。
values({a = 1, b = 2, c = 3})
# [1, 2, 3]
values({name = "web", env = "prod"})
# ["prod", "web"](按键排序)
zipmap
从键列表和值列表创建映射。
zipmap(["a", "b", "c"], [1, 2, 3])
# {a = 1, b = 2, c = 3}
zipmap(["name", "env"], ["web", "prod"])
# {name = "web", env = "prod"}
使用场景:从列表构建映射。
# 从实例列表创建 ID 到名称的映射
variable "instances" {
default = [
{ id = "i-001", name = "web-1" },
{ id = "i-002", name = "web-2" }
]
}
locals {
ids = [for i in var.instances : i.id]
names = [for i in var.instances : i.name]
id_to_name = zipmap(local.ids, local.names)
# { "i-001" = "web-1", "i-002" = "web-2" }
}
编码函数
base64encode / base64decode
Base64 编码和解码。
base64encode("hello") # "aGVsbG8="
base64decode("aGVsbG8=") # "hello"
# 常用场景:传递用户数据
variable "script_content" {
default = "#!/bin/bash\necho hello"
}
locals {
user_data = base64encode(var.script_content)
}
base64gzip
Base64 编码并 gzip 压缩。
base64gzip("hello world")
# "H4sIAAAAAAAA/3PInc/PyU9SUSotTi7KLCjJz0vNBwA55QsIBwAAAA=="
textencodebase64 / textdecodebase64
指定字符集的 Base64 编码。
textencodebase64("hello", "UTF-8") # "aGVsbG8="
textdecodebase64("aGVsbG8=", "UTF-8") # "hello"
csvdecode
解析 CSV 格式数据。
csvdecode("name,age\nAlice,30\nBob,25")
# [
# {name = "Alice", age = "30"},
# {name = "Bob", age = "25"}
# ]
# 实际应用:从 CSV 文件读取配置
variable "users_csv" {
default = <<EOF
name,email,role
alice,[email protected],admin
bob,[email protected],user
EOF
}
locals {
users = csvdecode(var.users_csv)
}
jsonencode / jsondecode
JSON 编码和解码。
jsonencode({name = "web", count = 3})
# {"count":3,"name":"web"}
jsondecode("{\"name\":\"web\",\"count\":3}")
# {name = "web", count = 3}
# 常用场景:创建 IAM 策略
locals {
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = ["s3:GetObject"]
Resource = "arn:aws:s3:::my-bucket/*"
}
]
})
}
yamlencode / yamldecode
YAML 编码和解码。
yamlencode({name = "web", ports = [80, 443]})
# "name: web\nports:\n- 80\n- 443\n"
yamldecode("name: web\nports:\n- 80\n- 443\n")
# {name = "web", ports = [80, 443]}
# 常用场景:生成 Kubernetes 配置
locals {
config = yamlencode({
apiVersion = "v1"
kind = "ConfigMap"
metadata = {
name = "app-config"
}
data = {
DATABASE_URL = "postgresql://localhost:5432/mydb"
}
})
}
urlencode / urldecode
URL 编码和解码。
urlencode("hello world") # "hello%20world"
urlencode("a=b&c=d") # "a%3Db%26c%3Dd"
urldecode("hello%20world") # "hello world"
文件系统函数
file
读取文件内容。
file("script.sh")
# 返回 script.sh 文件的完整内容
file("${path.module}/config.yaml")
# 读取模块目录中的配置文件
fileexists
检查文件是否存在。
fileexists("script.sh") # true 或 false
fileexists("${path.module}/config.yaml")
使用场景:条件性读取文件。
locals {
config = fileexists("custom_config.yaml") ? file("custom_config.yaml") : file("default_config.yaml")
}
fileset
获取匹配模式的文件列表。
fileset("${path.module}/configs", "*.yaml")
# ["config1.yaml", "config2.yaml"]
fileset("${path.module}", "**/*.tf")
# 递归查找所有 .tf 文件
# 为每个文件创建资源
locals {
config_files = fileset("${path.module}/configs", "*.json")
}
resource "aws_s3_object" "config" {
for_each = toset(local.config_files)
bucket = aws_s3_bucket.configs.id
key = each.value
source = "${path.module}/configs/${each.value}"
}
filebase64
读取文件并 Base64 编码。
filebase64("script.sh")
# 返回文件的 Base64 编码内容
# 常用场景:上传二进制文件
resource "aws_lambda_function" "example" {
filename = "function.zip"
source_code_hash = filebase64sha256("function.zip")
}
filebase64sha256 / filebase64sha512
计算文件的 Base64 编码哈希值。
filebase64sha256("function.zip")
# "oP6YhY9Ww..."
filemd5 / filesha1 / filesha256 / filesha512
计算文件的哈希值。
filemd5("file.txt") # "d41d8cd98f00b204e9800998ecf8427e"
filesha256("file.txt") # "e3b0c44298fc1c149afbf4c8996fb924..."
templatefile
渲染模板文件。
# template.tftpl
# My name is ${name}, I'm ${age} years old.
templatefile("template.tftpl", {name = "Alice", age = 30})
# "My name is Alice, I'm 30 years old."
# 常用场景:渲染配置文件
# config.tftpl
# server {
# listen ${port};
# server_name ${domain};
# }
locals {
nginx_config = templatefile("${path.module}/nginx.conf.tftpl", {
port = 80
domain = "example.com"
})
}
path 模块
路径相关变量(不是函数)。
path.module # 当前模块的路径
path.root # 根模块的路径
path.cwd # 当前工作目录
# 使用示例
locals {
config_path = "${path.module}/configs"
script_path = "${path.root}/scripts"
}
日期时间函数
formatdate
格式化日期时间。
formatdate("YYYY-MM-DD", "2024-01-15T10:30:00Z")
# "2024-01-15"
formatdate("YYYY-MM-DD hh:mm:ss", "2024-01-15T10:30:00Z")
# "2024-01-15 10:30:00"
formatdate("DD MMM YYYY", "2024-01-15T10:30:00Z")
# "15 Jan 2024"
# 格式说明
# YYYY - 四位年份
# YY - 两位年份
# MMM - 月份缩写(Jan, Feb...)
# MM - 两位月份(01-12)
# DD - 两位日期(01-31)
# hh - 小时(01-12)
# HH - 小时(00-23)
# mm - 分钟
# ss - 秒
timeadd
时间加法。
timeadd("2024-01-15T10:00:00Z", "1h")
# "2024-01-15T11:00:00Z"
timeadd("2024-01-15T10:00:00Z", "2h30m")
# "2024-01-15T12:30:00Z"
timeadd("2024-01-15T10:00:00Z", "-1h")
# "2024-01-15T09:00:00Z"
# 支持的单位:ns, us, ms, s, m, h
timecmp
比较两个时间戳。
timecmp("2024-01-15T10:00:00Z", "2024-01-15T11:00:00Z")
# -1(第一个时间早于第二个)
timecmp("2024-01-15T11:00:00Z", "2024-01-15T10:00:00Z")
# 1(第一个时间晚于第二个)
timecmp("2024-01-15T10:00:00Z", "2024-01-15T10:00:00Z")
# 0(两个时间相等)
timestamp
获取当前时间戳。
timestamp()
# "2024-01-15T10:30:00Z"
# 常用场景:生成唯一名称或标记创建时间
locals {
timestamp = timestamp()
name = "backup-${formatdate("YYYYMMDD-hhmmss", local.timestamp)}"
}
注意:每次运行都会生成新的时间戳,可能导致资源每次都被更新。建议仅在需要的地方使用。
plantimestamp
获取计划时的时间戳(在 plan 阶段固定)。
plantimestamp()
# 返回 plan 执行时的时间戳
# 与 timestamp 的区别
# timestamp() - 每次 apply 都会重新计算
# plantimestamp() - 在 plan 时固定,apply 时使用相同值
哈希和加密函数
base64sha256 / base64sha512
计算 Base64 编码的哈希值。
base64sha256("hello")
# "LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ="
base64sha512("hello")
# "2dEx...
md5
计算 MD5 哈希值。
md5("hello") # "5d41402abc4b2a76b9719d911017c592"
# 注意:MD5 已不再安全,仅用于非安全场景(如文件校验)
sha1
计算 SHA1 哈希值。
sha1("hello") # "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d"
sha256 / sha512
计算 SHA 哈希值。
sha256("hello")
# "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
sha512("hello")
# "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72..."
bcrypt
生成 bcrypt 哈希(用于密码)。
bcrypt("password")
# "$2a$10$Ww7ZQwvVXjBqC1qXHvqMzuN6wX..."
# 指定成本因子(默认 10)
bcrypt("password", 12)
rsadecrypt
使用 RSA 私钥解密。
rsadecrypt(encrypted_value, private_key_pem)
# 返回解密后的明文
uuid
生成 UUID。
uuid() # "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
# 每次调用都会生成新的 UUID
uuidv5
基于命名空间生成确定性 UUID。
uuidv5("dns", "example.com")
# "c4b9b3d0-7b6d-5b8d-8b0a-9b9b9b9b9b9b"
# 相同的输入总是产生相同的输出
uuidv5("dns", "example.com") == uuidv5("dns", "example.com") # true
# 预定义的命名空间
# "dns" - DNS 名称空间
# "url" - URL 名称空间
# "oid" - ISO OID 名称空间
# "x500" - X.500 DN 名称空间
IP 网络函数
cidrhost
计算 CIDR 块中的主机 IP。
cidrhost("10.0.0.0/24", 5)
# "10.0.0.5"
cidrhost("192.168.1.0/24", 254)
# "192.168.1.254"
# 索引从 0 开始,0 是网络地址
cidrnetmask
将 CIDR 前缀转换为子网掩码。
cidrnetmask("10.0.0.0/24")
# "255.255.255.0"
cidrnetmask("10.0.0.0/16")
# "255.255.0.0"
cidrnetmask("10.0.0.0/8")
# "255.0.0.0"
cidrsubnet
计算子网 CIDR。
# 语法:cidrsubnet(prefix, newbits, netnum)
# prefix - 原始 CIDR
# newbits - 新增的位数
# netnum - 子网编号
cidrsubnet("10.0.0.0/16", 8, 0)
# "10.0.0.0/24"
cidrsubnet("10.0.0.0/16", 8, 1)
# "10.0.1.0/24"
cidrsubnet("10.0.0.0/16", 8, 2)
# "10.0.2.0/24"
# 实际应用:创建多个子网
variable "vpc_cidr" {
default = "10.0.0.0/16"
}
locals {
# 创建 4 个子网,每个增加 2 位
subnet_cidrs = [
cidrsubnet(var.vpc_cidr, 2, 0), # 10.0.0.0/18
cidrsubnet(var.vpc_cidr, 2, 1), # 10.0.64.0/18
cidrsubnet(var.vpc_cidr, 2, 2), # 10.0.128.0/18
cidrsubnet(var.vpc_cidr, 2, 3), # 10.0.192.0/18
]
}
cidrsubnets
一次计算多个子网。
# 语法:cidrsubnets(prefix, newbits...)
# 每个参数指定对应子网的新增位数
cidrsubnets("10.0.0.0/16", 8, 8, 8, 8)
# ["10.0.0.0/24", "10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
# 不同大小的子网
cidrsubnets("10.0.0.0/16", 4, 4, 8, 8)
# ["10.0.0.0/20", "10.0.16.0/20", "10.0.32.0/24", "10.0.33.0/24"]
# 实际应用
variable "vpc_cidr" {
default = "10.0.0.0/16"
}
locals {
# 创建不同大小的子网
subnets = cidrsubnets(var.vpc_cidr, 4, 4, 8, 8, 8, 8)
}
类型转换函数
can
检查表达式是否有效(不会报错)。
can(nonsensitive(var.sensitive_value)) # true 或 false
can(tostring(123)) # true
can(tostring([])) # false
# 常用场景:条件判断
variable "config" {
default = { port = 8080 }
}
locals {
# 检查配置是否包含 port
has_port = can(var.config.port)
}
issensitive
检查值是否被标记为敏感。
variable "secret" {
sensitive = true
default = "my-secret"
}
locals {
is_secret = issensitive(var.secret) # true
}
nonsensitive
移除敏感标记。
variable "secret" {
sensitive = true
default = "my-secret"
}
locals {
# 使用 nonsensitive 后可以在输出中显示
public_value = nonsensitive(var.secret)
}
# 注意:只应在确认安全的情况下使用
sensitive
将值标记为敏感。
variable "password" {
default = "default-password"
}
locals {
# 确保值被视为敏感
safe_password = sensitive(var.password)
}
tobool
转换为布尔值。
tobool(true) # true
tobool("true") # true
tobool(1) # true
tobool("yes") # 报错(不支持的值)
tolist
转换为列表。
tolist(["a", "b", "c"])
# ["a", "b", "c"]
tolist(set(["a", "b"]))
# ["a", "b"](集合转为列表,顺序不确定)
tomap
转换为映射。
tomap({a = 1, b = 2})
# {a = 1, b = 2}
tomap([["a", 1], ["b", 2]])
# {a = 1, b = 2}(从键值对列表转换)
tonumber
转换为数字。
tonumber("42") # 42
tonumber(42) # 42
tonumber("3.14") # 3.14
tonumber("hello") # 报错
toset
转换为集合。
toset(["a", "b", "c"])
# toset(["a", "b", "c"])
# 集合自动去重
toset(["a", "b", "a", "c"])
# toset(["a", "b", "c"])
# 常用于 for_each
resource "aws_instance" "web" {
for_each = toset(["web-1", "web-2", "web-3"])
tags = {
Name = each.value
}
}
tostring
转换为字符串。
tostring("hello") # "hello"
tostring(42) # "42"
tostring(true) # "true"
tostring([]) # 报错
try
尝试多个表达式,返回第一个成功的。
try(var.maybe_null, "default")
# 如果 var.maybe_null 不为 null,返回它;否则返回 "default"
try(var.config.port, var.config.default_port, 8080)
# 尝试获取 port,失败则尝试 default_port,最后使用 8080
# 常用场景:处理可选嵌套属性
variable "config" {
default = {}
}
locals {
# 尝试获取嵌套的值
port = try(var.config.server.port, 8080)
}
type
获取值的类型。
type("hello") # "string"
type(42) # "number"
type(true) # "bool"
type(["a", "b"]) # "list(string)"
type({a = 1}) # "map(number)"
条件与控制流
defaults
为对象类型设置默认值(Terraform 1.3+)。
variable "config" {
type = object({
name = string
port = optional(number, 80)
enabled = optional(bool, true)
})
default = {
name = "web"
}
}
# config.port 将是 80,config.enabled 将是 true
函数组合最佳实践
链式调用
将多个函数组合使用:
# 读取文件、处理并编码
locals {
config_content = file("${path.module}/config.json")
config_data = jsondecode(local.config_content)
config_hash = md5(local.config_content)
}
# 字符串处理链
locals {
clean_name = lower(trimspace(replace(var.name, " ", "-")))
}
在 for 表达式中使用函数
# 转换列表中所有元素
variable "names" {
default = ["Web Server", "App Server", "DB Server"]
}
locals {
# 转换为小写并用连字符连接
normalized_names = [for name in var.names : lower(replace(name, " ", "-"))]
# ["web-server", "app-server", "db-server"]
# 创建映射
name_map = {for name in var.names : lower(replace(name, " ", "-")) => name}
}
条件表达式与函数
variable "environment" {
default = "production"
}
locals {
# 根据环境选择不同的配置
instance_type = var.environment == "production" ? "t2.large" : "t2.micro"
# 使用函数进行条件判断
is_production = contains(["prod", "production"], lower(var.environment))
}
调试技巧
在 terraform console 中测试
$ terraform console
# 测试函数
> upper("hello")
"HELLO"
> join("-", ["a", "b", "c"])
"a-b-c"
> cidrsubnet("10.0.0.0/16", 8, 1)
"10.0.1.0/24"
使用 locals 调试
locals {
# 添加调试用的中间值
debug_input = var.some_value
debug_step1 = upper(local.debug_input)
debug_step2 = replace(local.debug_step1, "-", "_")
final_result = local.debug_step2
}
# 通过 terraform output 查看中间值
output "debug_info" {
value = {
input = local.debug_input
step1 = local.debug_step1
step2 = local.debug_step2
result = local.final_result
}
}
常见错误
1. 类型不匹配
# 错误:尝试对非字符串使用字符串函数
length(123) # 报错
# 正确:先转换为字符串
length(tostring(123)) # 3
2. 空值处理
# 错误:对 null 调用函数
upper(null) # 报错
# 正确:使用 coalesce 提供默认值
upper(coalesce(var.name, "default"))
3. 列表越界
# 危险:直接访问可能不存在的索引
var.items[5] # 如果列表长度小于 6 会报错
# 安全:使用 element 或条件判断
element(var.items, 5) # 循环访问
var.items[5] if length(var.items) > 5 else null
下一步
掌握了函数用法后,可以继续学习 Provisioners 配置器,了解如何在资源配置后执行额外操作。