Search code examples
ansiblejinja2ansible-template

Custom sort list based on another


I'm trying to sort a list of files based on a desired extension preference. Let's say I have a list that looks like:

- name: Declare all assets
  ansible.builtin.set_fact:
    asset_list:
      - artifact_amd64.tar.gz
      - artifact_amd64.tar.gz.sbom
      - artifact_arm64.tar.gz
      - artifact.deb
      - artifact.appimage
      - artifact_amd64
      - artifact_amd64.sha256sum

Now, I'd like to get sort them in some order which could be changed by prompt, say: ['deb', 'appimage', 'tar.gz', '']. In this case '' means no extension, which in this case means it's a linux executable. Regarding the files that don't match the extension, I'm not intereseted in them, I'd prefer them to be removed.

Is this possible to do without writing a custom filter? I had the idea to prepend the extensions to the items with something like: map('regex_replace', '^(.*\\.)(deb|appimage|tar\\.gz)$', '\\2 \\1\\2') but I would still need to prepend a number or something to pass it through the regular ansible sort before trying to remove it to get the final filename. The entire thing looks very complicated and hard to read in that case and I don't have a good solution for no extension.


Solution

  • Given the lists of the files and the extensions

        asset_list:
          - artifact_amd64.tar.gz
          - artifact_amd64.tar.gz.sbom
          - artifact_arm64.tar.gz
          - artifact.deb
          - artifact.appimage
          - artifact_amd64
          - artifact_amd64.sha256sum
        asset_order: [deb, appimage, tar.gz]
    

    Reverse the items

        asset_list_reverse: "{{ asset_list | map('reverse') }}"
        asset_order_reverse: "{{ asset_order | map('reverse') }}"
    

    Use the fact that the test match:

    succeeds if it finds the pattern at the beginning of the string

    and create the ordered list of files with extensions

      asset_list_order: |
        {% filter flatten %}
        [{% for ext in asset_order_reverse %}
        {{ asset_list_reverse | select('match', ext) | map('reverse') }},
        {% endfor %}]
        {% endfilter %}
    

    gives

      asset_list_order:
      - artifact.deb
      - artifact.appimage
      - artifact_amd64.tar.gz
      - artifact_arm64.tar.gz
    

    Create a list of executables

      asset_list_exe: "{{ asset_list | reject('search', '\\.') }}"
    

    gives

      asset_list_exe:
      - artifact_amd64
    

    Add the lists

      result: "{{ asset_list_order + asset_list_exe }}"
    

    gives

      result:
      - artifact.deb
      - artifact.appimage
      - artifact_amd64.tar.gz
      - artifact_arm64.tar.gz
      - artifact_amd64
    

    Notes

    1. Example of a complete playbook for testing
    - hosts: localhost
    
      vars:
    
        asset_list:
          - artifact_amd64.tar.gz
          - artifact_amd64.tar.gz.sbom
          - artifact_arm64.tar.gz
          - artifact.deb
          - artifact.appimage
          - artifact_amd64
          - artifact_amd64.sha256sum
        asset_list_reverse: "{{ asset_list | map('reverse') }}"
    
        asset_order: [deb, appimage, tar.gz]
        asset_order_reverse: "{{ asset_order | map('reverse') }}"
    
        asset_list_order: |
          {% filter flatten %}
          [{% for ext in asset_order_reverse %}
          {{ asset_list_reverse | select('match', ext) | map('reverse') }},
          {% endfor %}]
          {% endfilter %}
        asset_list_exe: "{{ asset_list | reject('search', '\\.') }}"
        result: "{{ asset_list_order + asset_list_exe }}"
    
      tasks:
    
        - debug:
            var: asset_list_order
        - debug:
            var: asset_list_exe
        - debug:
            var: result
    

    1. If you want to put the empty string into the list, for example,
      asset_order: [deb, appimage, tar.gz, '']
    

    remove it from the list of the reversed extensions

      asset_order_reverse: "{{ asset_order | select | map('reverse') }}"
    

    and test it when assembling the result

      result: "{{ ('' in asset_order) |
                  ternary(asset_list_order + asset_list_exe,
                          asset_list_order) }}"
    

    1. The next option is to create the complete list in Jinja. This keeps the order of the extensions including the executables. The example of a complete playbook for testing
    - hosts: localhost
    
      vars:
    
        asset_list:
          - artifact_amd64.tar.gz
          - artifact_amd64.tar.gz.sbom
          - artifact_arm64.tar.gz
          - artifact.deb
          - artifact.appimage
          - artifact_amd64
          - artifact_amd64.sha256sum
        asset_order: [deb, '', appimage, tar.gz]
    
        asset_list_reverse: "{{ asset_list | map('reverse') }}"
        asset_order_reverse: "{{ asset_order | map('reverse') }}"
    
        result: |
          [{% for ext in asset_order_reverse %}
          {% if ext|length > 0 %}
          {{ asset_list_reverse | select('match', ext) | map('reverse') }},
          {% else %}
          {{ asset_list | reject('search', '\.') }},
          {% endif %}
          {% endfor %}]
    
      tasks:
    
        - debug:
            var: result | flatten
    

    gives

      result | flatten:
      - artifact.deb
      - artifact_amd64
      - artifact.appimage
      - artifact_amd64.tar.gz
      - artifact_arm64.tar.gz