ansible

Update a value in a list of dictionaries


I have the following list of dictionaries:

"errorlist": [
    {
        "error": "Not found",
        "path": "/tmp/working/directory1/file1"
    },
    {
        "error": "Not found",
        "path": "/tmp/working/directory2/file1"
    },
    {
        "error": "Not found",
        "path": "/tmp/working/directory1/file2"
    },
    {
        "error": "Not found",
        "path": "/tmp/working/directory2/file2"
    }
]

I'd like to remove the "/tmp/working/" part of the path values, i.e. convert the absolute paths to relative ones.

I've been through similar questions while searching online but haven't found a solution. It should be rather simple but I can't wrap my head around it.

The expected result should be either the original list with updated values or a new list with the same structure.


Solution

  • Filter the basenames and create a list of dictionaries

      paths: "{{ errorlist | map(attribute='path') | map('basename') |
                             map('community.general.dict_kv', 'path') }}"
    

    gives

      paths:
        - {path: file1}
        - {path: file1}
        - {path: file2}
        - {path: file2}
    

    zip the lists and combine the items

      result: "{{ errorlist | zip(paths) | map('combine') }}"
    

    gives

      result:
        - {error: Not found, path: file1}
        - {error: Not found, path: file1}
        - {error: Not found, path: file2}
        - {error: Not found, path: file2}
    

    Fit the pipes to your needs.


    • Example of a complete playbook for testing
    - hosts: localhost
    
      vars:
    
        errorlist:
          - {error: Not found, path: /tmp/working/directory1/file1}
          - {error: Not found, path: /tmp/working/directory2/file1}
          - {error: Not found, path: /tmp/working/directory1/file2}
          - {error: Not found, path: /tmp/working/directory2/file2}
    
        paths: "{{ errorlist | map(attribute='path') | map('basename') |
                               map('community.general.dict_kv', 'path') }}"
        result: "{{ errorlist | zip(paths) | map('combine') }}"
    
      tasks:
    
        - debug:
            var: paths | to_yaml
        - debug:
            var: result | to_yaml
    
    • Fit the filters to your needs. For example, to remove any part of the string use the filter regex_replace
      paths: "{{ errorlist | map(attribute='path') |
                             map('regex_replace', '/tmp/working/(.*)', '\\1') |
                             map('community.general.dict_kv', 'path') }}"
    

    gives

      paths:
        - {path: directory1/file1}
        - {path: directory2/file1}
        - {path: directory1/file2}
        - {path: directory2/file2}
    

    This will result in

      result:
        - {error: Not found, path: directory1/file1}
        - {error: Not found, path: directory2/file1}
        - {error: Not found, path: directory1/file2}
        - {error: Not found, path: directory2/file2}
    

    Q: "How can I achieve the same result without community.general.dict_kv?"

    A: Use json_query. The below declaration gives the same result

      paths: "{{ errorlist | map(attribute='path') |
                             map('regex_replace', '/tmp/working/(.*)', '\\1') |
                             json_query('[].{path: @}') }}"
    

    In Ansible 2.9 and lower try to add the filter list

      paths: "{{ errorlist | map(attribute='path') | list |
                             map('regex_replace', '/tmp/working/(.*)', '\\1') | list |
                             json_query('[].{path: @}') | list }}"
    

    Debugging

    Decompose the pipe

      path1: "{{ errorlist | map(attribute='path') }}"
      path2: "{{ errorlist | map(attribute='path') |
                             map('regex_replace', '/tmp/working/(.*)', '\\1') }}"
      path3: "{{ errorlist | map(attribute='path') |
                             map('regex_replace', '/tmp/working/(.*)', '\\1') |
                             json_query('[].{path: @}') }}"
    

    and display the types of intermediary results

        - debug:
            msg: "{{ path1 | type_debug }}"
        - debug:
            msg: "{{ path2 | type_debug }}"
        - debug:
            msg: "{{ path3 | type_debug }}"