Ansible list a directories files (ansible 2.9.10)

Viewed 841

So I was recently frustrated by how hard it is to do the a simple find and change permissions on a list of files using Ansible. I achieved it two ways, both work, but I want to know if someone has a better method.

My first method, I just ask Ansible to do the same as I would do in bash, like so:

tasks:
- shell: ls -1 "{{ SomeDir }}"
  register: file_names
- command: chmod 754 "{{ SomeDir }}/{{ item }}"
  loop: "{{ file_names.stdout_lines }}"

I came back to it today to try and find a more Ansible native way of doing the task and came up with this, but it just seems so clunky for such a simple task:

tasks:
- name: "Find all files in the directory"
  find:
    paths: "{{ SomeDir }}"
    file_type: file
  register : find_output

- name: "loop through the .files output and then pick out the .path attribute"
  file:
    path: "{{ item.path }}"
    mode: '0754'
  loop:
    "{{ find_output.files }}"

I'm not great at output manipulation, as you can see. I would have loved it if I could have done something like {{ find_output.files.path }}, but I could not get lookups or flatten operations to work, maybe someone can set me straight.

1 Answers

Just an idea for you to simplify it, since, if I understand it properly, you are just looking to change all the files permissions in a folder, non-recursively.
To achieve this, you could also use the fileglob lookup and its equivalent with_fileglob structure.

Effectively reducing your two tasks in one:

- file:
    path: "{{ item }}"
    mode: "0754"
  with_fileglob: "{{ SomeDir }}/*"

If you want to make a simple list out of a complex list of dictionaries like the file module is yielding you, you can use a combination of the map and list filters.

Here is an example playbook achieving this:

- hosts: all
  gather_facts: no
  
  tasks:
    - debug:
        msg: "{{ faked_find_output.files | map(attribute='path') | list }}"
      vars:
        faked_find_output:
          changed: false
          failed: false
          files:
            - gid: 0
              uid: 0
              path: /path/to/file1
              size: 0
            - gid: 0
              uid: 0
              path: /path/to/file2
              size: 0

This yields:

PLAY [all] ********************************************************************************************************

TASK [debug] ******************************************************************************************************
ok: [localhost] => {
    "msg": [
        "/path/to/file1",
        "/path/to/file2"
    ]
}

PLAY RECAP ********************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

But mind that, this will not work with your use case, as the file module does not accept a list of path.

So in you case, the best you can do with this is:

- file:
    path: "{{ item }}"
    mode: "0754"
  loop: "{{ find_output.files | map(attribute='path') | list }}"

But, to be honest, this is just throwing complexity in for no reason.

Related