ansiblejinja2

How do I create a nested list fact?


In Ansible. This might be a bit of a challenge. I have this variable:

nodes:
  - name: fin
    hosts: 2
  - name: swe
    hosts: 1

And from this I would like a fact like this:

fin:
   - name: fin1
   - name: fin2
swe:
   - name: swe1

Is this possible?


Solution

  • There are multiple solutions. I'll give two which are the easiest and most legible and maintainable in my opinion.

    Use a custom filter

    This is my preferred one as using python to transform the data gives much more possibilities. I just crafted one which fulfills your above requirement. Meanwhile note it is not production ready as you should add input data validation and exception handling. But this will give you the global idea.

    Create a filter_plugins/my_node_plugins.py file adjacent to you playbook:

    def transform_my_nodes(nodes):
        transformed_nodes = {}
        for node in nodes:
            node_list = []
            for node_number in range(1, node["hosts"] + 1):
                node_list.append({"name": f"{node['name']}{node_number}"})
            transformed_nodes[node["name"]] = node_list
    
        return transformed_nodes
    
    class FilterModule(object):
    
        def filters(self):
            return {
                'transform_my_nodes': transform_my_nodes,
            }
    

    Then the following playbook:

    - hosts: localhost
      gather_facts: false
    
      vars:
        nodes:
          - name: fin
            hosts: 2
          - name: swe
            hosts: 1
    
      tasks:
        - name: Transform data with a custom filter
          debug:
            var: nodes | transform_my_nodes
    

    gives:

    $ ansible-playbook transform_with_filter.yml 
    
    PLAY [localhost] *********************************************************************************************************************************************************************************************************************
    
    TASK [Transform data with a custom filter] *******************************************************************************************************************************************************************************************
    ok: [localhost] => {
        "nodes | transform_my_nodes": {
            "fin": [
                {
                    "name": "fin1"
                },
                {
                    "name": "fin2"
                }
            ],
            "swe": [
                {
                    "name": "swe1"
                }
            ]
        }
    }
    
    PLAY RECAP ***************************************************************************************************************************************************************************************************************************
    localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
    

    Use a template

    Create a templates/transform_nodes.j2 file adjacent to your playbook:

    {% for node in nodes %}
    {{ node.name }}:
    {%  for node_number in range(1, node.hosts + 1) %}
        - name: {{ node.name }}{{ node_number }}
    {% endfor %}
    {% endfor %}
    

    Then the following playbook:

    - hosts: localhost
      gather_facts: false
    
      vars:
        nodes:
          - name: fin
            hosts: 2
          - name: swe
            hosts: 1
    
      tasks:
        - name: Transform data with a template
          debug:
            var: lookup('template', 'transform_nodes.j2') | from_yaml
    

    gives:

    $ ansible-playbook transform_with_template.yml 
    
    PLAY [localhost] *********************************************************************************************************************************************************************************************************************
    
    TASK [Transform data with a template] ************************************************************************************************************************************************************************************************
    ok: [localhost] => {
        "lookup('template', 'transform_nodes.j2') | from_yaml": {
            "fin": [
                {
                    "name": "fin1"
                },
                {
                    "name": "fin2"
                }
            ],
            "swe": [
                {
                    "name": "swe1"
                }
            ]
        }
    }
    
    PLAY RECAP ***************************************************************************************************************************************************************************************************************************
    localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0