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

**URL:** <https://community.icinga.com/t/dsl-flatten-a-nested-dictionary-with-a-function-for-apply-for-rules/1330>\
**Category:** Tutorials and Guides\
**Tags:** dsl, icinga2\
**Created:** [May 27, 2019, 11:21am UTC](https://community.icinga.com/t/dsl-flatten-a-nested-dictionary-with-a-function-for-apply-for-rules/1330 "2019-05-27T11:21:09Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![dnsmichi](https://community.icinga.com/user_avatar/community.icinga.com/dnsmichi/32/7721_2.png) [@dnsmichi](https://community.icinga.com/u/dnsmichi)\
**Post date:** [May 27, 2019, 11:21am UTC](https://community.icinga.com/t/dsl-flatten-a-nested-dictionary-with-a-function-for-apply-for-rules/1330/1 "2019-05-27T11:21:10Z")

</div>

> 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:

```auto
  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`.

```auto
  "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/](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.

```auto
vim /etc/icinga2/icinga2.conf

include "constants.conf"
include "functions.conf"

```

```auto
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

```auto
// 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:

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

```

## Object List Verification

```auto
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 = ""

```
