]> granicus.if.org Git - icinga2/commitdiff
Explain how to register functions in the global scope
authorMichael Friedrich <michael.friedrich@icinga.com>
Tue, 6 Feb 2018 11:44:45 +0000 (12:44 +0100)
committerMichael Friedrich <michael.friedrich@netways.de>
Tue, 6 Feb 2018 12:31:47 +0000 (13:31 +0100)
refs #6075

doc/08-advanced-topics.md

index b0c2b79a033bb4f11466fb9f0fd0baecf11bc35f..c92f7a703225a54c2afef7fff054e1388b89af0e 100644 (file)
@@ -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 <a id="use-functions-command-arguments-setif"></a>
-
-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 <a id="use-functions-global-register"></a>
 
-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.<unique_function_name>` 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 <a id="use-functions-command-attribute"></a>
+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 <a id="custom-functions-as-attribute"></a>
 
@@ -894,6 +842,141 @@ with the `vars_app` dictionary.
       assign where check_app_type(host, "ABAP")
     }
 
+
+#### Use Functions in Command Arguments set_if <a id="use-functions-command-arguments-setif"></a>
+
+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 <a id="use-functions-command-attribute"></a>
+
+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 <a id="access-object-attributes-at-runtime"></a>
 
 The [Object Accessor Functions](18-library-reference.md#object-accessor-functions)