From 8dd8af051216d02c740055b145bb64fdb118b2bd Mon Sep 17 00:00:00 2001 From: Michael Friedrich Date: Tue, 6 Feb 2018 12:44:45 +0100 Subject: [PATCH] Explain how to register functions in the global scope --- doc/08-advanced-topics.md | 313 ++++++++++++++++++++++++-------------- 1 file changed, 198 insertions(+), 115 deletions(-) diff --git a/doc/08-advanced-topics.md b/doc/08-advanced-topics.md index 3afa7bef2..cd1f68119 100644 --- a/doc/08-advanced-topics.md +++ b/doc/08-advanced-topics.md @@ -650,6 +650,204 @@ 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. +#### Register and Use Global Functions + +[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. + +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. + +The following function converts a given state parameter into a returned string value. The important +bits for registering it into the global scope are: + +* `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. + +``` +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.") + } +} +``` + +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. + +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. + +``` +$ 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" +``` + +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. + +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 Service "state-test" { + check_command = "dummy" + host_name = NodeName + + vars.dummy_state = 2 + + vars.dummy_text = {{ + var h = macro("$host.name$") + var s = macro("$service.name$") + + var state = get_service(h, s).state + + log(LogInformation, "dummy_state", "Host: " + h + " Service: " + s + " State: " + state) + + return state_to_string(state) + }} +} +``` + + +#### Use Custom Functions as Attribute + +To use custom functions as attributes, the function must be defined in a +slightly unexpected way. The following example shows how to assign values +depending on group membership. All hosts in the `slow-lan` host group use 300 +as value for `ping_wrta`, all other hosts use 100. + + globals.group_specific_value = function(group, group_value, non_group_value) { + return function() use (group, group_value, non_group_value) { + if (group in host.groups) { + return group_value + } else { + return non_group_value + } + } + } + + apply Service "ping4" { + import "generic-service" + check_command = "ping4" + + vars.ping_wrta = group_specific_value("slow-lan", 300, 100) + vars.ping_crta = group_specific_value("slow-lan", 500, 200) + + assign where true + } + +#### Use Functions in Assign Where Expressions + +If a simple expression for matching a name or checking if an item +exists in an array or dictionary does not fit, you should consider +writing your own global [functions](17-language-reference.md#functions). +You can call them inside `assign where` and `ignore where` expressions +for [apply rules](03-monitoring-basics.md#using-apply-expressions) or +[group assignments](03-monitoring-basics.md#group-assign-intro) just like +any other global functions for example [match](18-library-reference.md#global-functions-match). + +The following example requires the host `myprinter` being added +to the host group `printers-lexmark` but only if the host uses +a template matching the name `lexmark*`. + + template Host "lexmark-printer-host" { + vars.printer_type = "Lexmark" + } + + object Host "myprinter" { + import "generic-host" + import "lexmark-printer-host" + + address = "192.168.1.1" + } + + /* register a global function for the assign where call */ + globals.check_host_templates = function(host, search) { + /* iterate over all host templates and check if the search matches */ + for (tmpl in host.templates) { + if (match(search, tmpl)) { + return true + } + } + + /* nothing matched */ + return false + } + + object HostGroup "printers-lexmark" { + display_name = "Lexmark Printers" + /* call the global function and pass the arguments */ + assign where check_host_templates(host, "lexmark*") + } + + +Take a different more complex example: All hosts with the +custom attribute `vars_app` as nested dictionary should be +added to the host group `ABAP-app-server`. But only if the +`app_type` for all entries is set to `ABAP`. + +It could read as wildcard match for nested dictionaries: + + where host.vars.vars_app["*"].app_type == "ABAP" + +The solution for this problem is to register a global +function which checks the `app_type` for all hosts +with the `vars_app` dictionary. + + object Host "appserver01" { + check_command = "dummy" + vars.vars_app["ABC"] = { app_type = "ABAP" } + } + object Host "appserver02" { + check_command = "dummy" + vars.vars_app["DEF"] = { app_type = "ABAP" } + } + + globals.check_app_type = function(host, type) { + /* ensure that other hosts without the custom attribute do not match */ + if (typeof(host.vars.vars_app) != Dictionary) { + return false + } + + /* iterate over the vars_app dictionary */ + for (key => val in host.vars.vars_app) { + /* if the value is a dictionary and if contains the app_type being the requested type */ + if (typeof(val) == Dictionary && val.app_type == type) { + return true + } + } + + /* nothing matched */ + return false + } + + object HostGroup "ABAP-app-server" { + 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 @@ -783,121 +981,6 @@ You can omit the `log()` calls, they only help debugging. } } -#### Use Custom Functions as Attribute - -To use custom functions as attributes, the function must be defined in a -slightly unexpected way. The following example shows how to assign values -depending on group membership. All hosts in the `slow-lan` host group use 300 -as value for `ping_wrta`, all other hosts use 100. - - globals.group_specific_value = function(group, group_value, non_group_value) { - return function() use (group, group_value, non_group_value) { - if (group in host.groups) { - return group_value - } else { - return non_group_value - } - } - } - - apply Service "ping4" { - import "generic-service" - check_command = "ping4" - - vars.ping_wrta = group_specific_value("slow-lan", 300, 100) - vars.ping_crta = group_specific_value("slow-lan", 500, 200) - - assign where true - } - -#### Use Functions in Assign Where Expressions - -If a simple expression for matching a name or checking if an item -exists in an array or dictionary does not fit, you should consider -writing your own global [functions](17-language-reference.md#functions). -You can call them inside `assign where` and `ignore where` expressions -for [apply rules](03-monitoring-basics.md#using-apply-expressions) or -[group assignments](03-monitoring-basics.md#group-assign-intro) just like -any other global functions for example [match](18-library-reference.md#global-functions-match). - -The following example requires the host `myprinter` being added -to the host group `printers-lexmark` but only if the host uses -a template matching the name `lexmark*`. - - template Host "lexmark-printer-host" { - vars.printer_type = "Lexmark" - } - - object Host "myprinter" { - import "generic-host" - import "lexmark-printer-host" - - address = "192.168.1.1" - } - - /* register a global function for the assign where call */ - globals.check_host_templates = function(host, search) { - /* iterate over all host templates and check if the search matches */ - for (tmpl in host.templates) { - if (match(search, tmpl)) { - return true - } - } - - /* nothing matched */ - return false - } - - object HostGroup "printers-lexmark" { - display_name = "Lexmark Printers" - /* call the global function and pass the arguments */ - assign where check_host_templates(host, "lexmark*") - } - - -Take a different more complex example: All hosts with the -custom attribute `vars_app` as nested dictionary should be -added to the host group `ABAP-app-server`. But only if the -`app_type` for all entries is set to `ABAP`. - -It could read as wildcard match for nested dictionaries: - - where host.vars.vars_app["*"].app_type == "ABAP" - -The solution for this problem is to register a global -function which checks the `app_type` for all hosts -with the `vars_app` dictionary. - - object Host "appserver01" { - check_command = "dummy" - vars.vars_app["ABC"] = { app_type = "ABAP" } - } - object Host "appserver02" { - check_command = "dummy" - vars.vars_app["DEF"] = { app_type = "ABAP" } - } - - globals.check_app_type = function(host, type) { - /* ensure that other hosts without the custom attribute do not match */ - if (typeof(host.vars.vars_app) != Dictionary) { - return false - } - - /* iterate over the vars_app dictionary */ - for (key => val in host.vars.vars_app) { - /* if the value is a dictionary and if contains the app_type being the requested type */ - if (typeof(val) == Dictionary && val.app_type == type) { - return true - } - } - - /* nothing matched */ - return false - } - - object HostGroup "ABAP-app-server" { - assign where check_app_type(host, "ABAP") - } ### Access Object Attributes at Runtime