跳到主要内容

函数详解

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 配置器,了解如何在资源配置后执行额外操作。