4. Rego语言入门(二):引用、点操作符、方括号操作符、变量赋值、规则定义
上一节我们搭建了 Rego 的认知基础,理解了策略即代码的核心思想。本节将深入 Rego 的语法骨架,掌握如何访问数据、绑定变量以及定义可复用的策略规则。这些是编写任何非 trivial OPA 策略的必备技能。
4.1 引用(References):定位数据的路径
在 Rego 中,引用是访问嵌套文档(如 input、data)内部值的表达式。你可以把它想象成文件系统中的路径,或者 JSON 的键路径。
引用由一系列操作符连接而成,最常用的是点操作符(.)和方括号操作符([])。
4.1.1 点操作符(Dot Operator)
点操作符用于访问对象(Object)中的键。当键是合法的 Rego 标识符(字母、数字、下划线,且不以数字开头)时,点操作符是最简洁的写法。
# 假设 input 为:{"user": "alice", "action": "read", "resource": {"type": "file", "path": "/etc/config"}}
# 使用点操作符访问
user_name := input.user # 结果: "alice"
action_type := input.action # 结果: "read"
resource_type := input.resource.type # 结果: "file"
-、空格)或为数字时,点操作符会失效,必须使用方括号操作符。
4.1.2 方括号操作符(Bracket Operator)
方括号操作符更通用,可以访问对象的任意键(包括字符串键),也可以访问数组(Array)或集合(Set)中的元素(通过索引)。
# 假设 input 为:{"my-key": "secret", "items": ["a", "b", "c"], "config": {"port": 8080}}
# 访问包含特殊字符的键
secret_val := input["my-key"] # 结果: "secret"
# 访问数组元素(索引从0开始)
first_item := input.items[0] # 结果: "a"
# 访问嵌套对象
port_val := input["config"]["port"] # 结果: 8080
# 点操作符与方括号操作符可以混用
mixed := input.config["port"] # 结果: 8080
4.1.3 引用操作符对比
| 操作符 | 适用场景 | 示例 | 限制 |
|---|---|---|---|
.(点) |
对象键为合法标识符 | input.user.name |
不能用于数字、特殊字符键 |
[](方括号) |
任意对象键、数组/集合索引 | input["my-key"]、arr[0] |
语法稍显冗长 |
4.2 变量赋值(Variable Assignment)
Rego 中的变量赋值使用 := 操作符。与命令式语言不同,Rego 的变量是不可变(Immutable)的:一旦赋值,在同一作用域内不能重新赋值。
= 用于比较(统一),:= 用于赋值。混淆两者是初学者最常见的错误。
# 正确:变量赋值
x := 10
y := "hello"
z := [1, 2, 3]
# 错误:尝试重新赋值(编译错误)
x := 20 # 错误:x 已经被赋值
# 正确:使用 = 进行比较(统一)
x == 10 # 返回 true
# 正确:在规则体中使用 = 进行模式匹配
# 这会在后续规则定义中详细讲解
变量赋值可以在规则体(Rule Body)中的任何位置出现,但必须在使用该变量之前完成赋值。
# 正确:先赋值,后使用
user := input.user
admin := user == "admin"
# 错误:先使用,后赋值(编译错误)
# admin := user == "admin" # 如果 user 尚未赋值,这里会报错
# user := input.user
4.3 规则定义(Rule Definitions)
规则是 Rego 策略的核心构建块。一个规则定义了一个决策,它由头部(Head)和体(Body)组成。Rego 支持两种主要规则形式:完整规则(Complete Rules)和部分规则(Partial Rules)。
4.3.1 完整规则(Complete Rules)
完整规则为规则名生成一个单一值。如果规则体中的所有表达式都为真,则规则为真;否则为假(或未定义)。
# 语法:rule_name := value { body }
# 如果 body 为真,则 rule_name 的值为 value
# 示例:判断用户是否为管理员
is_admin := true {
input.user.role == "admin"
}
# 示例:计算允许的最大并发数
max_concurrent := 100 {
input.environment == "production"
}
max_concurrent := 50 {
input.environment != "production"
}
对于完整规则,如果存在多个同名规则定义(如上例中的 max_concurrent),Rego 会尝试所有规则体,并返回第一个成功(体为真)的规则的值。如果没有任何规则体为真,则规则未定义。
4.3.2 部分规则(Partial Rules)
部分规则为规则名生成一组值(通常是一个集合或对象)。它通过迭代数据集合来生成结果,类似于编程语言中的 map 或 filter 操作。
# 语法:rule_name[key] := value { body }
# 对于 body 为真的每个 key,生成一个键值对
# 示例:获取所有具有 "read" 权限的用户
readers[user] := true {
some user
data.users[user].permissions[_] == "read"
}
# 示例:获取所有非管理员用户的名称
non_admins[name] := true {
some name
data.users[name]
data.users[name].role != "admin"
}
部分规则常用于构建白名单或黑名单,或者从复杂的数据结构中提取特定子集。
4.3.3 完整规则 vs 部分规则
| 特性 | 完整规则 | 部分规则 |
|---|---|---|
| 结果类型 | 单一值(布尔、数字、字符串等) | 集合或对象(多值) |
| 头部语法 | rule_name := value |
rule_name[key] := value |
| 典型用途 | 全局决策(允许/拒绝)、计算单一结果 | 数据过滤、权限映射、白名单构建 |
| 迭代方式 | 不迭代,直接计算 | 使用 some 关键字迭代集合 |
| 未定义情况 | 所有规则体为假时未定义 | 没有匹配项时返回空集合/空对象 |
4.4 综合示例:引用、赋值与规则
下面是一个综合示例,展示了本节所有概念如何协同工作,构建一个简单的 IaC 资源命名策略。
# 策略:确保所有 AWS S3 存储桶名称符合命名规范
# 规则:名称必须以 "prod-" 或 "dev-" 开头,且只包含小写字母和连字符
# 引用 input 中的资源列表
resources := input.resources
# 完整规则:判断单个资源名称是否合规
valid_name(name) := true {
startswith(name, "prod-")
regex.match("^[a-z-]+$", name)
}
valid_name(name) := true {
startswith(name, "dev-")
regex.match("^[a-z-]+$", name)
}
# 部分规则:收集所有不合规的资源
violations[resource_id] := true {
some resource_id
resource := resources[resource_id]
resource.type == "aws_s3_bucket"
not valid_name(resource.name)
}
# 完整规则:最终决策
deny := true {
count(violations) > 0
}
- 引用是访问数据的路径,点操作符和方括号操作符各有适用场景。
- 变量赋值使用
:=,变量不可变,=用于比较。 - 完整规则返回单一值,适合做全局决策。
- 部分规则返回多值集合,适合做数据过滤和映射。
4.5 实战技巧与常见陷阱
- 引用未定义键:访问不存在的对象键不会报错,而是返回
undefined。这可能导致规则体意外为假。建议使用object.get()或显式检查。 - 变量作用域:Rego 变量是词法作用域的。在规则体内部赋值的变量,不能在规则体外部访问。
- 规则优先级:对于同名完整规则,Rego 按定义顺序尝试。一旦某个规则体为真,就返回其值,不再尝试后续规则。
- 部分规则中的
some关键字:some用于声明迭代变量。如果不使用some,Rego 无法自动遍历集合。
trace() 函数或 print() 语句(OPA v0.46+)输出中间变量值,帮助理解规则执行过程。