From 56ae875861ab260b80a030f50c4aff9f9dc8fff0 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 13 Apr 2024 13:32:39 +0200 Subject: Adding upstream version 2.14.2. Signed-off-by: Daniel Baumann --- doc/17-language-reference.md | 1371 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1371 insertions(+) create mode 100644 doc/17-language-reference.md (limited to 'doc/17-language-reference.md') diff --git a/doc/17-language-reference.md b/doc/17-language-reference.md new file mode 100644 index 0000000..5686d55 --- /dev/null +++ b/doc/17-language-reference.md @@ -0,0 +1,1371 @@ +# Language Reference + +## Object Definition + +Icinga 2 features an object-based configuration format. You can define new +objects using the `object` keyword: + +``` +object Host "host1.example.org" { + display_name = "host1" + + address = "192.168.0.1" + address6 = "2001:db8:1234::42" +} +``` + +In general you need to write each statement on a new line. Expressions started +with `{`, `(` and `[` extend until the matching closing character and can be broken +up into multiple lines. + +Alternatively you can write multiple statements on a single line by separating +them with a semicolon: + +``` +object Host "host1.example.org" { + display_name = "host1" + + address = "192.168.0.1"; address6 = "2001:db8:1234::42" +} +``` + +Each object is uniquely identified by its type (`Host`) and name +(`host1.example.org`). Some types have composite names, e.g. the +`Service` type which uses the `host_name` attribute and the name +you specified to generate its object name. + +Exclamation marks (!) are not permitted in object names. + +Objects can contain a comma-separated list of property +declarations. Instead of commas semicolons may also be used. +The following data types are available for property values: + +All objects have at least the following attributes: + +Attribute | Description +---------------------|----------------------------- +name | The name of the object. This attribute can be modified in the object definition to override the name specified with the `object` directive. +type | The type of the object. + +## Expressions + +The following expressions can be used on the right-hand side of assignments. + +### Numeric Literals + +A floating-point number. + +Example: + +``` +27.3 +``` + +### Duration Literals + +Similar to floating-point numbers except for the fact that they support +suffixes to help with specifying time durations. + +Example: + +``` +2.5m +``` + +Supported suffixes include ms (milliseconds), s (seconds), m (minutes), +h (hours) and d (days). + +Duration literals are converted to seconds by the config parser and +are treated like numeric literals. + +### String Literals + +A string. + +Example: + +``` +"Hello World!" +``` + +#### String Literals Escape Sequences + +Certain characters need to be escaped. The following escape sequences +are supported: + +Character | Escape sequence +--------------------------|------------------------------------ +" | \\" +\\ | \\\\ +<TAB> | \\t +<CARRIAGE-RETURN> | \\r +<LINE-FEED> | \\n +<BEL> | \\b +<FORM-FEED> | \\f + +In addition to these pre-defined escape sequences you can specify +arbitrary ASCII characters using the backslash character (\\) followed +by an ASCII character in octal encoding. + +### Multi-line String Literals + +Strings spanning multiple lines can be specified by enclosing them in +{{{ and }}}. + +Example: + +``` +{{{This +is +a multi-line +string.}}} +``` + +Unlike in ordinary strings special characters do not have to be escaped +in multi-line string literals. + +### Boolean Literals + +The keywords `true` and `false` are used to denote truth values. + +### Null Value + +The `null` keyword can be used to specify an empty value. + +### Dictionary + +An unordered list of key-value pairs. Keys must be unique and are +compared in a case-sensitive manner. + +Individual key-value pairs must either be comma-separated or on separate lines. +The comma after the last key-value pair is optional. + +Example: + +``` +{ + address = "192.168.0.1" + port = 443 +} +``` + +Identifiers may not contain certain characters (e.g. space) or start +with certain characters (e.g. digits). If you want to use a dictionary +key that is not a valid identifier, you can enclose the key in double +quotes. + +### Array + +An ordered list of values. + +Individual array elements must be comma-separated. +The comma after the last element is optional. + +Example: + +``` +[ "hello", 42 ] +``` + +An array may simultaneously contain values of different types, such as +strings and numbers. + +### Operators + +The following operators are supported in expressions. The operators are sorted by descending precedence. + +Operator | Precedence | Examples (Result) | Description +---------|------------|-----------------------------------------------|-------------------------------- +`()` | 1 | (3 + 3) * 5 | Groups sub-expressions +`()` | 1 | Math.random() | Calls a function +`[]` | 1 | a[3] | Array subscript +`.` | 1 | a.b | Element access +`!` | 2 | !"Hello" (false), !false (true) | Logical negation of the operand +`~` | 2 | ~true (false) | Bitwise negation of the operand +`+` | 2 | +3 | Unary plus +`-` | 2 | -3 | Unary minus +`&` | 2 | &var (reference to 'var') | Reference operator +`*` | 2 | *var | Indirection operator +`*` | 3 | 5m * 10 (3000) | Multiplies two numbers +`/` | 3 | 5m / 5 (60) | Divides two numbers +`%` | 3 | 17 % 12 (5) | Remainder after division +`+` | 4 | 1 + 3 (4), "hello " + "world" ("hello world") | Adds two numbers; concatenates strings +`-` | 4 | 3 - 1 (2) | Subtracts two numbers +`<<` | 5 | 4 << 8 (1024) | Left shift +`>>` | 5 | 1024 >> 4 (64) | Right shift +`<` | 6 | 3 < 5 (true) | Less than +`>` | 6 | 3 > 5 (false) | Greater than +`<=` | 6 | 3 <= 3 (true) | Less than or equal +`>=` | 6 | 3 >= 3 (true) | Greater than or equal +`in` | 7 | "foo" in [ "foo", "bar" ] (true) | Element contained in array +`!in` | 7 | "foo" !in [ "bar", "baz" ] (true) | Element not contained in array +`==` | 8 | "hello" == "hello" (true), 3 == 5 (false) | Equal to +`!=` | 8 | "hello" != "world" (true), 3 != 3 (false) | Not equal to +`&` | 9 | 7 & 3 (3) | Binary AND +`^` | 10 | 17 ^ 12 (29) | Bitwise XOR +| | 11 | 2 | 3 (3) | Binary OR +`&&` | 12 | true && false (false), 3 && 7 (7), 0 && 7 (0) | Logical AND +|| | 13 | true || false (true), 0 || 7 (7)| Logical OR +`=` | 14 | a = 3 | Assignment +`=>` | 15 | x => x * x (function with arg x) | Lambda, for loop +`?` | 16 | (2 * 3 > 5) ? 1 : 0 (1) | [Ternary operator](17-language-reference.md#conditional-statements-ternary) + +### References + +A reference to a value can be obtained using the `&` operator. The `*` operator can be used +to dereference a reference: + +``` +var value = "Hello!" +var p = &value /* p refers to value */ +*p = "Hi!" +log(value) // Prints "Hi!" because the variable was changed +``` + +### Namespaces + +Namespaces can be used to organize variables and functions. They are used to avoid name conflicts. The `namespace` +keyword is used to create a new namespace: + +``` +namespace Utils { + function calculate() { + return 2 + 2 + } +} +``` + +The namespace is made available as a global variable which has the namespace's name (e.g. `Utils`): + +``` +Utils.calculate() +``` + +The `using` keyword can be used to make all attributes in a namespace available to a script without having to +explicitly specify the namespace's name for each access: + +``` +using Utils +calculate() +``` + +The `using` keyword only has an effect for the current file and only for code that follows the keyword: + +``` +calculate() // This will not work. +using Utils +``` + +The following namespaces are automatically imported as if by using the `using` keyword: + +* System +* System.Configuration +* Types +* Icinga + +### Function Calls + +Functions can be called using the `()` operator: + +``` +const MyGroups = [ "test1", "test" ] + +{ + check_interval = len(MyGroups) * 1m +} +``` + +A list of available functions is available in the [Library Reference](18-library-reference.md#library-reference) chapter. + +## Assignments + +In addition to the `=` operator shown above a number of other operators +to manipulate attributes are supported. Here's a list of all +available operators (the outermost `{` `}` stand for a local variable scope): + +### Operator = + +Sets an attribute to the specified value. + +Example: + +``` +{ + a = 5 + a = 7 +} +``` + +In this example `a` has the value `7` after both instructions are executed. + +### Operator += + +The += operator is a shortcut. The following expression: + +``` +{ + a = [ "hello" ] + a += [ "world" ] +} +``` + +is equivalent to: + +``` +{ + a = [ "hello" ] + a = a + [ "world" ] +} +``` + +### Operator -= + +The -= operator is a shortcut. The following expression: + +``` +{ + a = 10 + a -= 5 +} +``` + +is equivalent to: + +``` +{ + a = 10 + a = a - 5 +} +``` + +### Operator \*= + +The *= operator is a shortcut. The following expression: + +``` +{ + a = 60 + a *= 5 +} +``` + +is equivalent to: + +``` +{ + a = 60 + a = a * 5 +} +``` + +### Operator /= + +The /= operator is a shortcut. The following expression: + +``` +{ + a = 300 + a /= 5 +} +``` + +is equivalent to: + +``` +{ + a = 300 + a = a / 5 +} +``` + +## Indexer + +The indexer syntax provides a convenient way to set dictionary elements. + +Example: + +``` +{ + hello.key = "world" +} +``` + +Example (alternative syntax): + +``` +{ + hello["key"] = "world" +} +``` + +This is equivalent to writing: + +``` +{ + hello += { + key = "world" + } +} +``` + +If the `hello` attribute does not already have a value, it is automatically initialized to an empty dictionary. + +## Template Imports + +Objects can import attributes from other objects. + +Example: + +``` +template Host "default-host" { + vars.colour = "red" +} + +template Host "test-host" { + import "default-host" + + vars.colour = "blue" +} + +object Host "localhost" { + import "test-host" + + address = "127.0.0.1" + address6 = "::1" +} +``` + +The `default-host` and `test-host` objects are marked as templates +using the `template` keyword. Unlike ordinary objects templates are not +instantiated at run-time. Parent objects do not necessarily have to be +templates, however in general they are. + +The `vars` dictionary for the `localhost` object contains all three +custom variables and the custom variable `colour` has the value `"blue"`. + +Parent objects are resolved in the order they're specified using the +`import` keyword. + +Default templates which are automatically imported into all object definitions +can be specified using the `default` keyword: + +``` +template CheckCommand "plugin-check-command" default { + // ... +} +``` + +Default templates are imported before any other user-specified statement in an +object definition is evaluated. + +If there are multiple default templates the order in which they are imported +is unspecified. + +## Constants + +Global constants can be set using the `const` keyword: + +``` +const VarName = "some value" +``` + +Once defined a constant can be accessed from any file. Constants cannot be changed +once they are set. + +> **Tip** +> +> Best practice is to manage constants in the [constants.conf](04-configuration.md#constants-conf) file. + +### Icinga 2 Specific Constants + +Icinga 2 provides a number of special global constants. These include directory paths, global configuration +and runtime parameters for the application version and (build) platform. + +#### Directory Path Constants + +Constant | Description +--------------------|------------------- +ConfigDir |**Read-only.** Main configuration directory. Usually set to `/etc/icinga2`. +DataDir |**Read-only.** Runtime data for the Icinga daemon. Usually set to `/var/lib/icinga2`. +LogDir |**Read-only.** Logfiles from the daemon. Usually set to `/var/log/icinga2`. +CacheDir |**Read-only.** Cached status information of the daemon. Usually set to `/var/cache/icinga2`. +SpoolDir |**Read-only.** Spool directory for certain data outputs. Usually set to `/var/spool/icinga2`. +InitRunDir |**Read-only.** Directory for PID files and sockets in daemon mode. Usually set to `/run/icinga2`. +ZonesDir |**Read-only.** Contains the path of the zones.d directory. Defaults to `ConfigDir + "/zones.d"`. + +#### Global Configuration Constants + +Constant | Description +--------------------|------------------- +Vars |**Read-write.** Contains a dictionary with global custom variables. Not set by default. +NodeName |**Read-write.** Contains the cluster node name. Set to the local hostname by default. +ReloadTimeout |**Read-write.** Defines the reload timeout for child processes. Defaults to `300s`. +Environment |**Read-write.** The name of the Icinga environment. Included in the SNI host name for outbound connections. Not set by default. +RunAsUser |**Read-write.** Defines the user the Icinga 2 daemon is running as. Set in the Icinga 2 sysconfig. +RunAsGroup |**Read-write.** Defines the group the Icinga 2 daemon is running as. Set in the Icinga 2 sysconfig. +MaxConcurrentChecks |**Read-write.** The number of max checks run simultaneously. Defaults to `512`. +ApiBindHost |**Read-write.** Overrides the default value for the ApiListener `bind_host` attribute. Defaults to `::` if IPv6 is supported by the operating system and to `0.0.0.0` otherwise. +ApiBindPort |**Read-write.** Overrides the default value for the ApiListener `bind_port` attribute. Not set by default. + +#### Application Runtime Constants + +Constant | Description +--------------------|------------------- +PlatformName |**Read-only.** The name of the operating system, e.g. `Ubuntu`. +PlatformVersion |**Read-only.** The version of the operating system, e.g. `14.04.3 LTS`. +PlatformKernel |**Read-only.** The name of the operating system kernel, e.g. `Linux`. +PlatformKernelVersion|**Read-only.** The version of the operating system kernel, e.g. `3.13.0-63-generic`. +BuildCompilerName |**Read-only.** The name of the compiler Icinga was built with, e.g. `Clang`. +BuildCompilerVersion|**Read-only.** The version of the compiler Icinga was built with, e.g. `7.3.0.7030031`. +BuildHostName |**Read-only.** The name of the host Icinga was built on, e.g. `acheron`. +ApplicationVersion |**Read-only.** The application version, e.g. `2.9.0`. + +#### Additional Constants + +Writable constants can be specified on the CLI using the `--define/-D` parameter. + +> **Note for v2.10+** +> +> Default paths which include `/etc` and `/var` as base directory continue to work +> based on the `SysconfDir` and `LocalStateDir` constants respectively. + +In addition to that, the constants below are used to define specific file paths. You should never need +to change them, as they are pre-compiled based on the constants above. + +Variable |Description +--------------------|------------------- +StatePath |**Read-write.** Contains the path of the Icinga 2 state file. Defaults to `DataDir + "/icinga2.state"`. +ObjectsPath |**Read-write.** Contains the path of the Icinga 2 objects file. Defaults to `CacheDir + "/icinga2.debug"`. +PidPath |**Read-write.** Contains the path of the Icinga 2 PID file. Defaults to `InitRunDir + "/icinga2.pid"`. +PkgDataDir |**Read-only.** Contains the path of the package data directory. Defaults to `PrefixDir + "/share/icinga2"`. + +The constants below have been used until Icinga v2.10, and are still intact. You don't need them +for future builds and configuration based on the newly available constants above. + +Variable |Description +--------------------|------------------- +PrefixDir |**Read-only.** Contains the installation prefix that was specified with `cmake -DCMAKE_INSTALL_PREFIX`. `Defaults to "/usr/local"`. +SysconfDir |**Read-only.** Contains the path of the sysconf directory. Defaults to `PrefixDir + "/etc"`. +LocalStateDir |**Read-only.** Contains the path of the local state directory. Defaults to `PrefixDir + "/var"`. +RunDir |**Read-only.** Contains the path of the run directory. Defaults to `LocalStateDir + "/run"`. + +#### Advanced Constants and Variables + +Advanced runtime constants. Please only use them if advised by support or developers. + +Variable | Description +---------------------------|------------------- +EventEngine |**Read-write.** The name of the socket event engine, can be `poll` or `epoll`. The epoll interface is only supported on Linux. +AttachDebugger |**Read-write.** Whether to attach a debugger when Icinga 2 crashes. Defaults to `false`. + +Advanced sysconfig environment variables, defined in `/etc/sysconfig/icinga2` (RHEL/SLES) or `/etc/default/icinga2` (Debian/Ubuntu). + +Variable | Description +---------------------------|------------------- +ICINGA2\_RLIMIT\_FILES |**Read-write.** Defines the resource limit for `RLIMIT_NOFILE` that should be set at start-up. Value cannot be set lower than the default `16 * 1024`. 0 disables the setting. Set in Icinga 2 sysconfig. +ICINGA2\_RLIMIT\_PROCESSES |**Read-write.** Defines the resource limit for `RLIMIT_NPROC` that should be set at start-up. Value cannot be set lower than the default `16 * 1024`. 0 disables the setting. Set in Icinga 2 sysconfig. +ICINGA2\_RLIMIT\_STACK |**Read-write.** Defines the resource limit for `RLIMIT_STACK` that should be set at start-up. Value cannot be set lower than the default `256 * 1024`. 0 disables the setting. Set in Icinga 2 sysconfig. + +#### Debug Constants and Variables + +These constants are only available in debug builds for developers and help with tracing messages and attaching to debuggers. + +Variable | Description +---------------------------|------------------- +Internal.DebugJsonRpc | **Read-write.** Setting this to `1` prints the raw JSON-RPC message to STDOUT. +Internal.DebugWorkerDelay | **Read-write.** Delays the main worker process by X seconds after forked from the umbrella process. This helps with attaching LLDB which cannot follow child forks like GDB. + +Example: + +``` +$ icinga2 daemon -DInternal.DebugWorkerDelay=120 +Closed FD 6 which we inherited from our parent process. +[2020-01-29 12:22:33 +0100] information/cli: Icinga application loader (version: v2.11.0-477-gfe8701d77; debug) +[2020-01-29 12:22:33 +0100] information/RunWorker: DEBUG: Current PID: 85253. Sleeping for 120 seconds to allow lldb/gdb -p attachment. + +$ lldb -p 85253 +(lldb) b icinga::Checkable::ProcessCheckResult +(lldb) c +``` + + +## Apply + +The `apply` keyword can be used to create new objects which are associated with +another group of objects. + +``` +apply Service "ping" to Host { + import "generic-service" + + check_command = "ping4" + + assign where host.name == "localhost" +} +``` + +In this example the `assign where` condition is a boolean expression which is +evaluated for all objects of type `Host` and a new service with name "ping" +is created for each matching host. [Expression operators](17-language-reference.md#expression-operators) +may be used in `assign where` conditions. + +The `to` keyword and the target type may be omitted if there is only one target +type, e.g. for the `Service` type. + +Depending on the object type used in the `apply` expression additional local +variables may be available for use in the `where` condition: + +Source Type | Target Type | Variables +------------------|-------------|-------------- +Service | Host | host +Dependency | Host | host +Dependency | Service | host, service +Notification | Host | host +Notification | Service | host, service +ScheduledDowntime | Host | host +ScheduledDowntime | Service | host, service + +Any valid config attribute can be accessed using the `host` and `service` +variables. For example, `host.address` would return the value of the host's +"address" attribute -- or null if that attribute isn't set. + +More usage examples are documented in the [monitoring basics](03-monitoring-basics.md#using-apply-expressions) +chapter. + +## Apply For + +[Apply](17-language-reference.md#apply) rules can be extended with the +[for loop](17-language-reference.md#for-loops) keyword. + +``` +apply Service "prefix-" for (key => value in host.vars.dictionary) to Host { + import "generic-service" + + check_command = "ping4" + vars.host_value = value +} +``` + +Any valid config attribute can be accessed using the `host` and `service` +variables. The attribute must be of the Array or Dictionary type. In this example +`host.vars.dictionary` is of the Dictionary type which needs a key-value-pair +as iterator. + +In this example all generated service object names consist of `prefix-` and +the value of the `key` iterator. The prefix string can be omitted if not required. + +The `key` and `value` variables can be used for object attribute assignment, e.g. for +setting the `check_command` attribute or custom variables as command parameters. + +`apply for` rules are first evaluated against all objects matching the `for loop` list +and afterwards the `assign where` and `ignore where` conditions are evaluated. + +It is not necessary to check attributes referenced in the `for loop` expression +for their existance using an additional `assign where` condition. + +More usage examples are documented in the [monitoring basics](03-monitoring-basics.md#using-apply-for) +chapter. + +## Group Assign + +Group objects can be assigned to specific member objects using the `assign where` +and `ignore where` conditions. + +``` +object HostGroup "linux-servers" { + display_name = "Linux Servers" + + assign where host.vars.os == "Linux" +} +``` + +In this example the `assign where` condition is a boolean expression which is evaluated +for all objects of the type `Host`. Each matching host is added as member to the host group +with the name "linux-servers". Membership exclusion can be controlled using the `ignore where` +condition. [Expression operators](17-language-reference.md#expression-operators) may be used in `assign where` and +`ignore where` conditions. + +Source Type | Variables +------------------|-------------- +HostGroup | host +ServiceGroup | host, service +UserGroup | user + + +## Boolean Values + +The `assign where`, `ignore where`, `if` and `while` statements, the `!` operator as +well as the `bool()` function convert their arguments to a boolean value based on the +following rules: + +Description | Example Value | Boolean Value +---------------------|-------------------|-------------- +Empty value | null | false +Zero | 0 | false +Non-zero integer | -23945 | true +Empty string | "" | false +Non-empty string | "Hello" | true +Empty array | [] | false +Non-empty array | [ "Hello" ] | true +Empty dictionary | {} | false +Non-empty dictionary | { key = "value" } | true + +For a list of supported expression operators for `assign where` and `ignore where` +statements, see [expression operators](17-language-reference.md#expression-operators). + +## Comments + +The Icinga 2 configuration format supports C/C++-style and shell-style comments. + +Example: + +``` +/* + This is a comment. + */ +object Host "localhost" { + check_interval = 30 // this is also a comment. + retry_interval = 15 # yet another comment +} +``` + +## Includes + +Other configuration files can be included using the `include` directive. +Paths must be relative to the configuration file that contains the +`include` directive. + +Example: + +``` +include "some/other/file.conf" +include "conf.d/*.conf" +``` + +Wildcard includes are not recursive. + +Icinga also supports include search paths similar to how they work in a +C/C++ compiler: + +``` +include +``` + +Note the use of angle brackets instead of double quotes. This causes the +config compiler to search the include search paths for the specified +file. By default $PREFIX/share/icinga2/include is included in the list of search +paths. Additional include search paths can be added using +[command-line options](11-cli-commands.md#config-include-path). + +Wildcards are not permitted when using angle brackets. + +## Recursive Includes + +The `include_recursive` directive can be used to recursively include all +files in a directory which match a certain pattern. + +Example: + +``` +include_recursive "conf.d", "*.conf" +include_recursive "templates" +``` + +The first parameter specifies the directory from which files should be +recursively included. + +The file names need to match the pattern given in the second parameter. +When no pattern is specified the default pattern "*.conf" is used. + +## Zone Includes + +> **Note** +> +> This is an internal functionality consumed by Icinga itself. +> +> The preferred way for users managing configuration files in +> zones is to use the [cluster config sync](06-distributed-monitoring.md#distributed-monitoring-top-down-config-sync) +> or [REST API config packages](12-icinga2-api.md#icinga2-api-config-management). + +The `include_zones` recursively includes all subdirectories for the +given path. + +In addition to that it sets the `zone` attribute for all objects created +in these subdirectories to the name of the subdirectory. + +Example: + +``` +include_zones "etc", "zones.d", "*.conf" +include_zones "puppet", "puppet-zones" +``` + +The first parameter specifies a tag name for this directive. Each `include_zones` +invocation should use a unique tag name. When copying the zones' configuration +files Icinga uses the tag name as the name for the destination directory in +`/var/lib/icinga2/api/config`. + +The second parameter specifies the directory which contains the subdirectories. + +The file names need to match the pattern given in the third parameter. +When no pattern is specified the default pattern "*.conf" is used. + +## Library directive + +The `library` directive was used to manually load additional +libraries. Starting with version 2.9 it is no longer necessary to explicitly load +libraries and this directive has no effect. + +## Functions + +Functions can be defined using the `function` keyword. + +Example: + +``` +function multiply(a, b) { + return a * b +} +``` + +When encountering the `return` keyword further execution of the function is terminated and +the specified value is supplied to the caller of the function: + +``` +log(multiply(3, 5)) +``` + +In this example the `multiply` function we declared earlier is invoked with two arguments (3 and 5). +The function computes the product of those arguments and makes the result available to the +function's caller. + +When no value is supplied for the `return` statement the function returns `null`. + +Functions which do not have a `return` statement have their return value set to the value of the +last expression which was performed by the function. For example, we could have also written our +`multiply` function like this: + +``` +function multiply(a, b) { + a * b +} +``` + +Anonymous functions can be created by omitting the name in the function definition. The +resulting function object can be used like any other value: + +``` +var fn = function() { 3 } + +fn() /* Returns 3 */ +``` + +## Lambda Expressions + +Functions can also be declared using the alternative lambda syntax. + +Example: + +``` +f = (x) => x * x +``` + +Multiple statements can be used by putting the function body into braces: + +``` +f = (x) => { + log("Lambda called") + x * x +} +``` + +Just like with ordinary functions the return value is the value of the last statement. + +For lambdas which take exactly one argument the braces around the arguments can be omitted: + +``` +f = x => x * x +``` + +### Lambda Expressions with Closures + +Lambda expressions which take a given number of arguments may need additional +variable values from the outer scope. When the lambda expression does not allow +to change the interface, [closures](17-language-reference.md#closures) come into play. + +``` +var y + +f = ((x) use(y) => x == y) +``` + +Note that the braces around arguments are always required when using closures. + +A more concrete example: + +Within the DSL, you want to [filter](18-library-reference.md#array-filter) an array of HostGroup objects by their name. +The filter function takes one argument being a function callback which either returns +`true` or `false`. Matching items are collected into the result set. + +``` +get_objects(HostGroup).filter((hg) => hg.name == "linux-servers") +``` + +Instead of hardcoding the matching hostgroup name into the lambda scope, you want +to control the value from the outside configuration values, e.g. in a custom variable +or global constant. + +``` +var hg_filter_name = "linux-servers" + +get_objects(HostGroup).filter((hg) use(hg_filter_name) => hg.name == hg_filter_name) +``` + +You can also use this example vice versa and extract host object matching a specific +host group name. + +``` +var hg_filter_name = "linux-servers" + +get_objects(Host).filter((h) use (hg_search_name) => hg_search_name in h.groups).map(h => h.name) +``` + +Note that this example makes use of the [map](18-library-reference.md#array-map) method for the Array type which +extracts the host name attribute from the full object into a new array. + +## Abbreviated Lambda Syntax + +Lambdas which take no arguments can also be written using the abbreviated lambda syntax. + +Example: + +``` +f = {{ 3 }} +``` + +This creates a new function which returns the value 3. + +## Variable Scopes + +When setting a variable Icinga checks the following scopes in this order whether the variable +already exists there: + +* Local Scope +* `this` Scope +* Global Scope + +The local scope contains variables which only exist during the invocation of the current function, +object or apply statement. Local variables can be declared using the `var` keyword: + +``` +function multiply(a, b) { + var temp = a * b + return temp +} +``` + +Each time the `multiply` function is invoked a new `temp` variable is used which is in no way +related to previous invocations of the function. + +When setting a variable which has not previously been declared as local using the `var` keyword +the `this` scope is used. + +The `this` scope refers to the current object which the function or object/apply statement +operates on. + +``` +object Host "localhost" { + check_interval = 5m +} +``` + +In this example the `this` scope refers to the "localhost" object. The `check_interval` attribute +is set for this particular host. + +You can explicitly access the `this` scope using the `this` keyword: + +``` +object Host "localhost" { + var check_interval = 5m + + /* This explicitly specifies that the attribute should be set + * for the host, if we had omitted `this.` the (poorly named) + * local variable `check_interval` would have been modified instead. + */ + this.check_interval = 1m +} +``` +Similarly the keywords `locals` and `globals` are available to access the local and global scope. + +Functions also have a `this` scope. However unlike for object/apply statements the `this` scope for +a function is set to whichever object was used to invoke the function. Here's an example: + +``` + hm = { + h_word = null + + function init(word) { + h_word = word + } + } + + /* Let's invoke the init() function */ + hm.init("hello") +``` + +We're using `hm.init` to invoke the function which causes the value of `hm` to become the `this` +scope for this function call. + +## Closures + +By default `function`s, `object`s and `apply` rules do not have access to variables declared +outside of their scope (except for global variables). + +In order to access variables which are defined in the outer scope the `use` keyword can be used: + +``` +function MakeHelloFunction(name) { + return function() use(name) { + log("Hello, " + name) + } +} +``` + +In this case a new variable `name` is created inside the inner function's scope which has the +value of the `name` function argument. + +Alternatively a different value for the inner variable can be specified: + +``` +function MakeHelloFunction(name) { + return function() use (greeting = "Hello, " + name) { + log(greeting) + } +} +``` + +## Conditional Statements + +### Conditional Statements: if/else + +Sometimes it can be desirable to only evaluate statements when certain conditions are met. The if/else +construct can be used to accomplish this. + +Example: + +``` +a = 3 + +if (a < 5) { + a *= 7 +} else if (a > 10) { + a *= 5 +} else { + a *= 2 +} +``` + +An if/else construct can also be used in place of any other value. The value of an if/else statement +is the value of the last statement which was evaluated for the branch which was taken: + +``` +a = if (true) { + log("Taking the 'true' branch") + 7 * 3 +} else { + log("Taking the 'false' branch") + 9 +} +``` + +This example prints the log message "Taking the 'true' branch" and the `a` variable is set to 21 (7 * 3). + +The value of an if/else construct is null if the condition evaluates to false and no else branch is given. + +### Conditional Statements: Ternary Operator + +Instead of if/else condition chains, you can also use the ternary operator `?` +with assignments. Values are separated with a colon `:` character. + +``` +cond ? cond_val_true : cond_val_false +``` + +Whether the first condition matches, the first value is returned, if not, the else and second +branch value is returned. + +The following example evaluates a condition and either assigns `1` or `0` +to the local variable. + +``` +<1> => var x = (2 * 3 > 5) ? 1 : 0 +null +<2> => x +1.000000 +<3> => var x = (2 * 3 > 7) ? 1 : 0 +null +<4> => x +0.000000 +``` + +Additional examples with advanced condition chaining: + +``` +<1> => 1 ? 2 : 3 ? 4 : 5 ? 6 : 7 +2.000000 +<2> => 0 ? 2 : 3 ? 4 : 5 ? 6 : 7 +4.000000 +<3> => 0 ? 2 : 0 ? 4 : 5 ? 6 : 7 +6.000000 +<4> => 0 ? 2 : 0 ? 4 : 0 ? 6 : 7 +7.000000 +<5> => 1 + 0 ? 2 : 3 + 4 +2.000000 +<6> => 0 + 0 ? 2 : 3 + 4 +7.000000 +<7> => (()=>{ return 1 ? 2 : 3 })() +2.000000 +<8> => var x = 1 ? 2 : 3 +null +<9> => x +2.000000 +``` + + +## While Loops + +The `while` statement checks a condition and executes the loop body when the condition evaluates to `true`. +This is repeated until the condition is no longer true. + +Example: + +``` +var num = 5 + +while (num > 5) { + log("Test") + num -= 1 +} +``` + +The `continue` and `break` keywords can be used to control how the loop is executed: The `continue` keyword +skips over the remaining expressions for the loop body and begins the next loop evaluation. The `break` keyword +breaks out of the loop. + +## For Loops + +The `for` statement can be used to iterate over arrays and dictionaries. + +Example: + +``` +var list = [ "a", "b", "c" ] + +for (var item in list) { + log("Item: " + item) +} +``` + +The loop body is evaluated once for each item in the array. The variable `item` is declared as a local +variable just as if the `var` keyword had been used. + +Iterating over dictionaries can be accomplished in a similar manner: + +``` +var dict = { a = 3, b = 7 } + +for (var key => var value in dict) { + log("Key: " + key + ", Value: " + value) +} +``` + +The `continue` and `break` keywords can be used to control how the loop is executed: The `continue` keyword +skips over the remaining expressions for the loop body and begins the next loop evaluation. The `break` keyword +breaks out of the loop. + +The `var` keyword is optional when declaring variables in the loop's header. Variables declared without the `var` +keyword are nonetheless local to the function. + +## Constructors + +In order to create a new value of a specific type constructor calls may be used. + +Example: + +``` +var pd = PerfdataValue() +pd.label = "test" +pd.value = 10 +``` + +You can also try to convert an existing value to another type by specifying it as an argument for the constructor call. + +Example: + +``` +var s = String(3) /* Sets s to "3". */ +``` + +## Throwing Exceptions + +Built-in commands may throw exceptions to signal errors such as invalid arguments. User scripts can throw exceptions +using the `throw` keyword. + +Example: + +``` +throw "An error occurred." +``` + +## Handling Exceptions + +Exceptions can be handled using the `try` and `except` keywords. When an exception occurs while executing code in the +`try` clause no further statements in the `try` clause are evaluated and the `except` clause is executed instead. + +Example: + +``` +try { + throw "Test" + + log("This statement won't get executed.") +} except { + log("An error occurred in the try clause.") +} +``` + +## Breakpoints + +The `debugger` keyword can be used to insert a breakpoint. It may be used at any place where an assignment would also be a valid expression. + +By default breakpoints have no effect unless Icinga is started with the `--script-debugger` command-line option. When the script debugger is enabled Icinga stops execution of the script when it encounters a breakpoint and spawns a console which lets the user inspect the current state of the execution environment. + +## Types + +All values have a static type. The `typeof` function can be used to determine the type of a value: + +``` +typeof(3) /* Returns an object which represents the type for numbers */ +``` + +The following built-in types are available: + +Type | Examples | Description +-----------|-------------------|------------------------ +Number | 3.7 | A numerical value. +Boolean | true, false | A boolean value. +String | "hello" | A string. +Array | [ "a", "b" ] | An array. +Dictionary | { a = 3 } | A dictionary. + +Depending on which libraries are loaded additional types may become available. The `icinga` +library implements a whole bunch of other [object types](09-object-types.md#object-types), +e.g. Host, Service, CheckCommand, etc. + +Each type has an associated type object which describes the type's semantics. These +type objects are made available using global variables which match the type's name: + +``` +/* This logs 'true' */ +log(typeof(3) == Number) +``` + +The type object's `prototype` property can be used to find out which methods a certain type +supports: + +``` +/* This returns: ["contains","find","len","lower","replace","reverse","split","substr","to_string","trim","upper"] */ +keys(String.prototype) +``` + +Additional documentation on type methods is available in the +[library reference](18-library-reference.md#library-reference). + +## Location Information + +The location of the currently executing script can be obtained using the +`current_filename` and `current_line` keywords. + +Example: + +``` +log("Hello from '" + current_filename + "' in line " + current_line) +``` + +## Reserved Keywords + +These keywords are reserved and must not be used as constants or custom variables. + +``` +object +template +include +include_recursive +include_zones +library +null +true +false +const +var +this +globals +locals +use +default +ignore_on_error +current_filename +current_line +apply +to +where +import +assign +ignore +function +return +break +continue +for +if +else +while +throw +try +except +in +using +namespace +``` +You can escape reserved keywords using the `@` character. The following example +tries to set `vars.include` which references a reserved keyword and generates +an error: + +``` +[2014-09-15 17:24:00 +0200] critical/config: Location: +/etc/icinga2/conf.d/hosts/localhost.conf(13): vars.sla = "24x7" +/etc/icinga2/conf.d/hosts/localhost.conf(14): +/etc/icinga2/conf.d/hosts/localhost.conf(15): vars.include = "some cmdb export field" + ^^^^^^^ +/etc/icinga2/conf.d/hosts/localhost.conf(16): } +/etc/icinga2/conf.d/hosts/localhost.conf(17): + +Config error: in /etc/icinga2/conf.d/hosts/localhost.conf: 15:8-15:14: syntax error, unexpected include (T_INCLUDE), expecting T_IDENTIFIER +[2014-09-15 17:24:00 +0200] critical/config: 1 errors, 0 warnings. +``` + +You can escape the `include` keyword by prefixing it with an additional `@` character: + +``` +object Host "localhost" { + import "generic-host" + + address = "127.0.0.1" + address6 = "::1" + + vars.os = "Linux" + vars.sla = "24x7" + + vars.@include = "some cmdb export field" +} +``` -- cgit v1.2.3