Florent
Florent

Reputation: 59

Ansible Jinja template combined list into dict

i am stuck with the template below :

- hosts: localhost
  gather_facts: false

  vars:
    test1:
      role1: groupA
      role2: [groupB, groupC]
      role3: groupD

    test2:
      groupA: role1
      groupB: role2
      groupC: role2

  tasks:
  - name: "[Test1] Mapping Role/Group"
    debug:
      var: test1.items()|map('join', ":" )| join(",")

  - name: "[Test1] List roles"
    debug:
      var: test1.keys() | join(',')

  - name: "[Test2] Mapping Role/Group"
    debug:
      var: test2.items()|join(",")

  - name: "[Test2] List roles"
    debug:
      var: test2.values() | unique() | join(",")

i prefer to use the format of the dict "test1" to construct the role/group mapping below

groupA: role1, groupB: role2, groupC: role2, groupD: role3

Actually, the result of the playlook is :

TASK [[Test1] Mapping Role/Group] **************************************************************************************************************************************************
Thursday 21 October 2021  15:06:25 +0200 (0:00:00.021)       0:00:00.021 ******
ok: [localhost] => {
    "test1.items()|map('join', \":\" )| join(\",\")": "role1:groupA,role2:['groupB', 'groupC'],role3:groupD"
}

TASK [[Test1] List roles] **********************************************************************************************************************************************************
Thursday 21 October 2021  15:06:25 +0200 (0:00:00.030)       0:00:00.051 ******
ok: [localhost] => {
    "test1.keys() | join(',')": "role1,role2,role3"
}

TASK [[Test2] Mapping Role/Group] **************************************************************************************************************************************************
Thursday 21 October 2021  15:06:25 +0200 (0:00:00.028)       0:00:00.080 ******
ok: [localhost] => {
    "test2.items()|join(\",\")": "('groupA', 'role1'),('groupB', 'role2'),('groupC', 'role2')"
}

TASK [[Test2] List roles] **********************************************************************************************************************************************************
Thursday 21 October 2021  15:06:25 +0200 (0:00:00.028)       0:00:00.108 ******
ok: [localhost] => {
    "test2.values() | unique() | join(\",\")": "role2,role1"
}

is it possible to do what i need with one line ? thanks

Upvotes: 0

Views: 237

Answers (2)

Frenchy
Frenchy

Reputation: 17007

when the logic becomes complex, i think its easier to use a custom filter (written in python):

you create a folder filter_plugins in your playbook folder (i have named the file myfilters.py and the filter customfilter)

#!/usr/bin/python
class FilterModule(object):
    def filters(self):
        return {

            'customfilter': self.customfilter
        }

    def customfilter(self, obj):
        result = ""

        for r in obj:
            if type(obj[r]) is list:
                for s in obj[r]:
                    result += ', ' if result else ''
                    result += '{value}: {key}'.format(value=s, key=r)
            else:
                result += ', ' if result else ''
                result += '{value}: {key}'.format(value=obj[r], key=r)
        return result

use case:

- name: vartest
  hosts: localhost
  vars:
    test1:
      role1: groupA
      role2: [groupB, groupC]
      role3: groupD 
  tasks:

    - name: transform variable
      set_fact:
        disp: "{{ test1 | customfilter }}"

    - name: debug
      debug:
        msg: "{{ disp }}"

result:

ok: [localhost] => {
    "msg": "groupA: role1, groupB: role2, groupC: role2, groupD: role3"
}

the solution is generic, with list or no list

same result with:

  vars:
    test1:
      role1: [groupA]
      role2: [groupB, groupC]
      role3: [groupD]

Upvotes: 1

Zeitounator
Zeitounator

Reputation: 44615

In my above comment, I was sure that a simple refactoring of your initial test1 dict to be consistent using list everywhere would made this very easy.

My goal was to answer with a solution not needing a single task/loop to calculate the result and to use some json_query magic in the middle.

This was before I (re)discovered that there is still an unsolved issue and an unmerged PR in jmespath which prevent to easily work with data returned as tuples by some ansible filters like zip or subelements.

This ended up being quite a nightmare but since I finally got the expected result with my original goal (no tasks or loop), I'll share it here. The solution is using several workarounds to transform what ansible returns as tuples to json strings, parsed back in ansible so they become lists that jmespath can finally work with.

The following playbook, using a slighly transformed definition of your original var

---
- hosts: localhost
  gather_facts: false

  vars:
    test1:
      role1: [groupA]
      role2: [groupB, groupC]
      role3: [groupD]

    q1: >-
      [].[to_string(@)]
    q2: >-
      [].join(': ', [[1], [0].key])

    my_map_string: >-
      {{
        test1
        | dict2items
        | subelements('value')
        | json_query(q1)
        | map('map', 'from_json')
        | json_query(q2)
        | join(', ')
      }}

  tasks:
    - name: Show result
      debug:
        var: my_map_string

Gives:

PLAY [localhost] *****************************************************************************

TASK [Show result] *****************************************************************************
ok: [localhost] => {
    "my_map_string": "groupA: role1, groupB: role2, groupC: role2, groupD: role3"
}

PLAY RECAP *****************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 

Upvotes: 0

Related Questions