
This Article only discusses about following topics in Sentinel Language:
- Boolean Expressions
- Arithmetic
- Slices
- Conditionals
- Loops
- Collection Operations
Sentinel policies are written using the Sentinel language. This language is easy to learn and easy to write. You can learn the Sentinel language and be productive within an hour. Learning Sentinel doesn’t require any formal programming experience.
Language: Boolean Expressions
Boolean expressions are expressions that evaluate to a boolean value from the result of a combination of one or more comparisons and logical operators. Boolean expressions form the basis of policy since policy can be broken down to a set of logical decisions that turn into true or false.
A single boolean expression is the body of a rule. Additionally, boolean expressions are used with conditionals, and more.
Order of Operations
The precedence of operators is shown below. Operators with higher precedence values (larger numbers) are evaluated first. Operators with the same precedence value are evaluated left-to-right.
Precedence Operator
6 * / %
5 + -
4 else
3 == != < <= > >= "is" "is not" "matches" "contains" "in"
2 and
1 or xor
Examples of this precedence are shown below:
4 * 5 / 5 // 4
4 * 5 + 2 // 22
4 + 5 * 2 // 14
Logical Operators
Logical operators are used to change or combine logical expressions. There are three binary operators and
, or
, and xor
. There is a single unary operator not
(which can also be specified as !
).
The binary operators require two boolean operands and the unary operator requires a single boolean operand. In both case, the operands are values or expressions that are boolean types.
Below is a basic explanation of the logical operators directly from the specification:
and conditional AND p and q is "if p then q else false"
or conditional OR p or q is "if p then true else q"
xor conditional XOR p xor q is "if p and not q or not p and q then true"
! NOT !p is "not p"
not NOT !p is "not p"
Using parentheses to group boolean expressions, you can combine boolean expressions to become more complex or affect ordering:
(p and q) or r
p and (q or r)
Comparison Operators
Comparison operators compare two operands and yield a boolean value.
For any comparison, the two operands must be equivalent types. The one exception are integers and floats. When an integer is compared with a float, the integer is promoted to a floating point value.
The available comparison operators are:
== equal
!= not equal
< less
<= less or equal
> greater
>= greater or equal
"is" equal
"is not" not equal
The behavior of is
with ==
and is not
with !=
is identical.
Example comparisons:
name is "Mitchell"
idnumber > 42
idnumber <= 12
Using the logical operators, these can be combined:
name is "Mitchell" and idnumber > 42
The language specification provides more detail on exactly how comparison behaves.
Set Operators
The set operators contains
and in
test for inclusion in a collection (a list or map).
Set operators may be negated by prefixing the operator with not
, such as not contains
and not in
. This is equivalent to wrapping the expression in a unary not
but results in a more readable form.
contains
tests if the left-hand collection contains the right-hand value. in
tests if the right-hand collection contains the left-hand value. For maps, “contains” looks at the keys, not the values.
Examples:
[1, 2, 3] contains 2 // true
[1, 2, 3] contains 5 // false
[1, 2, 3] contains "value" // false
[1, 2, 3] not contains "value" // true
{ "a": 1, "b": 2 } contains "a" // true
{ "a": 1, "b": 2 } contains "c" // false
{ "a": 1, "b": 2 } contains 2 // false
{ "a": 1, "b": 2 } not contains 2 // true
Matches Operator
The matches
operator tests if a string matches a regular expression. The matches
operator can be negated by prefixing the operator with not
, such as not matches
.
The left-hand value is the string to test. The right-hand value is a string value representing a regular expression. The syntax of the regular expression is the same general syntax used by Python, Ruby, and other languages. The precise syntax accepted is the syntax accepted by RE2.
Regular expressions are not anchored by default; any anchoring must be explicitly specified.
"test" matches "e" // true
"test" matches "^e" // false
"TEST" matches "test" // false
"TEST" matches "(?i)test" // true
"ABC123" matches "[A-Z]+\\d+" // true
"test" not matches "e" // false
Any, All Expressions
any
and all
expressions allow testing that any or all elements of a collection match some boolean expression. The result of an any
and all
expression is a boolean.
any
returns the boolean value true
if any value in the collection expression results in the body expression evaluating to true
. If the body expression evalutes to false
for all values, the any expression returns false
.
all
returns the boolean true
if all values in the collection expression result in the body expression evaluating to true
. If any value in the collection expression result in the body expression evaluating to false
, the all
expression returns false
.
For empty collections, any
returns false
and all
returns true
.
all group.tasks as t { t.driver is "vmware" }
any group.tasks as t { t.driver is "vmware" }
Since any
and all
expressions are themselves boolean expressions, they can be combined with other operators:
any ["a", "b"] as char { char is "a" } or
other_value is "another"
Emptiness Comparison
The expressions is empty
and is not empty
provide a convenience method for determining the emptiness of a value. It supports the same types as the built-in length, collections and strings.
[] is empty // true
[] is not empty // false
["foo"] is empty // false
["foo"] is not empty // true
undefined is empty // undefined
undefined is not empty // undefined
Language: Arithmetic
Sentinel supports arithmetic operators for integers and floats. Sentinel supports sum, difference, product, quotient, and remainder.
+ sum
* difference
* product
/ quotient
% remainder
These operators work in a typical infix style:
4 + 8 // 12
8 * 2 // 16
8 / 4 // 2
8 / 5 // 1
8 % 5 // 3
Order of Operations
Arithmetic follows a standard mathematical order of operations. Grouping with parentheses can be used to affect ordering.
4 * 5 / 5 // 4
4 * 5 + 2 // 22
4 + 5 * 2 // 14
(4 + 5) * 2 // 18
A full table of operator precendence can be found on the boolean expressions page. This shows how arithmetic operators relate to other operators.
Integer Division
Integer division with a remainder rounds down to the nearest integer. Example: 8 / 3 is 2
.
If the divisor is zero, an error occurs.
Mixed Numeric Operations
Mixed numeric operations between integer and floating-point values are permitted. The result is a floating-point operation with the integer converted to a floating-point value for purposes of calculation.
import "types"
a = 1.1 + 1 // 2.1
types.type_of(a) // "float"
Language: Slices
Slices are an efficient way to create a substring or sublist from an existing string or list, respectively.
The syntax for creating a slice is:
a[low : high]
low
is the lowest index to slice from. The resulting slice includes the value at low
. high
is the index to slice to. The resulting slice will not include the value at high
. The length of the resulting list or string is always high - low
.
a = [1, 2, 3, 4, 5]
b = a[1:4] // [2, 3, 4]
a = "hello"
b = a[1:4] // "ell"
Convenience Shorthands
Some convenience shorthands are available by omitting the value for either the low or high index in the slice expression: omitting low
implies a low index of 0, and omitting high
implies a high index of the length of the list.
a = [1, 2, 3, 4, 5]
b = a[:2] // [1, 2] (same as a[0:2])
b = a[2:] // [3, 4, 5] (same as a[2:length(a)])
Language: Conditionals
Conditional statements allow your policy to behave differently depending on a condition.
Conditional statements may only appear outside of rule expressions, such as in functions or in the global scope of a policy. This is because rules are only allowed to contain a single boolean expression.
If Statements
if
statements only execute their bodies if a condition is met. The syntax of an if
statement is:
if condition {
// ... this is executed if condition is true
}
The condition
must result in a boolean, such as by calling a function or evaluating a boolean expression. If the condition
is true
, the body (within the {}
) is executed. Otherwise, the body is skipped.
Examples:
// This would execute the body
value = 12
if value is 18 {
print("condition met")
}
// Direct boolean values can be used
value = true
if value {
print("condition met")
}
// This would not execute the body since the boolean expression will
// result in undefined.
value = {}
if value["key"] > 12 {
print("condition met")
}
Else, Else If
An else
clause can be given to an if
statement to execute a body in the case the condition is not met. By putting another if
statement directly after the else
, multiple conditions can be tested for. The syntax is:
if condition {
// ...
} else {
// ...
}
if condition {
// ...
} else if other_condition {
// ...
} else {
// ...
}
Scoping
The body of an if
statement does not create a new scope. Any variables assigned within the body of an if statement will modify the scope that the if
statement itself is in.
Example:
if true {
a = 42
}
print(a) // 42
a = 18
if true {
a = 42
}
print(a) // 42
Case Statements
case
statements are a selection control mechanism that execute a clause based on matching expressions. It is worth noting that the expression for case
is optional. When no expression is provided, it defaults the expression to true
. Additionally, the order of clauses is important, as they are evaluated from top to bottom, executing the first match. The syntax of a case statement is:
case expression {
when clause_expression:
// executed when clause_expression and expression are equal
else:
// executed if no clause matches expression
}
When Clause
Any clause that has an expression for comparison must use the when
keyword. It accepts a list of expressions, seperated by a ,
.
Example:
case x {
when "foo", "bar":
return true
}
case {
when x > 40:
return true
}
Else Clause
The else
keyword allows for capturing any expressions that have no matching when
clause.
Example:
case x {
when "foo", "bar":
return true
else:
return false
}
Language: Loops
Loop statements allow you to execute a body of code for each element in a collection or for some fixed number of times.
Loop statements may only appear outside of rule expressions, such as in functions or in the global scope of a policy. This is because rules are only allowed to contain a single boolean expression.
For Statements
for
statements allow repeated execution of a block for each element in a collection.
Example:
// A basic sum
count = 0
for [1, 2, 3] as num {
count += num
}
The syntax is for COLLECTION as value
. This will iterate over the collection, assigning each element to value
. In the example above, each element is assigned to num
. The body is executed for each element. In the example above, the body adds num
to the count
variable. This creates a basic sum of all values in the collection.
For a map, the assigned element is the key in the map. In the example below, name
would be assigned map keys.
list = []
for { "a": 1, "b": 2 } as name {
append(list, name)
}
print(list) // ["a" "b"]
An alternate syntax is for COLLECTION as key, value
. This will assign both the key and value to a variable. For a list, the key is the element index. For a map, it is the key and value is assigned the element value. Example:
count = 0
for { "a": 1, "b": 2 } as name, num {
count += num
}
print(count) // 3
Scoping
The body of a for
statement creates a new scope. If a variable is assigned within the body of a for statement that isn’t assigned in a parent scope, that variable will only exist for the duration of the body execution.
Example:
for list as value {
a = 42
}
print(a) // undefined
a = 18
for list as value {
a = 42
}
print(a) // 18
Language: Collection Operations
Collection operations are expressions that are performed on a list or map to return a variation of the initial data.
At the moment, filter
is the only collection operation available.
Filter Expression
filter
is a quantifier expression that returns a subset of the provided collection. Only elements whose filter body returns true will be returned. If any of the elements filter body returns undefined, the final result will be undefined.
Filter uses the same syntax as the any
and all
boolean expressions:
filter list as value { condition } // Single-iterator, list
filter list as idx, value { condition } // Double-iterator, list
filter map as key { condition } // Single-iterator, map
filter map as key, value { condition } // Double-iterator, map
Examples:
l = [1, 1, 2, 3, 5, 8]
evens = filter l as v { v % 2 is 0 } // [2, 8]
m = { "a": "foo", "b": "bar" }
matched_foo = filter m as _, v { v is "foo" } // { "a": "foo" }
Map Expression
map
is a quantifier expression that returns a list, regardless of the input collection type. Each element within the input collection is evaluted according to the map expression body and appended to the result.
l = [1, 2]
r = map l as v { v % 2 } // [false, true]
m = { "a": "foo", "b": "bar" }
r = map m as k, v { v } // ["foo", "bar"]
Thanks for sharing. I read many of your blog posts, cool, your blog is very good. https://www.binance.com/fr/register?ref=RQUR4BEO