This documentation supports the 20.08 (12.1) version of BMC Discovery.

To view an earlier version of the product, select the version from the Product version menu.

Body

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

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

The 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:

model.withdraw(node, 'attribute_name');

Conditionals

There is just one kind of conditional statement: the if:

if expression then
  ...
[elif expression then
  ...
]...
[else
  ...
]
end if;

There can be any number of elif clauses; the else clause is optional.

The expression can have any type — the values false, none, empty string, empty list, empty set, and numeric zero are considered false; all other values are considered true.

Loops

There is only one looping construct, the for loop:

for variable in expression do
  ...
end for;

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
    break;
  elif item = 10 then
    continue;
  end if;
  doSomething(item);
end for;

On exit from a loop, the loop variable is no longer valid. The loop variable cannot be modified in the body of the loop.

Body termination

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
  stop;
end if;

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.

Value expressions

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.

~

Bitwise inverse

 Infix arithmetic operations are the following:

*

Multiplication

/

Division

%

Modulo

+

Addition

-

Subtraction

<<

Bitwise left shift

>>

Bitwise right shift

&

Bitwise and

^

Bitwise exclusive or

|

Bitwise or

 The * / and % operators take binding precedence over + and -, which take precedence over << and >>, 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

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:

 

a < b
a <= b
a > b
a >= b
a = b
a <> b

For numeric values, numeric comparisons. For strings, character (lexicographical) comparisons.

a in b
a not in b

Containment tests.

If b is a list or set, true if a is / is not a member of the list / set.

If b is a table, true if a is / is not a key in the table.

If b is a node, true if a is / is not an attribute of the node.

a has subword b
a has substring b

Sub-word and sub-string tests. a and b must be strings, except that a can be none, in which case the test result is false.

a matches b

True if string a matches regular expression b. If a is none, the test result is false.

not a

True if a is considered false; false otherwise.

a and b

True if both a and b are; false otherwise.

a or b

True is either a or b or both are true; false if they are both false.

 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 not, then and, then or.

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 none.

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;
end if;

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 in operator:

attr_name  := "test";
attr_value := "example";
if attr_name in node then
    attr_value := node[attr_name];
else
    node[attr_name] := attr_value;
end if;

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.

Note

Once an attribute has been created, you cannot change its name.

Lists

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 [] operator:

first := values[0];

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 list.append function:

values := [ "one", "two" ];
list.append(values, "three")

You can also create a list using the list() function:

values := list("one", "two", "three");

Tables

From TPL 1.2, dynamic tables can be created with the table() function:

table1 := table();
table2 := table(one := "first", two := "second");

Keys are looked up and set in a table using the [] operator:

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 in operator:

if "three" in table1 then
    value := table1["three"];
    ...
end if;

Iterating over a table with a for loop gives the keys, which can be looked up with the [] operator:

for key in table2 do
    value := table2[key];
    log.info("The key is %key% and the value is %value%.");
end for;

Functions

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: log, text, number, regex, xpath, list, table, discovery, model, email, time, and inference, plus the global scope.

Functions are called as follows

scope.name(arguments)

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:

All functions are listed on the Functions page along with a one line overview of each.

String interpolation

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%!";

The variable bar is set to the string "Hello world!".

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

Slicing can be applied to lists and strings and it works in the same way as Python. So there are several forms:

value[lowerbound:upperbound]

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'

value[3:] is 'example string'
value[:3] is 'an '
value[-3:] is 'ing'
value[:-3] is 'an example str'

The second form takes a step value (which if not present is equivalent to a step of 1).

value[::2] is 'a xml tig'
value[::3] is 'aemetn'
value[::-1] is 'gnirts elpmaxe na'

Search expressions

Searches using the BMC Discovery search service are valid expressions in pattern bodies, with a number of minor modifications as described in Search expressions.

Was this page helpful? Yes No Submitting... Thank you

Comments