I'm trying to run a few Ansible commands depending on the outcome of a conditional check in my tasks based on the presence of a boolean and am tying myself up in knots trying to get it to work.
The desired outcome is as follows, and should be run for each item in the hosts
array:
- Check if the
lynchburg
variable is true
or false
- If the
lynchburg
variable is true
:
- Setup the folder structure if it doesn't already exist
- Create
gulpfile.js
from the included template file if it doesn't already exist
- If the
lynchburg
variable is false
:
- Don't setup the folder structure
- Don't create
gulpfile.js
- Regardless of whether the
lynchburg
is true
or false
, skip all tasks on subsequent reprovisions, as the folder structure and gulpfile.js
will either be there or not (although this should be taken care of by the logic in the conditionals)
From the code included below, I'm seeing the following results:
- When
lynchburg
is true
, the provision runs through as expected
the first time around and any subsequent reprovisions also work as
expected.
- When
lynchburg
is false
, the inc
folder structure is created, however gulpfile.js
isn't.
The main loop the task should run through is the following:
vhosts:
- site_name: sample
lynchburg: true
and the tasks are as follows:
# Create variable to check whether Lynchburg has already been installed
- name: Check if Lynchburg assets folders have been previously created
stat:
path: /var/www/{{ item.site_name }}/inc
with_items: "{{ vhosts }}"
when: item.lynchburg == true
register: lynchburg_assets
- name: Check if Lynchburg gulpfile.js has been previously created
stat:
path: /var/www/{{ item.site_name }}/gulpfile.js
with_items: "{{ vhosts }}"
when: item.lynchburg == true
register: lynchburg_gulpfile
- name: Create inc folder
with_items: "{{ lynchburg_assets.results }}"
file:
path: /var/www/{{ item.item.site_name }}/inc
state: directory
when: item.stat.isdir is undefined and item.item.lynchburg == true
- name: Create scss folder
with_items: "{{ lynchburg_assets.results }}"
file:
path: /var/www/{{ item.item.site_name }}/inc/scss
state: directory
when: item.stat.isdir is undefined and item.item.lynchburg == true
- name: Create js folder
with_items: "{{ lynchburg_assets.results }}"
file:
path: /var/www/{{ item.item.site_name }}/inc/js
state: directory
when: item.stat.isdir is undefined and item.item.lynchburg == true
- name: Create img folder
with_items: "{{ lynchburg_assets.results }}"
file:
path: /var/www/{{ item.item.site_name }}/inc/img
state: directory
when: item.stat.isdir is undefined and item.item.lynchburg == true
- name: Create fonts folder
with_items: "{{ lynchburg_assets.results }}"
file:
path: /var/www/{{ item.item.site_name }}/inc/fonts
state: directory
when: item.stat.isdir is undefined and item.item.lynchburg == true
- name: Create gulpfile.js
with_items: "{{ lynchburg_gulpfile.results }}"
template:
src: gulpfile.js
dest: /var/www/{{ item.item.site_name }}/gulpfile.js
when: item.stat.exists is defined and item.stat.exists == false and item.item.lynchburg == true
N.B. If there's any other way to create the full folder structure easily without running five different tasks - one for each folder/subfolder - and someone could advise what that is, that'd also be appreciated!
When lynchburg
is false
, the inc
folder structure is created, however gulpfile.js
isn't.
I know how to solve it, I don't know (yet) how to explain it:
Change the order of expressions in the conditional:
when: item.item.lynchburg == true and item.stat.isdir is undefined
For some reason:
when: dummy.key is undefined and false
and
when: false and dummy.key is undefined
give different results.
It works as expected only when checking the top-level variable (dummy is undefined
).
I seem to have fixed this in a moment of blind experimentation. I've removed the conditional check from the folder structure tasks, so it's just checking the raw vhosts
loop, rather than looping through them first and then running the tasks on the results of the loop.
My tasks now look as follows, which seem to work exactly as I'd hope they would:
---
- name: Create inc folder
with_items: "{{ vhosts }}"
file:
path: /var/www/{{ item.site_name }}/inc
state: directory
when: item.lynchburg == true
- name: Create scss folder
with_items: "{{ vhosts }}"
file:
path: /var/www/{{ item.site_name }}/inc/scss
state: directory
when: item.lynchburg == true
- name: Create js folder
with_items: "{{ vhosts }}"
file:
path: /var/www/{{ item.site_name }}/inc/js
state: directory
when: item.lynchburg == true
- name: Create img folder
with_items: "{{ vhosts }}"
file:
path: /var/www/{{ item.site_name }}/inc/img
state: directory
when: item.lynchburg == true
- name: Create fonts folder
with_items: "{{ vhosts }}"
file:
path: /var/www/{{ item.site_name }}/inc/fonts
state: directory
when: item.lynchburg == true
- name: Check if Lynchburg gulpfile.js has been previously created
stat:
path: /var/www/{{ item.site_name }}/gulpfile.js
with_items: "{{ vhosts }}"
when: item.lynchburg == true
register: lynchburg_gulpfile
- name: Create gulpfile.js
with_items: "{{ lynchburg_gulpfile.results }}"
template:
src: gulpfile.js
dest: /var/www/{{ item.item.site_name }}/gulpfile.js
when: item.stat.exists is defined and item.stat.exists == false and item.item.lynchburg == true
I'll leave it to the community to tell me if this is correct or not but, as I say, it seems to work perfectly.
It's possible I'm missing something, but it looks to me like the stat
tasks and the conditional checks that go with them are unnecessary:
In the case of the folder structure, the first time the task runs, the directories will be created. But if it runs again on the same host, the tasks will be skipped, and you'll get output like this:
localhost : ok=3 changed=0 unreachable=0 failed=0
- In the case of the
template
task, you can set its force
option to false
which means the task won't run if the file already exists.
Finally, your folder structure can be created fairly easily in one task by using the with_nested
style of loop (Ansible < 2.5), or a lookup (Ansible ≥ 2.5).
I'd recreate your playbook to look something like this (note that it will create the file structure in its own directory):
---
- hosts: localhost
vars:
vhosts:
- site_name: sample
lynchburg: true
- site_name: sample2
lynchburg: false
tasks:
- name: Create required folder structure.
file:
path: "{{ playbook_dir }}/var/www/{{ item.0.site_name }}/inc/{{ item.1 }}"
state: directory
with_nested:
- "{{ vhosts }}"
- ["scss", "img", "js", "fonts"]
when: item.0.lynchburg
- name: Template gulpfile into place.
template:
src: "{{ playbook_dir }}/gulpfile.js.j2"
dest: "{{ playbook_dir }}/var/www/{{ item.site_name }}/gulpfile.js"
force: false
with_items: "{{ vhosts }}"
when: item.lynchburg
Running this the first time creates the following result and folder structure. Note that:
Ansible reports two changed tasks:
____________
< PLAY RECAP >
------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
localhost : ok=3 changed=2 unreachable=0 failed=0
only the sample
host directory and files are created.
var
└── www
└── sample
├── gulpfile.js
└── inc
├── fonts
├── img
├── js
└── scss
If we run ansible-playbook playbook.yml
again: