Evgeniy Berezovsky
Evgeniy Berezovsky

Reputation: 19258

Nest with_filetree and loop

How can I nest with_filetree and a loop?

Here is my attempt using a block:

- name: Deploy
  hosts: all
  connection: ssh
  become: true

  vars:
    instances:
      abcd:
        admin_port: 12345
  
  tasks:
    - name: Template cfg
      vars:
        file_path: "{{ item.path }}"
      block:
        - name: Templating config files
          vars:
            instance: "{{ item.value }}"
          template:
            src: "config-templates/{{ file_path }}"
            dest: "{{ install_dir }}/{{ instance.name }}/"
          loop: "{{ instances | dict2items }}"
      with_filetree: "config-templates"
      when: item.state == 'file'

But ansible complains:

ERROR! 'with_filetree' is not a valid attribute for a Block

I must be missing something obvious, but I don't see how to do this. It does not seem to work using with_nested/with_cartesian.

Please help.

Upvotes: 3

Views: 1536

Answers (2)

Zeitounator
Zeitounator

Reputation: 44809

Using include to nest loops is the only viable solution when you have several loops in the include task.

Meanwhile on most scenario including yours, you can reduce the work to a single loop. The basic idea: rather than nesting loops, you create a list with all elements to loop over. In this case, the product filter should do the job.

The other "trick" (since there are no real examples in the doc for filetree...) is to remember that a with_<some_lookup> loop can generally be transformed to loop: {{ lookup|query('some_lookup', args....) }}.

Putting it all together, here is a (not fully tested) example that should meet your requirement:

- name: Templating from filetree and instances
  vars:
    file: "{{ item.0 }}"
    instance: "{{ item.1.value }}"
  template:
    src: "config-templates/{{ file.path }}"
    dest: "{{ install_dir }}/{{ instance.name }}/"
  loop: "{{ lookup('filetree', 'templates/config') | product(instances | dict2items) | list }}"
  when: file.state == 'file'

Edit: not trying to push towards "MY !!!" solution :), but if you don't have too many tasks, here is a possible scenario to keep everything in the same file.

Edit2: You actually don't even need a task, especially if your play targets several hosts. Updated with a full example pseudo playbook.

- name: Deploy
  hosts: all
  connection: ssh
  become: true

  vars:
    my_files: "{{ lookup('filetree', 'templates/config') | selectattr('state', 'eq', 'file') | list }}"
    action1_list: ['some', 'list']
    action2_list: ['other', 'list']
    
  tasks:
    - name: action 1
      debug:
        msg: action1
      vars:
        file: item.0
        action1_item: item.1
      loop: "{{ my_files | product(action1_list) | list }}"

    - name: action 2
      debug:
        msg: action2
      vars:
        file: item.0
        action2_item: item.1
      loop: "{{ my_files | product(action2_list) | list }}"

Upvotes: 3

Evgeniy Berezovsky
Evgeniy Berezovsky

Reputation: 19258

Thanks to Zeitounator's comment, I was able to create a solution. Is has a drawback, however, in that the inner loop task(s) need to be put in a separate file.

  tasks:
    - name: Template cfg
      include_tasks: inner-template-tasks.yaml
      with_filetree: "templates/config"
      when: item.state == 'file'

inner-template-tasks.yaml would look something like this:

- name: Templating {{ item.path }}
  vars:
    instance: "{{ loop_item.value }}"
  template:
    src: "config-templates/{{ item.path }}"
    dest: "{{ install_dir }}/{{ instance.name }}/"
  loop: "{{ instances | dict2items }}"
  loop_control:
    loop_var: loop_item

I only wonder why ansible forces us to use a separate file for this...

Upvotes: 1

Related Questions