ansiblejinja2

In Ansible, add an element to every dict in a list of dicts, but multiply it by an existing entry


I have a list of dicts:

vars:
  sites:
    - name: "Tampa"
      site_number: 0
    - name: "Miami"
      site_number: 1
    - name: "Daytona"
      site_number: 2

I want to add an entry to every dict in this list, but multiply it by a value. I have tried the following:

- set_fact:
    sites: >-
      {{
        sites | map('combine', {
          'site_subnet': '172.16.0.' + (item.site_number*8)|string + '/29',
          'test1': item.site_number
        }) | list
      }}
  loop: "{{ sites }}"
- debug: var=sites

Now, the resultant output shows that sites[n].test1 is always 2, and of course, the site_subnet is always 172.16.0.16/29. What is the malfunction with my code?


Solution

  • Ansible is generally not a great tool for modifying data structures. You can get what you want by creating a new variable, like this:

    - hosts: localhost
      gather_facts: false
      vars:
        sites:
          - name: "Tampa"
            site_number: 0
          - name: "Miami"
            site_number: 1
          - name: "Daytona"
            site_number: 2
      tasks:
        - set_fact:
            new_sites: >-
              {{
                new_sites + [item | combine({
                  'site_subnet': '172.16.0.' ~ (item.site_number*8) ~ '/29',
                  'test1': item.site_number
                })]
              }}
          loop: "{{ sites }}"
          vars:
            new_sites: []
    
        - debug:
            var: new_sites
    

    This results in the following output (which is what I think you want):

    TASK [debug] *************************************************************************************************************************************************************
    ok: [localhost] => {
        "new_sites": [
            {
                "name": "Tampa",
                "site_number": 0,
                "site_subnet": "172.16.0.0/29",
                "test1": 0
            },
            {
                "name": "Miami",
                "site_number": 1,
                "site_subnet": "172.16.0.8/29",
                "test1": 1
            },
            {
                "name": "Daytona",
                "site_number": 2,
                "site_subnet": "172.16.0.16/29",
                "test1": 2
            }
        ]
    }
    

    For any sort of complex logic, I always find it more managable to use a custom filter instead. I would drop the following into filter_plugins/filters.py:

    def filter_update_sites(sites):
        return [
            site | {
                "site_subnet": f"172.16.0.{site['site_number']*8}/29"
            }
            for site in sites
        ]
    
    
    class FilterModule:
        def filters(self):
            return {
                "update_sites": filter_update_sites,
            }
    

    And then write the playbook like this:

    - hosts: localhost
      gather_facts: false
      vars:
        sites:
          - name: "Tampa"
            site_number: 0
          - name: "Miami"
            site_number: 1
          - name: "Daytona"
            site_number: 2
      tasks:
        - set_fact:
            new_sites: "{{ sites | update_sites }}"
    
        - debug:
            var: new_sites
    

    Which produces:

    TASK [debug] *************************************************************************************************************************************************************
    ok: [localhost] => {
        "new_sites": [
            {
                "name": "Tampa",
                "site_number": 0,
                "site_subnet": "172.16.0.0/29"
            },
            {
                "name": "Miami",
                "site_number": 1,
                "site_subnet": "172.16.0.8/29"
            },
            {
                "name": "Daytona",
                "site_number": 2,
                "site_subnet": "172.16.0.16/29"
            }
        ]
    }