Search code examples
ansibleglobsubdirectory

Get a non-recursive list of local directories (containing templates) in Ansible


I have a bunch of directories containing similar template files in an ansible role.
Something like:

/ansible/
└── roles
    └── webserver
        ├── tasks
        │   └── main.yml
        └── templates
            └── sites
                ├── bar
                │   ├── index.html
                │   ├── script.js
                │   ├── style.css
                │   └── webapp.conf
                ├── baz
                │   ├── index.html
                │   ├── script.js
                │   ├── style.css
                │   └── webapp.conf
                └── foo
                    ├── index.html
                    ├── script.js
                    ├── style.css
                    └── webapp.conf

In the task, I want to list the directories (bar, baz, foo, plus whatever new ones I create there) and apply template actions to their files.

I've tried using with_fileglob but it can't list directories (only files, it ignores directories).

- name: Template index.html
   template:
     src: "sites/{{item}}/index.html"
     dest: "/var/www/{{item}}/index.html"
   with_fileglob: "templates/sites/*"

There is also a regression such that it can't even find the files with a glob on the directory.

- name: Template index.html
   template:
     src: "sites/{{item}}"
     dest: "/var/www/{{item}}/index.html"
   with_fileglob: "templates/sites/*/index.html"

I've tried find, but I can't get it to work on the local templates directory (it seems to find things on the host.)

I've tried filetree

- name: Template index.html
   template:
     src: "sites/{{item.path}}"
     dest: "/var/www/{{item.path}}/index.html"
   with_filetree: "templates/sites/"
   when: item.state == 'directory'

but it:

  • Is recursive (even if I use a when to limit it to directories, it returns sub-directories)
  • Prints annoying messages about all the things it skips

Is there a way to get a list of all my sites in my local template directory?


Solution

  • If you want to use filetree and make it less verbose and up to your need, you can use it as a lookup, and not as a with_* loop.

    This way you can pre-filter it with the help of the selectattr and rejectattr filters:

    For example:

    - debug:
        msg: "{{ item.path }}"
      loop: >-
        {{
          lookup('community.general.filetree', 'templates/sites')
            | selectattr('state', '==', 'directory')
            | rejectattr('path', 'contains', '/')
        }}
      loop_control:
        label: "{{ item.path }}"
    

    In this task:

    • selectattr('state', '==', 'directory') will ensure you are only listing directories
    • rejectattr('path', 'contains', '/') will reject path containing a /, so effectively excluding subdirectories
    • loop_control and its parameter label: "{{ item.path }}" will make the loop less verbose on complex dictionaries

    Or your can use JMESPath, as your partially used it indeed, but the filtering can be part of the query:

    - debug:
        msg: "{{ item }}"
      loop: >-
        {{
          lookup('community.general.filetree', 'templates/sites')
            | json_query('[?state == `directory` && !contains(path, `/`)].path')
        }}
    

    Given the file strucure:

    .
    └── templates
        └── sites
            ├── bar
            │   ├── baz
            │   └── index.html
            └── foo
                └── index.html
    

    Where baz is a subdirectory, the two tasks above would both yield:

    ok: [localhost] => (item=bar) => 
      msg: bar
    ok: [localhost] => (item=foo) => 
      msg: foo