OPA 文件模型
OPA將從外部載入的資料成為基本文件(base documents),有規則產生的值成為虛擬文件(virtual documents),此處"虛擬"的意思表示文件由策略進行了計算,且不是外部載入的。Rego中可以使用名為data
的全域性變數訪問這兩種資料。
非同步載入的基本文件可以通過data
全域性變數進行訪問。另一方面,如果軟體需要查詢OPA來獲取策略決策時,也可以將基礎文件同步推入或拉入OPA,此時需要通過input
全域性變數來引用同步推送的基本文件。同步載入的資料儲存在data
之外,防止命名衝突。
邏輯與
{
"servers": [
{"id": "app", "protocols": ["https", "ssh"], "ports": ["p1", "p2", "p3"]},
{"id": "db", "protocols": ["mysql"], "ports": ["p3"]},
{"id": "cache", "protocols": ["memcache"], "ports": ["p3"]},
{"id": "ci", "protocols": ["http"], "ports": ["p1", "p2"]},
{"id": "busybox", "protocols": ["telnet"], "ports": ["p1"]}
],
"networks": [
{"id": "net1", "public": false},
{"id": "net2", "public": false},
{"id": "net3", "public": true},
{"id": "net4", "public": true}
],
"ports": [
{"id": "p1", "network": "net1"},
{"id": "p2", "network": "net3"},
{"id": "p3", "network": "net2"}
]
}
OPA使用;
來表示邏輯AND
input.servers[0].id == "app"; input.servers[0].protocols[0] == "https"
$ true
也可以使用多行來忽略;
input.servers[0].id == "app"
input.servers[0].protocols[0] == "https"
$ rue
如果引用的內容不存在或匹配失敗,則返回的結果為undefined
s := input.servers[0]
s.id == "app1"
$undefined decision
s := input.servers[1110]
s.id == "app1"
$ undefined decision
變數
OPA的變數一旦賦值之後就是不可變的:
s := input.servers[0]
s := input.servers[1]
$ 1 error occurred: 2:1: rego_compile_error: var s assigned above
迭代
找出連線到public
網路的ports
的id
,下面使用some
關鍵字定義迴圈的變數,使用id
變數提取符合要求的ports
的id
,最後一行input.networks[j].public
也可以寫為input.networks[j].public==true
,同時注意條件之間是邏輯與的關係
some i, j
id := input.ports[i].id
input.ports[i].network == input.networks[j].id
input.networks[j].public
$ +---+------+---+
| i | id | j |
+---+------+---+
| 1 | "p2" | 2 |
+---+------+---+
可以使用下劃線_
(萬用字元)進行遍歷,使用下劃線來表示例項的單獨變數。如使用如下方式找出input.servers
中協議為http
的id
s := input.servers[_]
id := s.id
s.protocols[_] == "http"
$ true
規則(Rules)
使用規則可以重用判定邏輯,可以看作是一種函式實現。規則可以是"完整(complete)"或"部分(partial)"的。
完整規則
每個規則都包含一個head和一個body,如下head為any_public_networks = true
,body為net := input.networks[_]; net.public。如果忽略= <value>
部分(= <value>
用於使用右值賦予左邊的變數,如apps_by_hostname[hostname] = app,key為hostname,value為app),則預設為true
。
package example.rules
any_public_networks = true { # is true if...
net := input.networks[_] # some network exists and..
net.public # it is public.
}
可以為規則定義預設值,這樣在結果返回"undefined decision"時,會將any_public_networks
置為false
package example.rules
default any_public_networks = false
any_public_networks = true { # is true if...
net := input.no_exist_networks[_] # some network exists and..
net.public # it is public.
}
any_public_networks
$ false
可以使用如下方式進行訪問:
any_public_networks
$ true
也可以使用全域性變數data
進行訪問,訪問方式為data.<package-path>.<rule-name>
data.example.rules.any_public_networks
$ true
可以使用如下方式定義常量
package example.constants
pi := 3.14
部分規則
head為public_network[net.id]
,body為net := input.networks[_]; net.public
,可以看作是帶返回值的函式
package example.rules
public_network[net.id] { # net.id is in the public_network set if...
net := input.networks[_] # some network exists and...
net.public # it is public.
}
可以遍歷返回值
public_network[_]
$ +-------------------+
| public_network[_] |
+-------------------+
| "net3" |
| "net4" |
+-------------------+
邏輯或
完整規則的邏輯或
使用邏輯或時,要求多條規則的名稱相同。如下用於校驗servers是否暴露了telnet
或ssh
協議:
package example.logical_or
default shell_accessible = false
shell_accessible = true {
input.servers[_].protocols[_] == "telnet"
}
shell_accessible = true {
input.servers[_].protocols[_] == "ssh"
}
部分規則的邏輯或
如下規則用於找出協議為telnet
或ssh
的server的id
:
package example.logical_or
shell_accessible[server.id] {
server := input.servers[_]
server.protocols[_] == "telnet"
}
shell_accessible[server.id] {
server := input.servers[_]
server.protocols[_] == "ssh"
}
rego語法
標量
rego支援字串、數字、布林和null
greeting := "Hello"
max_height := 42
pi := 3.14159
allowed := true
location := null
字串
支援兩種型別的字串:雙引號包圍的字串和原始字串,後者一般用於正規表示式。
複合值
定義了數值集合
cube := {"width": 3, "height": 4, "depth":true}
cube.depth
$ true
物件
可以看作golang的map
ips_by_port := {
80: ["1.1.1.1", "1.1.1.2"],
443: ["2.2.2.1"],
}
ips_by_port[80]
$ [
"1.1.1.1",
"1.1.1.2"
]
陣列
陣列使用下標進行索引
s1 := [1, 2, 3]
s2 := [2, 1, 3]
s1[1]
$ 2
s1 == s2
$ false
集合
它是唯一值的無序集合。它沒有key,且無法使用下標進行索引。注意在解析為JSON格式時,集合體現為陣列格式。
s1 := {1, 2, 3}
s2 := {2, 1, 3}
s1 == s2
$ true
變數
變數位於規則的head和body,規則head中的變數可以認為是輸入或輸出,如果提供了確定的值,則認為是輸入,否則認為是輸出。例如:
sites := [
{"name": "prod"},
{"name": "smoke1"},
{"name": "dev"}
]
q[name] { name := sites[_].name }
如下x
並沒有繫結到某個值,則返回所有x
和q[x]
的值
q[x]
$ +----------+----------+
| x | q[x] |
+----------+----------+
| "dev" | "dev" |
| "prod" | "prod" |
| "smoke1" | "smoke1" |
+----------+----------+
如下"dev"是一個確定的值,作為輸入,用於判斷name
中是否存在該值
q["dev"]
$ "dev"
引用
有兩種方式訪問巢狀文件:點訪問方式和方括號訪問方式,如下:
sites[0].servers[1].hostname
sites[0]["servers"][1]["hostname"]
變數鍵
引用可以使用變數作為鍵,這種方式用於選擇所有元素的值
sites[i].servers[j].hostname
$ +---+---+------------------------------+
| i | j | sites[i].servers[j].hostname |
+---+---+------------------------------+
| 0 | 0 | "hydrogen" |
| 0 | 1 | "helium" |
| 0 | 2 | "lithium" |
| 1 | 0 | "beryllium" |
| 1 | 1 | "boron" |
| 1 | 2 | "carbon" |
| 2 | 0 | "nitrogen" |
| 2 | 1 | "oxygen" |
+---+---+------------------------------+
如果迭代時不需要用到變數,則可以使用下劃線_
sites[_].servers[_].hostname
$ +------------------------------+
| sites[_].servers[_].hostname |
+------------------------------+
| "hydrogen" |
| "helium" |
| "lithium" |
| "beryllium" |
| "boron" |
| "carbon" |
| "nitrogen" |
| "oxygen" |
+------------------------------+
複合鍵
s := {[1, 2], [1, 4], [2, 6]}
s[[1, 2]]
$ [
1,
2
]
s[[1, x]]
$ +---+-----------+
| x | s[[1, x]] |
+---+-----------+
| 2 | [1,2] |
| 4 | [1,4] |
+---+-----------+
多表示式
規則通常是多表示式的,包含到documents的引用。下面定義了一個陣列,每個陣列包含一個服務的應用名稱和主機名稱
apps_and_hostnames[[name, hostname]] {
some i, j, k
name := apps[i].name
server := apps[i].servers[_]
sites[j].servers[k].name == server
hostname := sites[j].servers[k].hostname
}
apps_and_hostnames[x]
$ +----------------------+-----------------------+
| x | apps_and_hostnames[x] |
+----------------------+-----------------------+
| ["mongodb","oxygen"] | ["mongodb","oxygen"] |
| ["mysql","carbon"] | ["mysql","carbon"] |
| ["mysql","lithium"] | ["mysql","lithium"] |
| ["web","beryllium"] | ["web","beryllium"] |
| ["web","boron"] | ["web","boron"] |
| ["web","helium"] | ["web","helium"] |
| ["web","hydrogen"] | ["web","hydrogen"] |
| ["web","nitrogen"] | ["web","nitrogen"] |
+----------------------+-----------------------+
推導式
與規則類似,推導式有一個head和一個body。
region := "west"
names := [name | sites[i].region == region; name := sites[i].name]
$ +-----------------+--------+
| names | region |
+-----------------+--------+
| ["smoke","dev"] | "west" |
+-----------------+--------+
這與python中的推導式類似
# Python equivalent of Rego comprehension shown above.
names = [site.name for site in sites if site.region == "west"]
陣列推導式
格式如下:
[ <term> | <body> ]
app_to_hostnames[app_name] = hostnames {
app := apps[_]
app_name := app.name
hostnames := [hostname | name := app.servers[_]
s := sites[_].servers[_]
s.name == name
hostname := s.hostname]
}
app_to_hostnames[app]
$ +-----------+------------------------------------------------------+
| app | app_to_hostnames[app] |
+-----------+------------------------------------------------------+
| "mongodb" | ["oxygen"] |
| "mysql" | ["lithium","carbon"] |
| "web" | ["hydrogen","helium","beryllium","boron","nitrogen"] |
+-----------+------------------------------------------------------+
物件推導式
格式如下:
{ <key>: <term> | <body> }
注意key不能有衝突
app_to_hostnames := {app.name: hostnames |
app := apps[_]
hostnames := [hostname |
name := app.servers[_]
s := sites[_].servers[_]
s.name == name
hostname := s.hostname]
}
app_to_hostnames[app]
$ +-----------+------------------------------------------------------+
| app | app_to_hostnames[app] |
+-----------+------------------------------------------------------+
| "mongodb" | ["oxygen"] |
| "mysql" | ["lithium","carbon"] |
| "web" | ["hydrogen","helium","beryllium","boron","nitrogen"] |
+-----------+------------------------------------------------------+
集合推導式
格式如下:
{ <term> | <body> }
a := [1, 2, 3, 4, 3, 4, 3, 4, 5]
b := {x | x = a[_]}
$ +---------------------+-------------+
| a | b |
+---------------------+-------------+
| [1,2,3,4,3,4,3,4,5] | [1,2,3,4,5] |
+---------------------+-------------+
規則
集合
返回結果是一個集合
hostnames[name] { name := sites[_].servers[_].hostname }
hostnames[name]
$ +-------------+-----------------+
| name | hostnames[name] |
+-------------+-----------------+
| "beryllium" | "beryllium" |
| "boron" | "boron" |
| "carbon" | "carbon" |
+-------------+-----------------+
物件
返回結果是一個可檢索的物件
apps_by_hostname[hostname] = app {
some i
server := sites[_].servers[_]
hostname := server.hostname
apps[i].servers[_] == server.name
app := apps[i].name
}
apps_by_hostname["helium"]
$ "web"
增量定義
增量定義實際就是邏輯或
如下,將servers
和containers
資料抽象為 instances
:
instances[instance] {
server := sites[_].servers[_]
instance := {"address": server.hostname, "name": server.name}
}
instances[instance] {
container := containers[_]
instance := {"address": container.ipaddress, "name": container.name}
}
完整定義
除了使用部分規則定義集合和物件,還可以使用完整規則,完整規則忽略了head中的key,通常用於表示常量
pi := 3.14159
完整定義一次性賦予一個值,如下將32和4賦值給max_memory就會發生錯誤
# Power users get 32GB memory.
max_memory = 32
# Restricted users get 4GB memory.
max_memory = 4
$ module.rego:8: eval_conflict_error: complete rules must not produce multiple outputs
使用:=
時,每個包中只能宣告一個相同名稱的完整定義:
package example
pi := 3.14
# some other rules...
pi := 3.14156 # Redeclaration error because 'pi' already declared above.
函式
Rego支援自定義函式,這些函式可以與內建函式一樣呼叫。函式可以有任意多個輸入,但只能有一個輸出
trim_and_split(s) = x {
t := trim(s, " ")
x := split(t, ".")
}
trim_and_split(" foo.bar ")
$ [
"foo",
"bar"
]
一個函式可以定義多次,用於實現通過條件來選擇所要執行的函式:
q(1, x) = y {
y := x
}
q(2, x) = y {
y := x*4
}
q(1, 2)
$ 2
q(2, 2)
$ 8
但在呼叫時需要注意,入參不能匹配多個函式
r(1, x) = y {
y := x
}
r(x, 2) = y {
y := x*4
}
r(1, 2)
$ module.rego:3: eval_conflict_error: functions must not produce multiple outputs for same inputs
注意,如果無法匹配到函式,則結果是未定義的:
s(x, 2) = y {
y := x * 4
}
s(5, 3)
$ undefined decision
否定
t {
greeting := "hello"
not greeting == "goodbye"
}
t
$ true
下面用於分別找出在和不在prod
環境的app:
prod_servers[name] {
site := sites[_]
site.name == "prod"
name := site.servers[_].name
}
apps_in_prod[name] {
app := apps[_]
server := app.servers[_]
prod_servers[server] #過濾出在prod的app,行與行之間是與的關係,如果不存在則不會執行下一個語句,即name不會被賦值
name := app.name
}
apps_not_in_prod[name] {
name := apps[_].name
not apps_in_prod[name]
}
全量(FOR ALL)
Rego沒有直接的方式來表示全量("FOR ALL")。例如需要找出名稱非"bitcoin-miner"的app時,使用如下方式是錯誤的,無論apps中是否存在名為"bitcoin-miner"的app,最終都會返回true
no_bitcoin_miners {
app := apps[_]
app.name != "bitcoin-miner" # THIS IS NOT CORRECT.
}
可以使用如下方式來實現上述目的:
no_bitcoin_miners_using_negation {
not any_bitcoin_miners
}
any_bitcoin_miners {
some i
app := apps[i]
app.name == "bitcoin-miner"
}
此外還可以使用推導式實現:
no_bitcoin_miners_using_comprehension {
bitcoin_miners := {app | app := apps[_]; app.name == "bitcoin-miner"}
count(bitcoin_miners) == 0
}
模組
在rego中,策略被定義在模組中,一個模組需要包含:
註釋
使用#
進行註釋
package
包可以將一個或多個模組中的規則打包到特定的名稱空間中。
import
模組中可以使用data
和input
引用文字
如在 kubernetes.admission
中定義了一個規則 deny:
package kubernetes.admission
deny[msg] {
input.request.kind.kind == "Pod"
some i
image := input.request.object.spec.containers[i].image
not startswith(image, "hooli.com/")
msg := sprintf("image '%v' comes from untrusted registry", [image])
}
在另一個包中可以通過如下方式引用deny規則:
{
"user": "alice",
"action": "read",
"object": "id123",
"type": "dog"
}
package app.rbac
import data.kubernetes.admission
deny[input.user]
some關鍵字
With 關鍵字
with關鍵字允許查詢以程式設計方式指定巢狀在input 文件 和data 文件下的值。with
關鍵字充當表示式的修飾符。一個表示式可以有零或多個with
修飾符。
格式如下,data.foo.bar
,如果策略試圖替換data.foo.bar.baz
,那麼編譯器將產生錯誤)。
<expr> with <target-1> as <value-1> [with <target-2> as <value-2> [...]]
舉例如下:
allow with input as {"user": "charlie", "method": "GET"} with data.roles as {"dev": ["charlie"]}
with
關鍵字僅影響連線表達符,後續表示式將看到未修改的值。下面是一種例外(input.foo=1,input.bar=2),outer中的輸入在middle中進行了計算
inner := [x, y] {
x := input.foo
y := input.bar
}
middle := [a, b] {
a := inner with input.foo as 100
b := input
}
outer := result {
result := middle with input as {"foo": 200, "bar": 300} #middle中修改了a
}
{
"inner": [
1,
2
],
"middle": [
[
100,
2
],
{
"bar": 2,
"foo": 1
}
],
"outer": [
[
100,
300
],
{
"bar": 300,
"foo": 200
}
]
}
Default 關鍵字
default關鍵字允許策略為具有完整定義的規則生成的文件定義預設值。格式如下:
default <name> = <term>
用法如下,如果沒有default,則會返回undefined
default allow = false
allow {
input.user == "bodddb"
input.method == "GEdddT"
}
allow {
input.user == "aliddce"
}
Else 關鍵字
與程式語言中的else類似
authorize = "allow" {
input.user == "superuser" # allow 'superuser' to perform any operation.
} else = "deny" {
input.path[0] == "admin" # disallow 'admin' operations...
input.source_network == "external" # from external networks.
} # ... more rules
操作符
成員和迭代:in
需要import future.keywords。成員操作符in
用於檢查一個元素是否存在於array, set, 或 object中,返回true
或false
。
import future.keywords.in
p = [x, y, z] {
x := 3 in [1, 2, 3] # array
y := 3 in {1, 2, 3} # set
z := 3 in {"foo": 1, "bar": 3} # object
}
{
"p": [
true,
true,
true
]
}
當在in
操作符左側提供兩個引數,且右側為object或array,則第一個引數作為key(object)或index(array):
import future.keywords.in
p := [ x, y ] {
x := "foo", "bar" in {"foo": "bar"} # key, val with object
y := 2, "baz" in ["foo", "bar", "baz"] # key, val with array
}
{
"p": [
true,
true
]
}
注意在列表(如集合或陣列以及函式引數)上下文中需要使用圓括號來讓兩側引數一一對應
import future.keywords.in
p := x {
x := { 0, 2 in [2] } #這是一個集合,表示0和2 in [2]
}
q := x {
x := { (0, 2 in [2]) }#這是一個集合,但計算的是0, 2 in [2]
}
w := x {
x := g((0, 2 in [2]))#g(x)只有一個引數,需要使用圓括號括起來
}
z := x {
x := f(0, 2 in [2])#f(x)有兩個引數,第一個引數是0,第二個引數是2 in [2]
}
f(x, y) = sprintf("two function arguments: %v, %v", [x, y])
g(x) = sprintf("one function argument: %v", [x])
與not
結合使用,可以很方便地斷言一個元素是否是陣列的成員:
import future.keywords.in
deny {
not "admin" in input.user.roles
}
test_deny {
deny with input.user.roles as ["operator", "user"]
}
{
"test_deny": true
}
使用some
,可以根據不同的型別引入新的變數
import future.keywords.in
p[x] {
some x in ["a", "r", "r", "a", "y"]
}
q[x] {
some x in {"s", "e", "t"}
}
r[x] {
some x in {"foo": "bar", "baz": "quz"}
}
{
"p": [
"a",
"r",
"y"
],
"q": [
"e",
"s",
"t"
],
"r": [
"bar",
"quz"
]
}
使用兩個引數可以檢索object的關鍵字和array的索引
import future.keywords.in
p[x] {
some x, "r" in ["a", "r", "r", "a", "y"] # key variable, value constant
}
q[x] = y {
some x, y in ["a", "r", "r", "a", "y"] # both variables
}
r[y] = x {
some x, y in {"foo": "bar", "baz": "quz"}
}
{
"p": [
1,
2
],
"q": {
"0": "a",
"1": "r",
"2": "r",
"3": "a",
"4": "y"
},
"r": {
"bar": "foo",
"quz": "baz"
}
}
some
變數的任何引數都可以是複合的非基礎值:
import future.keywords.in
p[x] = y {
some x, {"foo": y} in [{"foo": 100}, {"bar": 200}]#x為key為foo的陣列索引,y為key為foo的值
}
p[x] = y {
some {"bar": x}, {"foo": y} in {{"bar": "b"}: {"foo": "f"}} # x為bar的值,y為foo的值
}
{
"p": {
"0": 100,
"b": "f"
}
}
等式:賦值,比較和聯合
Rego支援三種等式:賦值(:=),比較()和聯合(=)。建議使用賦值(:=)和比較()。
賦值 :=
可以使用一種簡單的解構形式將陣列中的值解包並將其分配給變數
address := ["3 Abbey Road", "NW8 9AY", "London", "England"]
in_london {
[_, _, city, country] := address
city == "London"
country == "England"
}
{
"address": [
"3 Abbey Road",
"NW8 9AY",
"London",
"England"
],
"in_london": true
}
比較 ==
聯合 =
Rego會將比較為真的值賦於變數。聯合可以賦予變數使表示式為true的值。
sites[i].servers[j].name = apps[k].servers[m]
+---+---+---+---+
| i | j | k | m |
+---+---+---+---+
| 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 1 |
| 0 | 2 | 1 | 0 |
| 1 | 0 | 0 | 2 |
| 1 | 1 | 0 | 3 |
| 1 | 2 | 1 | 1 |
| 2 | 0 | 0 | 4 |
| 2 | 1 | 2 | 0 |
+---+---+---+---+
比較表示式
a == b # `a` is equal to `b`.
a != b # `a` is not equal to `b`.
a < b # `a` is less than `b`.
a <= b # `a` is less than or equal to `b`.
a > b # `a` is greater than `b`.
a >= b # `a` is greater than or equal to `b`.
內建函式
內建函式的格式如下:
<name>(<arg-1>, <arg-2>, ..., <arg-n>)
錯誤
預設情況下,遇到執行時錯誤的內建函式呼叫會將結果設為undefined (通常可以被視為false),且不會停止策略計算。這種方式可以保證在使用呼叫內建函式時,輸入無效引數不會導致整個策略停止計算。
策略相關
Assignment and Equality
# assign variable x to value of field foo.bar.baz in input
x := input.foo.bar.baz
# check if variable x has same value as variable y
x == y
# check if variable x is a set containing "foo" and "bar"
x == {"foo", "bar"}
# OR
{"foo", "bar"} == x
Lookup
Arrays
# lookup value at index 0
val := arr[0]
# check if value at index 0 is "foo"
"foo" == arr[0]
# find all indices i that have value "foo"
"foo" == arr[i]
# lookup last value
val := arr[count(arr)-1]
# with `import future.keywords.in`
some 0, val in arr # lookup value at index 0
0, "foo" in arr # check if value at index 0 is "foo"
some i, "foo" in arr # find all indices i that have value "foo"
Objects
# lookup value for key "foo"
val := obj["foo"]
# check if value for key "foo" is "bar"
"bar" == obj["foo"]
# OR
"bar" == obj.foo
# check if key "foo" exists and is not false
obj.foo
# check if key assigned to variable k exists
k := "foo"
obj[k]
# check if path foo.bar.baz exists and is not false
obj.foo.bar.baz
# check if path foo.bar.baz, foo.bar, or foo does not exist or is false
not obj.foo.bar.baz
# with `import future.keywords.in`
o := {"foo": false}
# check if value exists: the expression will be true
false in o
# check if value for key "foo" is false
"foo", false in o
Sets
# check if "foo" belongs to the set
a_set["foo"]
# check if "foo" DOES NOT belong to the set
not a_set["foo"]
# check if the array ["a", "b", "c"] belongs to the set
a_set[["a", "b", "c"]]
# find all arrays of the form [x, "b", z] in the set
a_set[[x, "b", z]]
# with `import future.keywords.in`
"foo" in a_set
not "foo" in a_set
some ["a", "b", "c"] in a_set
some [x, "b", z] in a_set
Iteration
Arrays
# iterate over indices i
arr[i]
# iterate over values
val := arr[_]
# iterate over index/value pairs
val := arr[i]
# with `import future.keywords.in`
some val in arr # iterate over values
some i, _ in arr # iterate over indices
some i, val in arr # iterate over index/value pairs
Objects
# iterate over keys
obj[key]
# iterate over values
val := obj[_]
# iterate over key/value pairs
val := obj[key]
# with `import future.keywords.in`
some val in obj # iterate over values
some key, _ in obj # iterate over keys
some key, val in obj # key/value pairs
Sets
# iterate over values
set[val]
# with `import future.keywords.in`
some val in set
Advanced
# nested: find key k whose bar.baz array index i is 7
foo[k].bar.baz[i] == 7
# simultaneous: find keys in objects foo and bar with same value
foo[k1] == bar[k2]
# simultaneous self: find 2 keys in object foo with same value
foo[k1] == foo[k2]; k1 != k2
# multiple conditions: k has same value in both conditions
foo[k].bar.baz[i] == 7; foo[k].qux > 3
For All
# assert no values in set match predicate
count({x | set[x]; f(x)}) == 0
# assert all values in set make function f true
count({x | set[x]; f(x)}) == count(set)
# assert no values in set make function f true (using negation and helper rule)
not any_match
# assert all values in set make function f true (using negation and helper rule)
not any_not_match
any_match {
set[x]
f(x)
}
any_not_match {
set[x]
not f(x)
}
Rules
In the examples below ...
represents one or more conditions.
Constants
a = {1, 2, 3}
b = {4, 5, 6}
c = a | b
Conditionals (Boolean)
# p is true if ...
p = true { ... }
# OR
p { ... }
Conditionals
default a = 1
a = 5 { ... }
a = 100 { ... }
Incremental
# a_set will contain values of x and values of y
a_set[x] { ... }
a_set[y] { ... }
# a_map will contain key->value pairs x->y and w->z
a_map[x] = y { ... }
a_map[w] = z { ... }
Ordered (Else)
default a = 1
a = 5 { ... }
else = 10 { ... }
Functions (Boolean)
f(x, y) {
...
}
# OR
f(x, y) = true {
...
}
Functions (Conditionals)
f(x) = "A" { x >= 90 }
f(x) = "B" { x >= 80; x < 90 }
f(x) = "C" { x >= 70; x < 80 }