3

Let say, I have this directory structure:

# ls /root/ansible_test/
one  two

And the playbooks looks like this:

- name: gathering all dirs
  stat:
    path: /root/ansible_test/{{ item }}/
  register: dir_check
  changed_when: false
  check_mode: no
  loop:
    - "one"
    - "two"
    - "three"

- name: check all of the dirs are created
  set_fact:
    all_dirs_created: true
  when: item.stat.exists == true
  loop: "{{ dir_check.results }}"

- debug:
    msg: "Not all dirs are created!"
  when: all_dirs_created is not defined

My problem is that the "one" and "two" dirs are created, so the fact will be defined because if one dir exists, then the loop will return true. I also tried opposite and checked item.stat.exists == false but if one dir is not created (three) then fact will be created also.

I would like to play the task set_fact only if all of the item in the loop is true or if one of them is false. How do I achieve this in this case?

U880D
  • 8,601
  • 6
  • 24
  • 40
Darwick
  • 357
  • 3
  • 14
  • Parts of the answer can be found under [conditionals for skipped](https://stackoverflow.com/a/71148510/6771046), would it answer your question? – U880D Feb 20 '22 at 19:33
  • 1
    What is the reason you want to do that? Ansible is made to describe a desired state, and thanks to [idempotency](https://docs.ansible.com/ansible/latest/reference_appendices/glossary.html#term-Idempotency), in Ansible one would just create the directories `one`, `two` and `three`, to be sure the desired state is present, and Ansible will deal with not recreating them If they exists already. – β.εηοιτ.βε Feb 20 '22 at 19:45
  • @U880D I think not, or I can't see there is a part which answers my question at all – Darwick Feb 20 '22 at 20:06
  • @β.εηοιτ.βε Ofcourse, for creating dirs it will be easy, but in my case I should use it for another purpose. I just wanted to be simple as much as possible, not used my complicated code. – Darwick Feb 20 '22 at 20:08

1 Answers1

4

Q: set_fact only if all of the items in the loop are true or if one of them is false

A: Count the items. For example

    - set_fact:
        dirs_missing: "{{ _all|int - _exist|int }}"
      vars:
        _all: "{{ dir_check.results|length }}"
        _exist: "{{ dir_check.results|
                    map(attribute='stat.exists')|
                    select|length }}"

gives (in your case)

  dirs_missing: '1'

Now, you can set whatever you want, e.g.

    - name: check all of the dirs are created
      set_fact:
        all_dirs_created: true
      when: dirs_missing|int == 0

    - debug:
        msg: "Not all dirs are created!
              (Exactly {{ dirs_missing }} missing.)"
      when: all_dirs_created is not defined

gives

TASK [check all of the dirs are created] ********************************
skipping: [localhost]

TASK [debug] ************************************************************
ok: [localhost] => 
  msg: Not all dirs are created! (Exactly 1 missing.)

You can simplify the code by using the filter counter from the latest collection Community.General. See Counting elements in a sequence, e.g.

    - name: check all of the dirs are created
      set_fact:
        all_dirs_created: true
      when: _counts[false] is not defined
      vars:
        _counts: "{{ dir_check.results|
                     map(attribute='stat.exists')|
                     community.general.counter }}"

The solutions above counted the items of the list to enable the evaluation of the option if one of them is false. The code can be simplified further if the counting of the items is not necessary. For example, test if all items are true

    - name: check all of the dirs are created
      set_fact:
        all_dirs_created: true
      when: dir_check.results|map(attribute='stat.exists') is all

However, then you have to test the existence of the variable all_dirs_created. More practical is setting both values. Ultimately, the expected functionality of your last two tasks can be reduced to the code below

    - name: check all of the dirs are created
      set_fact:
        all_dirs_created: "{{ dir_check.results|
                              map(attribute='stat.exists') is all }}"
    - debug:
        msg: Not all dirs are created!
      when: not all_dirs_created
Vladimir Botka
  • 58,131
  • 4
  • 32
  • 63
  • I find this a really good example for how [to make use of one attribute from each item in a list of complex variables (Jinja2 `map` filter)](https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html#managing-uuids) and [loops and list comprehensions](https://docs.ansible.com/ansible/latest/user_guide/complex_data_manipulation.html#loops-and-list-comprehensions), +1. – U880D Feb 20 '22 at 20:20
  • Made a bit easyer to not set another fact, insted changed the when condition to: `when: dir_check.results|length - dir_check.results|map(attribute='stat.exists')|select|length == 0` But ofcourse, you gave me the point how to do that, so +1 also. – Darwick Feb 20 '22 at 21:08