DSL: Flatten a nested dictionary with a function for apply for rules

Author: @mfriedrich
Version: v0.1

Tested with: Icinga 2 v2.10.5

Question

Custom facts for database would include nested information.

    1. Level: production or staging
    1. Level: db or app name
    1. Level: additional configuration

Example:

  vars.mysql_databases["production"] = {
    "app1" = {
      "port" = 3306
    },
    "app2" = {
     "port" = 3307
    }
  }
  vars.mysql_databases["staging"] = {
    "app1" = {
      "port" = 3306
    },
    "app2" = {
      "port" = 3307
    }
  }

Apply for needs 2 levels where the key is a unique name for the service object being generated.

The idea is to flatten the Dictionary to the 2nd level and use a key like this: production-app1 or staging-app2.

  "staging-app1" = { 
    port = 3306
  }
  "staging-app7" = { 
    port = 3307
  }
  "production-app1" = { 
    port = 3306
  }
  "production-app2" = { 
    port = 3307
  }

This can be used to generate service objects and use the inner dictionary as config, e.g. to set the port for the database instance.

Solution

Sourcing from https://icinga.com/2018/09/17/icinga-2-dsl-feature-namespaces-coming-in-v2-10/

Function

We’re using a dedicated namespace called MyFunctions instead of putting the function into the global namespace. These functions are organized in functions.conf which must be included in icinga2.conf after the constants.conf file.

vim /etc/icinga2/icinga2.conf

include "constants.conf"
include "functions.conf"
vim /etc/icinga2/functions.conf

namespace MyFunctions {

  /* Flatten a dictionary with unique keys for service apply for rules. */
  function flattenDictionary2ndLevel(obj, d) {
    var res = {}

    // Some sanity checks up front
    if (typeof(d) != Dictionary) {
      //log(LogWarning, "flattenDictionary2ndLevel", "ERROR: parameter is not a dictionary.")
      return {} // return an empty result set on error, including the log above
    }

    // Loop over the given dictionary
    for (k1 => v1 in d) {
      // Sanity check
      if (typeof(v1) != Dictionary) {
        //log(LogWarning, "flattenDictionary2ndLevel", "ERROR: First level with key " + k1 + " is not a dictionary value.")
        continue;
      }

      // Nested loop building the unique flattened key name
      for (k2 => v2 in v1) {
        var new_key = k1 + "-" + k2 // Generate a unique name.
        var new_val = v2.clone()
        // Add a specific global constant - optional example.
        if (globals.MysqlUsername != "") {
          new_val["username"] = globals.MysqlUsername
        }
        // Store the value with the new key
        res[new_key] = new_val
      }
    }

    //log(LogInformation, "flattenDictionary2ndLevel", res)

    return res
  }
}

Host Object and Apply For

// Load the namespace
using MyFunctions

object Host "db-host01" {
  check_command = "dummy"

  vars.mysql_databases["production"] = {
    "app1" = {
      "port" = 3306
    },
    "app2" = {
     "port" = 3307
    }
  }
  vars.mysql_databases["staging"] = {
    "app1" = {
      "port" = 3306
    },
    "app2" = {
      "port" = 3307
    }
  }
}

apply Service "mysql-" for (db_key => config in flattenDictionary2ndLevel(host, host.vars.mysql_databases)) {
  check_command = "dummy"
  vars += config
}

Parameters

Depending on the configuration and used check_command, port needs to be renamed to mysql_health_port in order to make vars += config work.

Or you’ll configure it manually inside the apply for scope with:

  if (config.port) {
    vars.mysql_health_port = config.port
  }

Object List Verification

michi@mbpmif /usr/local/icinga/icinga2/etc/icinga2 $ icinga2 object list --type Service --name db-host01*
Object 'db-host01!mysql-staging-app2' of type 'Service':
  % declared in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 24:1-24:107
  * __name = "db-host01!mysql-staging-app2"
  * action_url = ""
  * check_command = "dummy"
    % = modified in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 25:3-25:25
  * check_interval = 300
  * check_period = ""
  * check_timeout = null
  * command_endpoint = ""
  * display_name = "mysql-staging-app2"
  * enable_active_checks = true
  * enable_event_handler = true
  * enable_flapping = false
  * enable_notifications = true
  * enable_passive_checks = true
  * enable_perfdata = true
  * event_command = ""
  * flapping_threshold = 0
  * flapping_threshold_high = 30
  * flapping_threshold_low = 25
  * groups = [ ]
  * host_name = "db-host01"
    % = modified in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 24:1-24:107
  * icon_image = ""
  * icon_image_alt = ""
  * max_check_attempts = 3
  * name = "mysql-staging-app2"
    % = modified in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 24:1-24:107
  * notes = ""
  * notes_url = ""
  * package = "_etc"
    % = modified in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 24:1-24:107
  * retry_interval = 60
  * source_location
    * first_column = 1
    * first_line = 24
    * last_column = 107
    * last_line = 24
    * path = "/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf"
  * templates = [ "mysql-staging-app2" ]
    % = modified in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 24:1-24:107
  * type = "Service"
  * vars
    % = modified in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 26:3-26:16
    * port = 3307
  * volatile = false
  * zone = ""

Object 'db-host01!mysql-staging-app1' of type 'Service':
  % declared in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 24:1-24:107
  * __name = "db-host01!mysql-staging-app1"
  * action_url = ""
  * check_command = "dummy"
    % = modified in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 25:3-25:25
  * check_interval = 300
  * check_period = ""
  * check_timeout = null
  * command_endpoint = ""
  * display_name = "mysql-staging-app1"
  * enable_active_checks = true
  * enable_event_handler = true
  * enable_flapping = false
  * enable_notifications = true
  * enable_passive_checks = true
  * enable_perfdata = true
  * event_command = ""
  * flapping_threshold = 0
  * flapping_threshold_high = 30
  * flapping_threshold_low = 25
  * groups = [ ]
  * host_name = "db-host01"
    % = modified in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 24:1-24:107
  * icon_image = ""
  * icon_image_alt = ""
  * max_check_attempts = 3
  * name = "mysql-staging-app1"
    % = modified in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 24:1-24:107
  * notes = ""
  * notes_url = ""
  * package = "_etc"
    % = modified in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 24:1-24:107
  * retry_interval = 60
  * source_location
    * first_column = 1
    * first_line = 24
    * last_column = 107
    * last_line = 24
    * path = "/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf"
  * templates = [ "mysql-staging-app1" ]
    % = modified in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 24:1-24:107
  * type = "Service"
  * vars
    % = modified in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 26:3-26:16
    * port = 3306
  * volatile = false
  * zone = ""

Object 'db-host01!mysql-production-app2' of type 'Service':
  % declared in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 24:1-24:107
  * __name = "db-host01!mysql-production-app2"
  * action_url = ""
  * check_command = "dummy"
    % = modified in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 25:3-25:25
  * check_interval = 300
  * check_period = ""
  * check_timeout = null
  * command_endpoint = ""
  * display_name = "mysql-production-app2"
  * enable_active_checks = true
  * enable_event_handler = true
  * enable_flapping = false
  * enable_notifications = true
  * enable_passive_checks = true
  * enable_perfdata = true
  * event_command = ""
  * flapping_threshold = 0
  * flapping_threshold_high = 30
  * flapping_threshold_low = 25
  * groups = [ ]
  * host_name = "db-host01"
    % = modified in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 24:1-24:107
  * icon_image = ""
  * icon_image_alt = ""
  * max_check_attempts = 3
  * name = "mysql-production-app2"
    % = modified in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 24:1-24:107
  * notes = ""
  * notes_url = ""
  * package = "_etc"
    % = modified in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 24:1-24:107
  * retry_interval = 60
  * source_location
    * first_column = 1
    * first_line = 24
    * last_column = 107
    * last_line = 24
    * path = "/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf"
  * templates = [ "mysql-production-app2" ]
    % = modified in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 24:1-24:107
  * type = "Service"
  * vars
    % = modified in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 26:3-26:16
    * port = 3307
  * volatile = false
  * zone = ""

Object 'db-host01!mysql-production-app1' of type 'Service':
  % declared in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 24:1-24:107
  * __name = "db-host01!mysql-production-app1"
  * action_url = ""
  * check_command = "dummy"
    % = modified in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 25:3-25:25
  * check_interval = 300
  * check_period = ""
  * check_timeout = null
  * command_endpoint = ""
  * display_name = "mysql-production-app1"
  * enable_active_checks = true
  * enable_event_handler = true
  * enable_flapping = false
  * enable_notifications = true
  * enable_passive_checks = true
  * enable_perfdata = true
  * event_command = ""
  * flapping_threshold = 0
  * flapping_threshold_high = 30
  * flapping_threshold_low = 25
  * groups = [ ]
  * host_name = "db-host01"
    % = modified in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 24:1-24:107
  * icon_image = ""
  * icon_image_alt = ""
  * max_check_attempts = 3
  * name = "mysql-production-app1"
    % = modified in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 24:1-24:107
  * notes = ""
  * notes_url = ""
  * package = "_etc"
    % = modified in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 24:1-24:107
  * retry_interval = 60
  * source_location
    * first_column = 1
    * first_line = 24
    * last_column = 107
    * last_line = 24
    * path = "/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf"
  * templates = [ "mysql-production-app1" ]
    % = modified in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 24:1-24:107
  * type = "Service"
  * vars
    % = modified in '/usr/local/icinga/icinga2/etc/icinga2/tests/flatten.conf', lines 26:3-26:16
    * port = 3306
  * volatile = false
  * zone = ""
1 Like