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)的:一旦赋值,在同一作用域内不能重新赋值。

常见错误:在 Rego 中,= 用于比较(统一),:= 用于赋值。混淆两者是初学者最常见的错误。
# 正确:变量赋值
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
}
关键要点:
  • 引用是访问数据的路径,点操作符和方括号操作符各有适用场景。
  • 变量赋值使用 :=,变量不可变,= 用于比较。
  • 完整规则返回单一值,适合做全局决策。
  • 部分规则返回多值集合,适合做数据过滤和映射。
Rego 规则执行流程:从输入到决策 Rego 规则执行流程 input 文档 data 文档 引用(. / []) 变量赋值 (:=) 规则定义 完整规则 部分规则 决策输出 完整规则返回单一值;部分规则返回集合/对象,用于数据过滤

4.5 实战技巧与常见陷阱

  • 引用未定义键:访问不存在的对象键不会报错,而是返回 undefined。这可能导致规则体意外为假。建议使用 object.get() 或显式检查。
  • 变量作用域:Rego 变量是词法作用域的。在规则体内部赋值的变量,不能在规则体外部访问。
  • 规则优先级:对于同名完整规则,Rego 按定义顺序尝试。一旦某个规则体为真,就返回其值,不再尝试后续规则。
  • 部分规则中的 some 关键字:some 用于声明迭代变量。如果不使用 some,Rego 无法自动遍历集合。
调试技巧:使用 trace() 函数或 print() 语句(OPA v0.46+)输出中间变量值,帮助理解规则执行过程。