Looking for a more elegant way to declare complex variables

Sensu is monitoring services like Nagios and the things it should monitor can be configured with JSON check definition files.

A JSON check definition file can have one or more check definitions. Here is a simplified example:

{
  "checks": {
    "cron": {
      "command": "check-process.rb -p cron",      
      "interval": 60
    }
  }
}

Let’s say I have defined the following template for generating check files:

{
  "checks": {
    {% for check_def in item.check_defs %}
        "{{ check_def.name }}": {
    	  "command": "{{ check_def.command }}",
          "interval": {{ check_def.check_interval }}
        }{{ ',' if not loop.last else '' }}
    {% endfor %}
  }
}

I apply this template a task like this:

- copy:
    content:  "{{ lookup('template', 'check_def.json.j2') | to_nice_json }}"
    dest: "{{ somewhere }}"    
  with_items: "{{ check_sets }}"
  notify: Restart Sensu

The check_sets variable is defined similar to:

  vars:
    services:
      - foo
      - bar
      - baz

  tasks:    
      set_fact:
        check_defs: >
          {{
            check_defs|default([]) + [
              {
                'name' : 'my-check-def-for' + item.name,
                'command' : 'check-process.rb',
                'command_args' : '-p ' + item
              }
            ]
          }}
      with_items: "{{ services }}"

I find this approach to defining check_defs to be ugly for several reasons:

1- It is not declarative (e.g. check_defs|default([]) as well as appending items to the list are not declarative).

2- I don’t like the string concat logic using + and using quotes around hardcoded strings

3- For newcomers, it is not easy to understand what is going on

If I use a static list, I have to repeat some variables/values. For example:

check_defs:
	- name: my-check-def-for-foo
	  command: check-process.rb
	  command_args: -p foo

Here foo is repeated twice.

Also users might diverge from the naming convention. For example by providing:

check_defs:
	- name: foo-check
	  command: check-process.rb
	  command_args: -p foo

Is there a more elegant way to declare the check_defs variable that doesn’t have any of these drawbacks?

Thanks in advance.

Hi Behrang,

Comments inline...

Sensu is monitoring services like Nagios and the things it should monitor can be configured with JSON check definition files.

A JSON check definition file can have one or more check definitions. Here is a simplified example:

{
  "checks": {
    "cron": {
      "command": "check-process.rb -p cron",
      "interval": 60
    }
  }
}

Let's say I have defined the following template for generating check files:

{
  "checks": {
    {% for check_def in item.check_defs %}
        "{{ check_def.name }}": {
        "command": "{{ check_def.command }}",
          "interval": {{ check_def.check_interval }}
        }{{ ',' if not loop.last else '' }}
    {% endfor %}
  }
}

<snipped a lot>

I am using Ansible to manage checks.json myself but am doing so with the Sensu module from
http://docs.ansible.com/ansible/latest/sensu_check_module.html as below:

name: Updating checks in - /etc/sensu/conf.d/{{sensu_check_filename|default(check_filename)}}
  sensu_check:
    #path: "{{ sensu_check_filename|default(check_filename) }}"
    path: /etc/sensu/conf.d/checks.json
    name: "{{ item.name }}"
    command: "{{ item.command }}"
    interval: "{{ item.interval|default(check_interval) }}"
    metric: "{{ item.metric | default('no') }}"
    handle: "{{ item.handle | default('yes') }}"
    occurrences: "{{ item.occurrences | default(check_occurrences) }}"
    timeout: "{{ item.timeout | default(check_timeout) }}"
    dependencies: "{{ item.dependencies | default(omit) }}"
    subscribers: "{{ item.subscribers|default(default_subscribers) }}"
    handlers: "{{ item.handlers | default(omit) }}"
    refresh: "{{ item.refresh | default(check_refresh) }}"
    state: "{{ item.state | default('present') }}"
    standalone: "{{ item.standalone | default('no') }}"
    ttl: "{{ item.ttl | default(900) }}"
    aggregate: "{{ item.aggregate | default('no') }}"
    subdue_begin: "{{ item.subdue_begin | default(omit) }}"
    subdue_end: "{{ item.subdue_end | default(omit) }}"
    source: "{{ item.source | default(omit) }}"
    low_flap_threshold: "{{ item.low_flap_threshold | default(omit) }}"
    high_flap_threshold: "{{ item.high_flap_threshold | default(omit) }}"
  notify:
    - restart_sensu_server
    - restart_sensu_client
    - restart_sensu_api
  with_items: "{{ _sensu_check_list|sort }}"

Is there any particular reason why you chose to use a template?

Regards.
@shankerbalan

Just a note, this task does x2 transformations it does not need to, if
you use template directly you avoid many problems.

so instead of:
- copy:
    content: "{{ lookup('template', 'check_def.json.j2') | to_nice_json }}"
    dest: "{{ somewhere }}"
  with_items: "{{ check_sets }}"
  notify: Restart Sensu

use:
- template: src=check_def.json.j2 dest={{somewhere}}
  with_items: "{{ check_sets }}"
  notify: Restart Sensu

I want to pretty print the generated JSON file. Otherwise I would have used the template module.

Oops. I didn’t know this module existed. I am maintaining a set of scripts that was written before 2.0 and this module were released.