Search code examples
loopsansible

ansible change value of fact while looping through


I have a custom fact that is a list of dictionaries. I need to look at the value of each dictionary item and potentially change some of the values. below is an example of the fact, my loop looks at each item and queries an api for information related to bt_device and potentially change the value

[
    {
        "at_device": "sw",
        "at_name": "GigabitEthernet0/5",
        "at_type": "dcim.interface",
        "bt_device": "sw2",
        "bt_name": "GigabitEthernet1/0/2",
        "bt_type": "dcim.interface"
    },
    {
        "at_device": "sw",
        "at_name": "GigabitEthernet0/8",
        "at_type": "dcim.interface",
        "bt_device": "sw3",
        "bt_name": "GigabitEthernet1/0/5",
        "bt_type": "dcim.interface"
    }
]

so based on the query I need to change "sw3" to "fp3"

the below tasks work to show the data I want to change to on the screen, but I cannot make the leap to modifying the value in the fact as needed.

- name: load custom fact
  ansible.builtin.set_fact:
    custom_fact: "{{ custom_fact | default([]) + [{ 'at_device' : ansible_net_hostname, 'at_name' : item.key, 'at_type' : 'dcim.interface', 'bt_device' : item.value[0].host, 'bt_name' : item.value[0].port, 'bt_type' : 'dcim.interface'}] }}"
  loop: "{{ ansible_facts | dict2items }}"

- name: modify custom fact via query
  debug:
    msg: "{{ query(NetBoxy stuff).device.name }}"
  when: "query(NetBoxy stuff) | length > 0"
  loop: "{{ custom_fact }}"

Thanks

I have attempted many things however I am new enough to have no idea how to get there.


Solution

  • Q: Change "sw3" to "fp3".

    A: The solution depends on the structure of the data. For example, given the dictionary

      update:
        key: bt_device
        regex: sw3
        replace: fp3
    

    the declaration below

      result: "{{ devices | rejectattr(update.key, 'regex', update.regex) +
                  devices | selectattr(update.key, 'regex', update.regex) |
                            zip([{update.key: update.replace}]) |
                            map('combine') }}"
    

    gives what you want

      result:
      - at_device: sw
        at_name: GigabitEthernet0/5
        at_type: dcim.interface
        bt_device: sw2
        bt_name: GigabitEthernet1/0/2
        bt_type: dcim.interface
      - at_device: sw
        at_name: GigabitEthernet0/8
        at_type: dcim.interface
        bt_device: fp3
        bt_name: GigabitEthernet1/0/5
        bt_type: dcim.interface
    

    Example of a complete playbook for testing

    - hosts: localhost
      
      vars:
    
        devices:
          - at_device: sw
            at_name: GigabitEthernet0/5
            at_type: dcim.interface
            bt_device: sw2
            bt_name: GigabitEthernet1/0/2
            bt_type: dcim.interface
          - at_device: sw
            at_name: GigabitEthernet0/8
            at_type: dcim.interface
            bt_device: sw3
            bt_name: GigabitEthernet1/0/5
            bt_type: dcim.interface
    
        update:
          key: bt_device
          regex: sw3
          replace: fp3
    
        result: "{{ devices | rejectattr(update.key, 'regex', update.regex) +
                    devices | selectattr(update.key, 'regex', update.regex) |
                              zip([{update.key: update.replace}]) |
                              map('combine') }}"
    
      tasks:
    
        - debug:
            var: result
    

    The next option is a simpler dictionary that allows updating of more keys. For example,

      update:
        bt_device: {regex: sw3, replace: fp3}
        at_device: {regex: sw, replace: swX}
    

    the declarations below

      devices_updated: |
        {% for device in devices %}
        { {% for k in  device.keys()|intersect(update) %}
        {% if device[k] == update[k]['regex'] %}
        {{ k }}: {{ update[k]['replace'] }}, {% endif %}{% endfor %} }
        {% endfor %}
      result: "{{ devices |
                  zip(devices_updated.splitlines()|map('from_yaml')) |
                  map('combine') }}"
    

    gives

      result:
      - at_device: swX
        at_name: GigabitEthernet0/5
        at_type: dcim.interface
        bt_device: sw2
        bt_name: GigabitEthernet1/0/2
        bt_type: dcim.interface
      - at_device: swX
        at_name: GigabitEthernet0/8
        at_type: dcim.interface
        bt_device: fp3
        bt_name: GigabitEthernet1/0/5
        bt_type: dcim.interface
    

    Example of a complete playbook for testing

    - hosts: localhost
      
      vars:
    
        devices:
          - at_device: sw
            at_name: GigabitEthernet0/5
            at_type: dcim.interface
            bt_device: sw2
            bt_name: GigabitEthernet1/0/2
            bt_type: dcim.interface
          - at_device: sw
            at_name: GigabitEthernet0/8
            at_type: dcim.interface
            bt_device: sw3
            bt_name: GigabitEthernet1/0/5
            bt_type: dcim.interface
    
        update:
          bt_device: {regex: sw3, replace: fp3}
          at_device: {regex: sw, replace: swX}
    
        devices_updated: |
          {% for device in devices %}
          { {% for k in  device.keys()|intersect(update) %}
          {% if device[k] == update[k]['regex'] %}
          {{ k }}: {{ update[k]['replace'] }}, {% endif %}{% endfor %} }
          {% endfor %}
        result: "{{ devices |
                    zip(devices_updated.splitlines()|map('from_yaml')) |
                    map('combine') }}"
    
      tasks:
    
        - debug:
            var: devices_updated
        - debug:
            var: result