verideskstar
verideskstar

Reputation: 187

Perform string transform on dictionary values within a list of dicts via Ansible/Jinja

I have an Ansible host with variables like below. Lets assume that the 53dns rule was originally a group variable overwritten to null by a host variable; it would show up as None in Ansible.

firewall__rule_02loopback: |
  -A INPUT -i lo -j ACCEPT
  -A OUTPUT -o lo -j ACCEPT
firewall__rule_03icmp: | 
  -A INPUT -p icmp -j ACCEPT
  -A FORWARD -p icmp -j ACCEPT
firewall__rule_53dns: ~

I am looking for an Ansible/Jinja expression to convert these flat variables into a nested one such that the final product could look something like:

firewall__rules:
  - name: 02loopback
    content: |
      -A INPUT -i lo -j ACCEPT
      -A OUTPUT -o lo -j ACCEPT
  - name: 03icmp
    content: |
      -A INPUT -p icmp -j ACCEPT
      -A FORWARD -p icmp -j ACCEPT
  - name: 53dns
    content: None

I have been extract the variables into a single nested list of dicts with this:

set_fact:
  firewall__rules: '{{ hostvars[inventory_hostname]
    | dict2items("name", "content")
    | selectattr("name", "search", "^firewall__rule") }}'

However, the name field of every dict is still prefixed with firewall__rule_, how can I perform a transform on the name value for every dict inside the list?

Upvotes: 1

Views: 139

Answers (1)

Vladimir Botka
Vladimir Botka

Reputation: 68074

The set_fact below does the job

    - set_fact:
        firewall__rules: "{{ firewall__rules|default([]) +
                             [{'name': key, 'content': val}] }}"
      loop: "{{ query('varnames', 'firewall__rule_*') }}"
      vars:
        key: "{{ item.split('_')|last }}"
        val: "{{ lookup('vars', item) }}"

gives

  firewall__rules:
  - content: |-
      -A INPUT -i lo -j ACCEPT
      -A OUTPUT -o lo -j ACCEPT
    name: 02loopback
  - content: |-
      -A INPUT -p icmp -j ACCEPT
      -A FORWARD -p icmp -j ACCEPT
    name: 03icmp
  - content: ''
    name: 53dns

Example of a complete playbook for testing

- hosts: localhost

  vars:

    firewall__rule_02loopback: |
      -A INPUT -i lo -j ACCEPT
      -A OUTPUT -o lo -j ACCEPT
    firewall__rule_03icmp: | 
      -A INPUT -p icmp -j ACCEPT
      -A FORWARD -p icmp -j ACCEPT
    firewall__rule_53dns: ~

  tasks:

    - set_fact:
        firewall__rules: "{{ firewall__rules|default([]) +
                             [{'name': key, 'content': val}] }}"
      loop: "{{ query('varnames', 'firewall__rule_*') }}"
      vars:
        key: "{{ item.split('_')|last }}"
        val: "{{ lookup('vars', item) }}"

    - debug:
        var: firewall__rules

You can avoid the iteration of set_fact and hide the logic in vars. Declare the variables

  fr_vars: '^firewall__rule_.*$'
  fr_keys: "{{ query('varnames', fr_vars)|map('split', '_')|map('last') }}"
  fr_vals: "{{ lookup('vars', *q('varnames', fr_vars)) }}"
  fr_dict: "{{ dict(fr_keys|zip(fr_vals)) }}"

give the list of the keys

  fr_keys:
  - 02loopback
  - 03icmp
  - 53dns

, the list of the values

  fr_vals:
  - |-
    -A INPUT -i lo -j ACCEPT
    -A OUTPUT -o lo -j ACCEPT
  - |-
    -A INPUT -p icmp -j ACCEPT
    -A FORWARD -p icmp -j ACCEPT
  - null

, and the dictionary

  fr_dict:
    02loopback: |-
      -A INPUT -i lo -j ACCEPT
      -A OUTPUT -o lo -j ACCEPT
    03icmp: |-
      -A INPUT -p icmp -j ACCEPT
      -A FORWARD -p icmp -j ACCEPT
    53dns: null

Convert the dictionary to list

  fr_list: "{{ fr_dict|
               dict2items(key_name='name', value_name='content') }}"

gives the expected result

  fr_list:
  - content: |-
      -A INPUT -i lo -j ACCEPT
      -A OUTPUT -o lo -j ACCEPT
    name: 02loopback
  - content: |-
      -A INPUT -p icmp -j ACCEPT
      -A FORWARD -p icmp -j ACCEPT
    name: 03icmp
  - content: null
    name: 53dns

Example of a complete playbook for testing

- hosts: localhost

  vars:

    firewall__rule_02loopback: |
      -A INPUT -i lo -j ACCEPT
      -A OUTPUT -o lo -j ACCEPT
    firewall__rule_03icmp: | 
      -A INPUT -p icmp -j ACCEPT
      -A FORWARD -p icmp -j ACCEPT
    firewall__rule_53dns: ~

    fr_vars: '^firewall__rule_.*$'
    fr_keys: "{{ query('varnames', fr_vars)|map('split', '_')|map('last') }}"
    fr_vals: "{{ lookup('vars', *q('varnames', fr_vars)) }}"
    fr_dict: "{{ dict(fr_keys|zip(fr_vals)) }}"
    fr_list: "{{ fr_dict|dict2items(key_name='name', value_name='content') }}"

  tasks:

    - debug:
        var: fr_keys
    - debug:
        var: fr_vals
    - debug:
        var: fr_dict
    - debug:
        var: fr_list

Upvotes: 1

Related Questions