Once a pattern has triggered, its body is executed. The body consists of assignments, conditionals, loops, function calls, and termination statements.
The statements in the pattern body are not guaranteed to be executed in strict sequence. The system guarantees that dependencies between statements are properly resolved, but statements with no dependencies can be executed out of order or concurrently.
Assignments take the form:
name := expression;
Where the name must be a valid variable name and the expression must adhere to one of the valid expression kinds described in this section.
It is an error to assign to a name that has been declared as a constant in the same pattern.
Variables are dynamically typed according to the type of the expression. It is not an error to rebind a variable to a value with a different type to its previous value.
void keyword is added in TPL 1.6/BMC Discovery 8.3. Assigning this to an attribute on a node deletes the attribute. The statement:
node['attribute_name'] := void;
is the equivalent of:
There is just one kind of conditional statement: the
if expression then
[elif expression then
There can be any number of
elif clauses; the
else clause is optional.
The expression can have any type — the values
none, empty string, empty list, empty set, and numeric zero are considered false; all other values are considered true.
There is only one looping construct, the
for variable in expression do
The expression must be a list, table or search result set. It is a runtime error if it is not. The loop body executes for each item in the list, table or result set. When iterating over a list or result set, the loop variable is set to the corresponding list / set member; when iterating over a table, the variable is set to the key of the table item.
Looping can be terminated early with a
break statement, or the next iteration started before the end of the loop body with the
continue statement. For example:
for item in collection do
if item = 5 then
elif item = 10 then
On exit from a loop, the loop variable is no longer valid. The loop variable cannot be modified in the body of the loop.
After triggering, it is common for a body to use some "existence criteria" to confirm that the entity modeled by the pattern does indeed exist. It can be useful to terminate the execution of the pattern body prematurely, using the
stop statement, for example:
if not existence_condition then
If at all possible, patterns should use trigger conditions to avoid triggering when not necessary, rather than using
stop in a condition. It is much more efficient to avoid triggering a pattern than to trigger and then stop. If this cannot be avoided then the stop condition should be tested as early as possible especially if additional discovery can be avoided by doing so otherwise considerable load might be put on the system.
There are a number of different kinds of expressions used in assignments and other declarations. Simple value expressions consist of literal values as described in the section on Common declarations and variable names.
Numeric values can be involved in arithmetic expressions. Unary expressions prefix a value:
Negates the value
Has no effect to numeric arguments.
Infix arithmetic operations are the following:
Bitwise left shift
Bitwise right shift
Bitwise exclusive or
% operators take binding precedence over
-, which take precedence over
>>, which take precedence over the other operators. Parentheses can be used to group sets of operations to override the precedence rules.
String values can be concatenated using the
+ infix operator.
Logical expressions are expressions that evaluate to true or false. In boolean operations, values are considered true or false according to the same rules as for if statements, as described in section Conditionals. Logical expressions can be combined with the following operators:
For numeric values, numeric comparisons. For strings, character (lexicographical) comparisons.
Sub-word and sub-string tests.
True if string
True if a is considered false; false otherwise.
True if both
True is either
Binding precedence for the operators follows the order they are given above. The operators before
not are grouped together with the highest precedence, followed by
Node scoped values
Variables containing datastore nodes can be used to access the nodes' attributes, using the scoping dot character. For example:
process := // get a DiscoveredProcess node from somewhere
cmd_and_args := process.cmd + " " + process.args;
An attempt to access a non-existent attribute results in
Key expressions can also be used to access values from related nodes:
method := process.#Member:List:List:ProcessList.discovery_method;
Since key expression traversals can find more than one target node, key expressions always return list values. As a consequence, accessing a non-existent traversal key expression results in an empty list.
Attributes of nodes can also be set using the same kind of dot scoping. For example:
host := model.host(process);
if set_virtual then
host.virtual := true;
Any attribute name can be set in this way, regardless of whether or not the name is defined in the taxonomy. The attribute is stored with the type of the assigned value, even if the taxonomy specifies that it should have a different type.
Dynamically-named attributes can be set and retrieved using the
 operator, and tested for existence with the
attr_name := "test";
attr_value := "example";
if attr_name in node then
attr_value := node[attr_name];
node[attr_name] := attr_value;
Unlike with dot scoping, it is a runtime error to use the
 operator to access a non-existent attribute:
okay := node.not_defined; // value is none
fail := node["not_defined"]; // runtime error
In BMC Discovery the naming convention for attributes is to use only the lowercase letters a to z with underscores (_) to separate words. Attribute names must not start or end with a space. Attribute names in searches can only be specified using the same characters. If you use other characters, for example a hyphen (-), your search will cause an error.
Attribute names cannot be changed
Once an attribute has been created, you cannot change its name.
Literal lists are declared using [ and ] characters, with a comma-separated sequence of expression inside. e.g.
values := [ "one", "two", "three" ];
Lists can be dereferenced using the
first := values;
List indexing starts from zero.
Lists can be concatenated together with the
+ operator. The result of concatenating two lists is a new list; the original lists are left unchanged.
From TPL 1.2, items can be appended to lists with the
values := [ "one", "two" ];
You can also create a list using the
values := list("one", "two", "three");
From TPL 1.2, dynamic tables can be created with the
table1 := table();
table2 := table(one := "first", two := "second");
Keys are looked up and set in a table using the
value := table2["one"]; // value is "first"
table1["three"] := "example";
It is a runtime error to attempt to access a key that is not present. Test if a key is present in a table with the
if "three" in table1 then
value := table1["three"];
Iterating over a table with a for loop gives the keys, which can be looked up with the
for key in table2 do
value := table2[key];
log.info("The key is %key% and the value is %value%.");
Functions are used to interact with discovery, the data store, and external systems. Functions can be used as statements or as expressions, depending on whether there is a return value. Functions that return a value can be used as statements, in which case the return value is discarded.
Functions are defined outside of the pattern language. Function names are usually scoped, using the dot character to separate the scope from the function name. The scope is used to group related functions together. The currently defined scopes are:
inference, plus the global scope.
Functions are called as follows
Function arguments are comma-separated sequences of expressions or assignments. Assignment-style arguments are used to provide named arguments. There are three different ways that functions can process their arguments.
- Positional arguments
In functions with positional arguments, the order in which arguments are provided is significant. Arguments can be optional. Optional arguments are populated in the order they are specified — if a function takes two optional arguments, it is impossible to set the value of the second without also providing a value for the first.
- Positional and named arguments
Functions with positional and named arguments take a number of positional arguments, followed by a number of optional named arguments. Since the named arguments are identified by their names, any of the optional arguments can be set. Arguments are given names using assignment (
name := value) syntax.
- Implicitly and explicitly named arguments
Functions whose arguments are all optional named arguments can use implicitly named arguments. In that case, arguments can be provided either with assignment syntax, or can be provided as plain variable names. When plain variable names are used, the argument name is taken from the variable name, and its value from the variable's value. In a function with implicitly named arguments, it is an error to provide non-assignment arguments that are expressions, rather than plain variable names.
The functions are grouped as follows:
- Global scope functions
- Log functions
- Text functions
- Number functions
- Regex functions
- XPath functions
- Table functions
- Discovery functions
- Model functions
- Email functions
- Time functions
- Inference functions
All functions are listed on the Functions page along with a one line overview of each.
Unqualified literal strings take part in string interpolation. Values to be interpolated are surrounded by
% characters. Values are accessed from the pattern's variables and constants. For example, in:
foo := "world";
bar := "Hello %foo%!";
bar is set to the string "
Node scoped variables can also be interpolated. The above command and args example can be implemented with an interpolation:
cmd_and_args := "%process.cmd% %process.args%";
To embed a literal percentage symbol in a string, use
increase := 75;
report := "The size has increased by %increase% %%";
Values interpolated into strings must be strings or numbers.
Sometimes it is useful to perform interpolation on a pre-existing string, particularly to specify patterns in constants. This can be achieved with an
expand expression, e.g.
template := raw "db_%instance%_1";
db_name := expand(template, instance := some_node.instance_name);
The use of the
raw string suppresses the interpolation that would normally occur in setting the
template variable. The values to interpolate into the string in the
expand are implicitly and explicitly named arguments as described in the section on functions above.
Slicing can be applied to lists and strings and it works in the same way as Python. So there are several forms:
which will start from index lowerbound (inclusive) to upperbound (exclusive). If the bound is not specified it will represent the start (lower) or end (upper) of the value. It is also possible to use negative values which is equivalent to writing
size(value) + bound, that is,
value[:-1] is equivalent to
value[:size(value)-1]. For example:
value := 'an example string'
The second form takes a step value (which if not present is equivalent to a step of 1).
Searches using the BMC Discovery search service are valid expressions in pattern bodies, with a number of minor modifications as described in Search expressions.