From: Michael Friedrich Date: Tue, 6 Feb 2018 11:44:45 +0000 (+0100) Subject: Explain how to register functions in the global scope X-Git-Tag: v2.8.2~48 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=7f09fb1f659b434c0afbdfbe1e7f364de7fec483;p=icinga2 Explain how to register functions in the global scope refs #6075 --- diff --git a/doc/08-advanced-topics.md b/doc/08-advanced-topics.md index b0c2b79a0..c92f7a703 100644 --- a/doc/08-advanced-topics.md +++ b/doc/08-advanced-topics.md @@ -645,138 +645,86 @@ inside the `icinga2.log` file depending in your log severity * Use the `icinga2 console` to test basic functionality (e.g. iterating over a dictionary) * Build them step-by-step. You can always refactor your code later on. -#### Use Functions in Command Arguments set_if - -The `set_if` attribute inside the command arguments definition in the -[CheckCommand object definition](09-object-types.md#objecttype-checkcommand) is primarily used to -evaluate whether the command parameter should be set or not. - -By default you can evaluate runtime macros for their existence. If the result is not an empty -string, the command parameter is passed. This becomes fairly complicated when want to evaluate -multiple conditions and attributes. +#### Register and Use Global Functions -The following example was found on the community support channels. The user had defined a host -dictionary named `compellent` with the key `disks`. This was then used inside service apply for rules. - - object Host "dict-host" { - check_command = "check_compellent" - vars.compellent["disks"] = { - file = "/var/lib/check_compellent/san_disks.0.json", - checks = ["disks"] - } - } - -The more significant problem was to only add the command parameter `--disk` to the plugin call -when the dictionary `compellent` contains the key `disks`, and omit it if not found. - -By defining `set_if` as [abbreviated lambda function](17-language-reference.md#nullary-lambdas) -and evaluating the host custom attribute `compellent` containing the `disks` this problem was -solved like this: - - object CheckCommand "check_compellent" { - command = [ "/usr/bin/check_compellent" ] - arguments = { - "--disks" = { - set_if = {{ - var host_vars = host.vars - log(host_vars) - var compel = host_vars.compellent - log(compel) - compel.contains("disks") - }} - } - } - } +[Functions](17-language-reference.md#functions) can be registered into the global scope. This allows custom functions being available +in objects and other functions. Keep in mind that these functions are not marked +as side-effect-free and as such are not available via the REST API. -This implementation uses the dictionary type method [contains](18-library-reference.md#dictionary-contains) -and will fail if `host.vars.compellent` is not of the type `Dictionary`. -Therefore you can extend the checks using the [typeof](17-language-reference.md#types) function. +Add a new configuration file `functions.conf` and include it into the [icinga2.conf](04-configuring-icinga-2.md#icinga2-conf) +configuration file in the very beginning, e.g. after `constants.conf`. You can also manage global +functions inside `constants.conf` if you prefer. -You can test the types using the `icinga2 console`: +The following function converts a given state parameter into a returned string value. The important +bits for registering it into the global scope are: - # icinga2 console - Icinga (version: v2.3.0-193-g3eb55ad) - <1> => srv_vars.compellent["check_a"] = { file="outfile_a.json", checks = [ "disks", "fans" ] } - null - <2> => srv_vars.compellent["check_b"] = { file="outfile_b.json", checks = [ "power", "voltages" ] } - null - <3> => typeof(srv_vars.compellent) - type 'Dictionary' - <4> => +* `globals.` adds a new globals entry. +* `function()` specifies that a call to `state_to_string()` executes a function. +* Function parameters are defined inside the `function()` definition. -The more programmatic approach for `set_if` could look like this: +``` +globals.state_to_string = function(state) { + if (state == 2) { + return "Critical" + } else if (state == 1) { + return "Warning" + } else if (state == 0) { + return "OK" + } else if (state == 3) { + return "Unknown" + } else { + log(LogWarning, "state_to_string", "Unknown state " + state + " provided.") + } +} +``` - "--disks" = { - set_if = {{ - var srv_vars = service.vars - if(len(srv_vars) > 0) { - if (typeof(srv_vars.compellent) == Dictionary) { - return srv_vars.compellent.contains("disks") - } else { - log(LogInformationen, "checkcommand set_if", "custom attribute compellent_checks is not a dictionary, ignoring it.") - return false - } - } else { - log(LogWarning, "checkcommand set_if", "empty custom attributes") - return false - } - }} - } +The else-condition allows for better error handling. This warning will be shown in the Icinga 2 +log file once the function is called. +> **Note** +> +> If these functions are used in a distributed environment, you must ensure to deploy them +> everywhere needed. -#### Use Functions as Command Attribute +In order to test-drive the newly created function, restart Icinga 2 and use the [debug console](11-cli-commands.md#cli-command-console) +to connect to the REST API. -This comes in handy for [NotificationCommands](09-object-types.md#objecttype-notificationcommand) -or [EventCommands](09-object-types.md#objecttype-eventcommand) which does not require -a returned checkresult including state/output. +``` +$ ICINGA2_API_PASSWORD=icinga icinga2 console --connect 'https://root@localhost:5665/' +Icinga 2 (version: v2.8.1-373-g4bea6d25c) +<1> => globals.state_to_string(1) +"Warning" +<2> => state_to_string(2) +"Critical" +``` -The following example was taken from the community support channels. The requirement was to -specify a custom attribute inside the notification apply rule and decide which notification -script to call based on that. +You can see that this function is now registered into the [global scope](17-language-reference.md#variable-scopes). The function call +`state_to_string()` can be used in any object at static config compile time or inside runtime +lambda functions. - object User "short-dummy" { - } +The following service object example uses the service state and converts it to string output. +The function definition is not optimized and is enrolled for better readability including a log message. - object UserGroup "short-dummy-group" { - assign where user.name == "short-dummy" - } +``` +object Service "state-test" { + check_command = "dummy" + host_name = NodeName - apply Notification "mail-admins-short" to Host { - import "mail-host-notification" - command = "mail-host-notification-test" - user_groups = [ "short-dummy-group" ] - vars.short = true - assign where host.vars.notification.mail - } + vars.dummy_state = 2 -The solution is fairly simple: The `command` attribute is implemented as function returning -an array required by the caller Icinga 2. -The local variable `mailscript` sets the default value for the notification scrip location. -If the notification custom attribute `short` is set, it will override the local variable `mailscript` -with a new value. -The `mailscript` variable is then used to compute the final notification command array being -returned. + vars.dummy_text = {{ + var h = macro("$host.name$") + var s = macro("$service.name$") -You can omit the `log()` calls, they only help debugging. + var state = get_service(h, s).state - object NotificationCommand "mail-host-notification-test" { - command = {{ - log("command as function") - var mailscript = "mail-host-notification-long.sh" - if (notification.vars.short) { - mailscript = "mail-host-notification-short.sh" - } - log("Running command") - log(mailscript) + log(LogInformation, "dummy_state", "Host: " + h + " Service: " + s + " State: " + state) - var cmd = [ SysconfDir + "/icinga2/scripts/" + mailscript ] - log(LogCritical, "me", cmd) - return cmd - }} + return state_to_string(state) + }} +} +``` - env = { - } - } #### Use Custom Functions as Attribute @@ -894,6 +842,141 @@ with the `vars_app` dictionary. assign where check_app_type(host, "ABAP") } + +#### Use Functions in Command Arguments set_if + +The `set_if` attribute inside the command arguments definition in the +[CheckCommand object definition](09-object-types.md#objecttype-checkcommand) is primarily used to +evaluate whether the command parameter should be set or not. + +By default you can evaluate runtime macros for their existence. If the result is not an empty +string, the command parameter is passed. This becomes fairly complicated when want to evaluate +multiple conditions and attributes. + +The following example was found on the community support channels. The user had defined a host +dictionary named `compellent` with the key `disks`. This was then used inside service apply for rules. + + object Host "dict-host" { + check_command = "check_compellent" + vars.compellent["disks"] = { + file = "/var/lib/check_compellent/san_disks.0.json", + checks = ["disks"] + } + } + +The more significant problem was to only add the command parameter `--disk` to the plugin call +when the dictionary `compellent` contains the key `disks`, and omit it if not found. + +By defining `set_if` as [abbreviated lambda function](17-language-reference.md#nullary-lambdas) +and evaluating the host custom attribute `compellent` containing the `disks` this problem was +solved like this: + + object CheckCommand "check_compellent" { + command = [ "/usr/bin/check_compellent" ] + arguments = { + "--disks" = { + set_if = {{ + var host_vars = host.vars + log(host_vars) + var compel = host_vars.compellent + log(compel) + compel.contains("disks") + }} + } + } + } + +This implementation uses the dictionary type method [contains](18-library-reference.md#dictionary-contains) +and will fail if `host.vars.compellent` is not of the type `Dictionary`. +Therefore you can extend the checks using the [typeof](17-language-reference.md#types) function. + +You can test the types using the `icinga2 console`: + + # icinga2 console + Icinga (version: v2.3.0-193-g3eb55ad) + <1> => srv_vars.compellent["check_a"] = { file="outfile_a.json", checks = [ "disks", "fans" ] } + null + <2> => srv_vars.compellent["check_b"] = { file="outfile_b.json", checks = [ "power", "voltages" ] } + null + <3> => typeof(srv_vars.compellent) + type 'Dictionary' + <4> => + +The more programmatic approach for `set_if` could look like this: + + "--disks" = { + set_if = {{ + var srv_vars = service.vars + if(len(srv_vars) > 0) { + if (typeof(srv_vars.compellent) == Dictionary) { + return srv_vars.compellent.contains("disks") + } else { + log(LogInformationen, "checkcommand set_if", "custom attribute compellent_checks is not a dictionary, ignoring it.") + return false + } + } else { + log(LogWarning, "checkcommand set_if", "empty custom attributes") + return false + } + }} + } + + +#### Use Functions as Command Attribute + +This comes in handy for [NotificationCommands](09-object-types.md#objecttype-notificationcommand) +or [EventCommands](09-object-types.md#objecttype-eventcommand) which does not require +a returned checkresult including state/output. + +The following example was taken from the community support channels. The requirement was to +specify a custom attribute inside the notification apply rule and decide which notification +script to call based on that. + + object User "short-dummy" { + } + + object UserGroup "short-dummy-group" { + assign where user.name == "short-dummy" + } + + apply Notification "mail-admins-short" to Host { + import "mail-host-notification" + command = "mail-host-notification-test" + user_groups = [ "short-dummy-group" ] + vars.short = true + assign where host.vars.notification.mail + } + +The solution is fairly simple: The `command` attribute is implemented as function returning +an array required by the caller Icinga 2. +The local variable `mailscript` sets the default value for the notification scrip location. +If the notification custom attribute `short` is set, it will override the local variable `mailscript` +with a new value. +The `mailscript` variable is then used to compute the final notification command array being +returned. + +You can omit the `log()` calls, they only help debugging. + + object NotificationCommand "mail-host-notification-test" { + command = {{ + log("command as function") + var mailscript = "mail-host-notification-long.sh" + if (notification.vars.short) { + mailscript = "mail-host-notification-short.sh" + } + log("Running command") + log(mailscript) + + var cmd = [ SysconfDir + "/icinga2/scripts/" + mailscript ] + log(LogCritical, "me", cmd) + return cmd + }} + + env = { + } + } + + ### Access Object Attributes at Runtime The [Object Accessor Functions](18-library-reference.md#object-accessor-functions)