Complex with_subelements construct - ansible

I have the list:
final_list:
- result:
name: "val1"
status: true
- result:
name: "val2"
status: true
- skipped:
path: "path1"
- result:
results:
- result:
name: "val4"
status: true
- result:
name: "val5"
status: true
- skipped:
path: "path2"
I would like check if any status in my list is false.
I started with:
- set_fact:
any_false: true
when: (item.0.result is defined and item.0.result.status == false) or (item.1.result is defined and item.1.result.status == false)
with_subelements:
- "{{ final_list }}"
- result.results"
- skip_missing: True
but this task ommit elements from finally_list without results in result.
I would expect iterate by all elements which contain status variable.
I tried also:
with_subelements:
- "{{ final_list }}"
- result.results | default([])
but get error:
msg: msg: the key result should point to a dictionary, got 'None'

I suggest to do this a bit differently: get all individual result elements in a single flattened list, extract the status and check that all elements are true (or not).
The following playbook should be self explanatory (run with -v to see intermediate debug)
---
- hosts: localhost
gather_facts: false
vars:
final_list:
- result:
name: "val1"
status: true
- result:
results:
- result:
name: "valA"
status: false
- result:
name: "valB"
status: true
- skipped:
path: "path2"
- result:
name: "val2"
status: true
- skipped:
path: "path1"
- result:
results:
- result:
name: "val4"
status: true
- result:
name: "val5"
status: true
- skipped:
path: "path2"
# List of all top level elements without nested elements
single_results: "{{ final_list | rejectattr('result.results', 'defined') }}"
# Flattened list of all nested result elements.
# Will work with several nested elements (as you can see from my example data)
nested_results: "{{ final_list | selectattr('result.results', 'defined') | map(attribute='result.results') | flatten }}"
# Single list containing top level + nested result elements
all_results: "{{ single_results + nested_results }}"
# List of all existing status values in the list of result elements
all_status: "{{ all_results | selectattr('result.status', 'defined') | map(attribute='result.status') }}"
tasks:
- name: Show entire list
debug:
var: all_results
verbosity: 1
- name: Show list of status
debug:
var: all_status
verbosity: 1
- name: Make sure all exsiting status are true
assert:
that:
- all_status is all
fail_msg: "At least one status is false"
success_msg: "All status are true"
which gives:
PLAY [localhost] ***********************************************************************************************************************************************************************************************************************
TASK [Show entire list] ****************************************************************************************************************************************************************************************************************
skipping: [localhost]
TASK [Show list of status] *************************************************************************************************************************************************************************************************************
skipping: [localhost]
TASK [Make sure all exsiting status are true] ******************************************************************************************************************************************************************************************
fatal: [localhost]: FAILED! => {
"assertion": "all_status is all",
"changed": false,
"evaluated_to": false,
"msg": "At least one status is false"
}
PLAY RECAP *****************************************************************************************************************************************************************************************************************************
localhost : ok=0 changed=0 unreachable=0 failed=1 skipped=2 rescued=0 ignored=0
And if you modify the data to all true (only pasting the last task...)
TASK [Make sure all exsiting status are true] ******************************************************************************************************************************************************************************************
ok: [localhost] => {
"changed": false,
"msg": "All status are true"
}

Q: "Check if any status is false."
A: Simple brute force
- set_fact:
status: "{{ status|d([]) + [item.split(':')|last|trim|bool] }}"
loop: "{{ (final_list|to_nice_yaml).splitlines() }}"
when: item is match('^.*\\s+status:\\s+.*$')
gives the list of the statuses
status:
- true
- true
- true
- true
See Testing if a list value is True, e.g.
- debug:
msg: No status is False.
when: status is all

Related

Multiple 'and' operators in Ansible 'when' condition

This is the code that I am trying to run
- name: Read and Register Contents of .bash_profile
shell: grep -E 'AB_AG_HOME|AB_AG_LOCAL_ROOT|AB_AG_LOCAL_DIR|AB_AG_CONFIG_DIR|AB_AG_LOG_DIR' /home/username/.bash_profile
register: output
- debug:
msg: "{{ output.stdout_lines }}"
- name: Append AG environment variables in .bash_profile
shell: cat /home/{{ admin_user }}/tmp_bash.profile >> /home/{{ admin_user }}/.bash_profile
when: "'AB_AG_HOME' and 'AB_AG_LOCAL_ROOT' and 'AB_AG_LOCAL_DIR' and 'AB_AG_CONFIG_DIR' and 'AB_AG_LOG_DIR' not in item"
with_items: "{{ output.stdout_lines }}"
- name: Delete the temporary tmp_bash.profile"
file:
path: /home/{{ abinitio_admin_user }}/tmp_bash.profile
state: absent
when I run this code, all the values are repeated 4 times.
Is there anything that is missing?
By looping over the stdout_lines via
with_items: "{{ output.stdout_lines }}"`
the condition
when: "'AB_AG_HOME' and 'AB_AG_LOCAL_ROOT' and 'AB_AG_LOCAL_DIR' and 'AB_AG_CONFIG_DIR' and 'AB_AG_LOG_DIR' not in item"
would only be true if all strings are in one line (item) together.
You may have a look into the following minimal example
---
- hosts: localhost
become: false
gather_facts: false
vars:
result:
stdout_lines:
- "A1"
- "A2"
- "A3"
- "A4"
- "A5"
tasks:
- name: Debug one-line conditional
debug:
msg: "Not in stdout_lines"
when: "'A1' and 'A2' and 'A3' and 'A4' and 'A5' not in result.stdout_lines"
Whereby the first given example get skipped because of the condition, the second example
- name: Debug loop conditional
debug:
msg: "Not in {{ result.stdout_lines[ansible_loop.index0] }}"
when: "'A1' and 'A2' and 'A3' and 'A4' and 'A5' not in item"
loop: "{{ result.stdout_lines }}"
loop_control:
extended: true
will result into an output of
TASK [Debug loop conditional] ******
ok: [localhost] => (item=A1) =>
msg: Not in A1
ok: [localhost] => (item=A2) =>
msg: Not in A2
ok: [localhost] => (item=A3) =>
msg: Not in A3
ok: [localhost] => (item=A4) =>
msg: Not in A4
For further debugging you could also use the assert module – Asserts given expressions are true.

Ansible conditionals with with_items/nested

Given the following tasks:
- name: Gather security group info
amazon.aws.ec2_group_info:
filters:
"tag:vpn_ports": "*"
register: sec_group_info_output
- name: Extract security groups and ports
set_fact:
vpn_groups: "{{ vpn_groups + [{ 'group_id': item.group_id, 'ports': item.tags.vpn_ports.split(',') }] }}"
with_items:
- "{{ sec_group_info_output | json_query('security_groups') }}"
vars:
vpn_groups: []
when: sec_group_info_output != []
- name: Generate list with CIDRs
set_fact:
vpn_rules: "{{ vpn_rules + [{ 'group_id': item.0.group_id , 'port': item.1, 'cidr': item.2 }] }}"
with_nested:
- "{{ vpn_groups|subelements('ports') }}"
- "{{ cidr_ranges }}"
vars:
vpn_rules: []
when: sec_group_info_output != []
I am trying to skip the last two tasks if the first task returns an empty set.
My understanding is that the when conditional is evaluated for every loop, and not just for the task as a whole.
The below therefor gives me:
TASK [security_groups : Gather security group info] ****************************
ok: [localhost]
TASK [security_groups : Extract security groups and ports] *********************
TASK [security_groups : Generate list with CIDRs] ******************************
fatal: [localhost]: FAILED! => {"msg": "obj must be a list of dicts or a nested dict"}
PLAY RECAP *********************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=1 skipped=1 rescued=0 ignored=0
🚨 Error: The command exited with status 2
How would I go about fixing this error? I've tried putting |default([]) into my nested_items like below:
- name: Generate list with CIDRs
set_fact:
vpn_rules: "{{ vpn_rules + [{ 'group_id': item.0.group_id , 'port': item.1, 'cidr': item.2 }] |default([])}}"
with_nested:
- "{{ vpn_groups|subelements('ports') |default([])}}"
- "{{ cidr_ranges |default([])}}"
vars:
vpn_rules: []
when: sec_group_info_output != []
The error remains the same.
I've also tried putting both tasks in a block, but this had no effect and the error remains the same.
How would I be able to skip these tasks based on my condition?
Firstly, your condition is completely wrong. sec_group_info_output is a registered variable, so it can never be equal to an empty list. It will always be a dictionary containing information about the task execution. In order to have a chance of working as intended, it would need to be:
when: sec_group_info_output.security_groups != []
# more idiomatically, an empty list is false so you can just treat the value as a boolean
when: sec_group_info_output.security_groups
# or you can check the length
when: sec_group_info_output['security_groups'] | length > 0
However, in this case you don't need conditions at all. You're looping over the same list you're checking, and an empty loop will not execute any tasks. You just need a | default([]) in the loop definition on the third task in case the second didn't execute, and everything's fine.
- name: Gather security group info
amazon.aws.ec2_group_info:
filters:
"tag:vpn_ports": "*"
register: sec_group_info_output
- name: Extract security groups and ports
set_fact:
vpn_groups: "{{ vpn_groups | default([]) + [{ 'group_id': item.group_id, 'ports': item.tags.vpn_ports.split(',') }] }}"
loop: "{{ sec_group_info_output.security_groups }}"
- name: Generate list with CIDRs
set_fact:
vpn_rules: "{{ vpn_rules | default([]) + [{ 'group_id': item.0.0.group_id , 'port': item.0.1, 'cidr': item.1 }] }}"
loop: "{{ vpn_groups | default([]) | subelements('ports') | product(cidr_ranges) }}"
{{ vpn_groups | subelements('ports') | default([]) }} was headed in the right direction, but you put the default() in the wrong place. It needs to be before the subelements() filter so that that filter receives an empty list, not an undefined variable.

Registering variables from a looped task and conditionals for skipped tasks

I'm trying to figure out a port test task in a playbook. I have two different sets of ports that need to be checked, but only checked if the server hostname has dev/test/stg/eng in it. Then everything else will be considered a production host and the production ports will be checked.
Here is my playbook:
- name: Filebeat Firewall port check | Dev
wait_for:
host: "{{ item.name }}"
port: "{{ item.port }}"
state: started
delay: 0
timeout: 5
loop: "{{ dev_ports }}"
register: devresults
when: ansible_facts['hostname'] | regex_search('dev|tst|test|eng')
- name: Filebeat Firewall port check | Prod
wait_for:
host: "{{ item.name }}"
port: "{{ item.port }}"
state: started
delay: 0
timeout: 5
loop: "{{ prod_ports }}"
when: devresults is skipped
The first task runs as expected when ran against a production server (is skipped) and a development server (ports are checked). However, the second task is skipped when ran against a production server, as well as a development server (this should happen).
Basically, if the first task is skipped the second task should run, and if the first ran the second task should be skipped, this isn't happening. I'm not sure what I'm missing, I'm thinking its the when statement in the second task, or if the loop is causing my issue, any help would be welcomed.
Regarding your question
Basically, if the first task is skipped the second task should run, and if the first ran the second task should be skipped, this isn't happening.
I've setup a short first example
---
- hosts: localhost
become: false
gather_facts: false
tasks:
- name: Create result set
shell:
cmd: echo 'I am not skipped.'
register: result
when: not ansible_check_mode
- name: Show result when not skipped
debug:
var: result
when: not result.skipped | default('false') | bool
- name: Show result when skipped
debug:
var: result
when: result.skipped | default('false') | bool
and found it working when running with ansible-playbook skip.yml and ansible-playbook skip.yml --check.
Depending on the task and used modules, the reason seems to be when a task becomes skipped the result set is
TASK [Show result when skipped] **************************
ok: [localhost] =>
result:
changed: false
skip_reason: Conditional result was False
skipped: true
whereby when not skipped the result set is
TASK [Show result when not skipped] *********************
ok: [localhost] =>
result:
changed: true
cmd: echo 'I am not skipped.'
delta: '0:00:00.123456'
end: '2022-02-16 20:00:00.123456'
failed: false
rc: 0
start: '2022-02-16 20:00:00.000000'
stderr: ''
stderr_lines: []
stdout: I am not skipped.
stdout_lines:
- I am not skipped.
and don't has the skipped flag. Therefore you may introduce filter to Provide default values and to Force the data type.
Regarding your objection
"the example doesn't really applies as it isn't doing any loops"
you may have a look into the result set of the following second example with loop and Extended loop variables.
---
- hosts: localhost
become: false
gather_facts: false
tasks:
- name: Create result set
shell:
cmd: echo 'I am not skipped.'
register: result
when: item is even
loop: "{{ [1,2] }}"
loop_control:
extended: true
label: "{{ item }}"
- name: Show result
debug:
var: result
which produce an output of
TASK [Create result set] *******
changed: [localhost] => (item=2)
TASK [Show result] ******************************
ok: [localhost] =>
result:
changed: true
msg: All items completed
results:
- ansible_loop:
allitems:
- 1
- 2
first: true
index: 1
index0: 0
last: false
length: 2
nextitem: 2
revindex: 2
revindex0: 1
ansible_loop_var: item
changed: false
item: 1
skip_reason: Conditional result was False
skipped: true
- ansible_loop:
allitems:
- 1
- 2
first: false
index: 2
index0: 1
last: true
length: 2
previtem: 1
revindex: 1
revindex0: 0
ansible_loop_var: item
changed: true
cmd: echo 'I am not skipped.'
delta: '0:00:00.123456'
end: '2022-02-16 22:15:00.123456'
failed: false
invocation:
module_args:
_raw_params: echo 'I am not skipped.'
_uses_shell: true
argv: null
chdir: null
creates: null
executable: null
removes: null
stdin: null
stdin_add_newline: true
strip_empty_ends: true
warn: true
item: 2
rc: 0
start: '2022-02-16 22:15:00.000000'
stderr: ''
stderr_lines: []
stdout: I am not skipped.
stdout_lines:
- I am not skipped.
As you can see it is a data structure containing a list of items and it does contain the information about the first skipped item. To access it you may enhance the second example with
- name: Show result when skipped
debug:
msg: "{{ item.item }}"
when: item.skipped | default('false') | bool
loop: "{{ result.results }}"
loop_control:
extended: true
label: "{{ item }}"
and have a look into the output. It also works according the examples of Conditions based on registered variables.
- name: Show result when skipped
debug:
msg: "{{ item.item }}"
when: item is skipped
loop: "{{ result.results }}"
Further Reading
Troubleshoot Ansible Playbooks
Ansible - Check variable type
Discovering the data type

Getting changed/failed hosts list from a previous task | Ansible

All,
Example: If i've got 20 hosts for a playbook and running them with Serial:10, below shell command runs on 10 hosts at a time. Once done handler task is called, wherein the task which creates dict (_dict) doesn't give a dictionary output thus the second task - Failed host - failed with mentioned error.
- name: Run some shell command
shell: "echo 2 > /abcd/abcd.txt"
when: random condition is satisfied
register: update2
ignore_errors: yes
notify: abc_handler
- handler:
- name: abcd_handler
set_fact:
_dict: "{{ dict(ansible_play_hosts|zip(
ansible_play_hosts|map('extract', hostvars, 'update2'))) }}"
run_once: true
- name: Find failed hosts
set_fact:
_failed: "{{ _dict|dict2items|json_query('[?value.failed].key') }}"
run_once: true
Handler First task output:
"changed: false"
"ansible_facts": {
"_dict": "{u'host1': {'stderr_lines': [], u'changed': True,...u'host2':.....u'host10'}"
2nd handler task gives the mentioned error when the dict2items is run for above values.
Thank you.
Q: "List of hosts where a certain task executed, changed something, or got failed."
A: For example, the command makes no changes at test_11 changes the file at test_12, and fails at test_13
- hosts: test_11,test_12,test_13
tasks:
- shell:
cmd: "echo 2 > /tmp/test/abcd.txt"
creates: /tmp/test/abcd.txt
register: update1
ignore_errors: true
TASK [shell] ***********************************************************
changed: [test_12]
fatal: [test_13]: FAILED! => changed=true
cmd: echo 2 > /tmp/test/abcd.txt
delta: '0:00:00.045992'
end: '2021-04-25 23:22:31.623804'
msg: non-zero return code
rc: 2
start: '2021-04-25 23:22:31.577812'
stderr: '/bin/sh: cannot create /tmp/test/abcd.txt: Permission denied'
stderr_lines: <omitted>
stdout: ''
stdout_lines: <omitted>
...ignoring
ok: [test_11]
Let's create a dictionary with the data first, e.g.
- set_fact:
_dict: "{{ dict(ansible_play_hosts|
zip(ansible_play_hosts|
map('extract', hostvars, 'update1'))) }}"
run_once: true
gives
_dict:
test_11:
changed: false
cmd: echo 2 > /tmp/test/abcd.txt
failed: false
rc: 0
stdout: skipped, since /tmp/test/abcd.txt exists
stdout_lines:
- skipped, since /tmp/test/abcd.txt exists
test_12:
changed: true
cmd: echo 2 > /tmp/test/abcd.txt
delta: '0:00:00.032474'
end: '2021-04-25 23:14:36.361510'
failed: false
rc: 0
start: '2021-04-25 23:14:36.329036'
stderr: ''
stderr_lines: []
stdout: ''
stdout_lines: []
test_13:
changed: true
cmd: echo 2 > /tmp/test/abcd.txt
delta: '0:00:00.054980'
end: '2021-04-25 23:14:35.565811'
failed: true
msg: non-zero return code
rc: 2
start: '2021-04-25 23:14:35.510831'
stderr: '/bin/sh: cannot create /tmp/test/abcd.txt: Permission denied'
stderr_lines:
- '/bin/sh: cannot create /tmp/test/abcd.txt: Permission denied'
stdout: ''
stdout_lines: []
Note that test_11 is reported ok not skipped despite the registered variable showing "stdout: skipped, since /tmp/test/abcd.txt exists".
The analysis is now trivial, e.g.
- set_fact:
_failed: "{{ _dict|dict2items|json_query('[?value.failed].key') }}"
run_once: true
gives the list of the failed hosts
_failed:
- test_13
and the next task
- set_fact:
_changed: "{{ (_dict|dict2items|json_query('[?value.changed].key'))|
difference(_failed) }}"
_ok: "{{ _dict|dict2items|json_query('[?value.changed == `false`].key') }}"
run_once: true
gives
_changed:
- test_12
_ok:
- test_11
Note that
The failed hosts need to be subtracted from the changed hosts because failed hosts are also reported as changed.
There will be no registered variable if a task is skipped.
Serial
Split the playbook into 2 plays if serial is used. e.g.
shell> cat playbook.yml
- hosts: all
serial: 10
tasks:
- shell:
cmd: "echo 2 > /tmp/test/abcd.txt"
creates: /tmp/test/abcd.txt
register: update1
ignore_errors: true
- hosts: all
tasks:
- set_fact:
_dict: "{{ dict(ansible_play_hosts|zip(
ansible_play_hosts|map('extract', hostvars, 'update1'))) }}"
run_once: true
It seems you would like to get the hosts on which the command task (shown in the question) failed or changed, and then target them for some other tasks.
There are two things required for this:
If the command task fails, playbook execution will stop and hence none of the following tasks will run. So we need to add ignore_errors flag to the task
add_host module to create a new group of hosts when the task failed or changed
So finally tasks like below should do the trick:
- hosts: some_group
serial: 1
- name: update file count
shell: "echo 2 > /home/ec2-user/abcd.txt"
when:
- count.stdout == "1"
register: update1
ignore_errors: true
- name: conditionally add the hosts from current play hosts to a new group
add_host:
groups:
- new_group
host: "{{ ansible_hostname }}"
when: >
cmd_stat is failed or
cmd_stat is changed
# Then have a play targeting the new group
- hosts: new_group
tasks:
# Tasks to be performed
Though the use of serial might make the whole playbook run longer if there are lot of hosts.

What causes Ansible to evaluate 'false and true' as 'true'? [duplicate]

I have the following tasks in a playbook I'm writing (results listed next to the debug statement in <>):
- debug: var=nrpe_installed.stat.exists <true>
- debug: var=force_install <true>
- debug: var=plugins_installed.stat.exists <true>
- name: Run the prep
include: prep.yml
when: (nrpe_installed.stat.exists == false or plugins_installed.stat.exists == true or force_install == true)
tags: ['prep']
- debug: var=nrpe_installed.stat.exists <true>
- debug: var=force_install <true>
- debug: var=force_nrpe_install <false>
- name: Install NRPE
include: install-nrpe.yml
when: (nrpe_installed.stat.exists == false or force_install == true or force_nrpe_install == true)
tags: ['install_nrpe']
vars:
nrpe_url: 'http://url.goes.here'
nrpe_md5: 3921ddc598312983f604541784b35a50
nrpe_version: 2.15
nrpe_artifact: nrpe-{{ nrpe_version }}.tar.gz
nagios_ip: {{ nagios_ip }}
config_dir: /home/ansible/config/
And I'm running it with the following command:
ansible-playbook install.yml -i $invFile --extra-vars="hosts=webservers force_install=True"
The first include runs, but the second skips with this output:
skipping: [server1] => {"changed": false, "skip_reason": "Conditional check failed", "skipped": true}
I'm under the impression that the conditional check should pass for all of them as force_install == true evaluates to true which should make the whole when evaluate to true (since it's a series of 'OR's).
How do I get the when to run when the variables are set appropriately?
Edit:
Changing the second when for the Install NRPE include to the following works, but doesn't explain why the other one, Run the prep runs appropriately:
Working:
when: (not nrpe_installed.stat.exists or force_install or force_nrpe_install)
Also working:
when: (nrpe_installed.stat.exists == false or plugins_installed.stat.exists == true or force_install == true)
Not working:
when: (nrpe_installed.stat.exists == false or force_install == true or force_nrpe_install == true)
The truncated (duplicates removed) output of that particular section of the play is:
TASK [debug] *******************************************************************
ok: [server2] => {
"nrpe_installed.stat.exists": true
}
TASK [debug] *******************************************************************
ok: [server2] => {
"plugins_installed.stat.exists": true
}
TASK [debug] *******************************************************************
ok: [server2] => {
"force_install": true
}
TASK [Run the prep] ************************************************************
included: /tasks/nrpe-install/prep.yml for server2, server3, server4, server5, server6, server7
TASK [Prep and configure for installation | Install yum packages] **************
ok: [server6] => (item=[u'gcc', u'glibc', u'glibc-common', u'gd', u'gd-devel', u'make', u'net-snmp', u'openssl-devel', u'unzip', u'tar', u'gzip', u'xinetd']) => {"changed": false, "item": ["gcc", "glibc", "glibc-common", "gd", "gd-devel", "make", "net-snmp", "openssl-devel", "unzip", "tar", "gzip", "xinetd"], "msg": "", "rc": 0, "results": ["gcc-4.1.2-55.el5.x86_64 providing gcc is already installed", "glibc-2.5-123.el5_11.3.i686 providing glibc is already installed", "glibc-common-2.5-123.el5_11.3.x86_64 providing glibc-common is already installed", "gd-2.0.33-9.4.el5_4.2.x86_64 providing gd is already installed", "gd-devel-2.0.33-9.4.el5_4.2.i386 providing gd-devel is already installed", "make-3.81-3.el5.x86_64 providing make is already installed", "net-snmp-5.3.2.2-20.el5.x86_64 providing net-snmp is already installed", "openssl-devel-0.9.8e-40.el5_11.x86_64 providing openssl-devel is already installed", "unzip-5.52-3.el5.x86_64 providing unzip is already installed", "tar-1.15.1-32.el5_8.x86_64 providing tar is already installed", "gzip-1.3.5-13.el5.centos.x86_64 providing gzip is already installed", "xinetd-2.3.14-20.el5_10.x86_64 providing xinetd is already installed"]}
TASK [Prep and configure for installation | Make nagios group] *****************
ok: [server2] => {"changed": false, "gid": 20002, "name": "nagios", "state": "present", "system": false}
TASK [Prep and configure for installation | Make nagios user] ******************
ok: [server6] => {"append": false, "changed": false, "comment": "User for Nagios NRPE", "group": 20002, "home": "/home/nagios", "move_home": false, "name": "nagios", "shell": "/bin/bash", "state": "present", "uid": 20002}
TASK [debug] *******************************************************************
ok: [server2] => {
"nrpe_installed.stat.exists": true
}
TASK [debug] *******************************************************************
ok: [server2] => {
"force_install": true
}
TASK [debug] *******************************************************************
ok: [server2] => {
"force_nrpe_install": false
}
TASK [Install NRPE] ************************************************************
skipping: [server2] => {"changed": false, "skip_reason": "Conditional check failed", "skipped": true}
You need to convert the variable to a boolean:
force_install|bool == true
I don't claim I understand the logic behind it. In python any non-empty string should be truthy. But when directly used in a condition it evaluates to false.
The bool filter then again interprets the strings 'yes', 'on', '1', 'true' (case-insensitive) and 1 as true (see source). Any other string is false.
You might want to also set a default value in case force_install is not defined, since it would result in an undefined variable error:
force_install|default(false)|bool == true
Yeah... certainly requires testing. I recommend
| bool is good unless you expect undefined vars
var is defined and (var | bool) if you expect undefined vars
default(False) is OK as long and you are happy that these are true if the input is not parsed from YAML. I.e. variables defined through --extra_vars CLI parameter are strings.
"false"
"null"
"defined string"
Hope this helps:
#!/usr/bin/env ansible-playbook
---
- name: Test truthiness
hosts: localhost
gather_facts: False
vars:
truthy_vars:
# TRUE
- True
- 1
- "true"
# FALSE
- "false"
- null
- False
- 0
# ERROR (invalid sytax error stops the loop of the first of these)
- "null"
- "defined string"
# ERROR
# truthy_var_undefined
# FALSE
truthy_var_defined:
tasks:
- name: Test truthy
debug:
msg: "is truthy"
ignore_errors: True # beware, the loo
when: item
loop: "{{ truthy_vars }}"
loop_control:
label: Test {{ item }}
- name: truthy_var_undefined
debug:
when: truthy_var_undefined
ignore_errors: true
- name: truthy_var_defined
debug:
when: truthy_var_defined
- name: Test | default(False)
hosts: localhost
gather_facts: False
vars:
default_pipe_vars:
# TRUE
- True
- 1
- "true"
# beware these:
- "false"
- "null"
- "defined string"
# FALSE
- null
- False
- 0
# FALSE
# default_pipe_undefined
# FALSE
default_pipe_defined:
tasks:
- name: Test | default(False)
debug:
msg: "is not | default(False)"
when: item | default(False)
loop: "{{ default_pipe_vars }}"
loop_control:
label: Test {{ item }}
- name: default_pipe_undefined | default(False)
debug:
when: default_pipe_undefined | default(False)
- name: default_pipe_defined | default(False)
debug:
when: default_pipe_defined | default(False)
- name: Test | bool
hosts: localhost
gather_facts: False
vars:
bool_vars:
# TRUE
- True
- 1
- "true"
# FALSE
- "defined string"
- "null"
- "false"
- null
- False
- 0
# ERROR
# bool_undefined
# FALSE
bool_defined:
tasks:
- name: Test bool parsing
debug:
msg: "parsed as true booleans"
when: item | bool
loop: "{{ bool_vars }}"
loop_control:
label: Test {{ item }}
- name: bool_undefined | bool
debug:
when: bool_undefined | bool
ignore_errors: true
- name: bool_defined var | bool
debug:
when: bool_defined | bool
- name: Test is defined and | bool
hosts: localhost
gather_facts: False
vars:
defined_bool_vars:
# TRUE
- True
- 1
- "true"
# FALSE
- "defined string"
- "null"
- "false"
- null
- False
- 0
# FALSE
# defined_bool_undefined
# FALSE
defined_bool_defined:
tasks:
- name: Test bool parsing
debug:
msg: "parsed as true booleans"
when:
- item is defined
- item | bool
loop: "{{ defined_bool_vars }}"
loop_control:
label: Test {{ item }}
- name: defined_bool_undefined | bool
debug:
when:
- defined_bool_undefined is defined
- defined_bool_undefined | bool
ignore_errors: true
- name: defined_bool_defined var | bool
debug:
when:
- defined_bool_defined is defined
- defined_bool_defined | bool
https://gist.github.com/kcd83/4ea23d201c271b58f1c4ef7844408657

Resources