Ansible: 2.9
When should you use != or is not in when before include: create_dir.yml in Ansible?
Example code:
- name: "Get stat"
stat:
path: "/tmp"
register: stat_result
- include: create_dir.yml
when: stat_result.exists is not True
First of, you do have two issues in the tasks you are producing here:
you want to use the module file or the module stat but not a strange mixture of both
the stat module would give you a complex type stat containing the key exists, so really you should test stat_result.stat.exists
So, the explanation below is based on the tasks:
- stat:
path: "/tmp"
register: stat_result
- include: create_dir.yml
when: stat_result.stat.exists is not true
Which is a working couple of tasks.
As pointed in Jinja's documentation:
is: Performs a test.
Source https://jinja.palletsprojects.com/en/2.11.x/templates/#other-operators
!=: Compares two objects for inequality.
Source https://jinja.palletsprojects.com/en/2.11.x/templates/#comparisons
But since there are a true() and false() tests defined in Jinja, you could indeed write something like:
when: stat_result.stat.exists is not true()
And so a shorter version would be
when: stat_result.stat.exists is not true
# because the test function receives no parameters
Now indeed this test is similar to
when: stat_result.stat.exists != True
At the exception that the is not true test is more robust and would cope with undefined variables better than != True
For example, if you do comment your stat task, the test
when: stat_result.stat.exists is not true
would succeed, while
when: stat_result.stat.exists != True
would raise a fatal error:
The conditional check 'stat_result.stat.exists != True' failed. The error was: error while evaluating conditional (stat_result.stat.exists != True): 'stat_result' is undefined
Now this test is really not optimal, because you should not do things like when: bool_var is not true you should rater do when: not bool_var, so:
when: not stat_result.stat.exists
Related
My ENV is DEV while the instance_name is myapp44
Thus, i want the role for myapp7 to skip as the when condition fails.
Below is my role that works fine and skips as the when clause fails.
- { role: myapp7, ENV: "{{ENV}}", ACTION: "{{ACTION}}", when: "'myapp7' in instance_name" }
The issue is when i wish to test multiple condition using and condition. I expect it to fail and skip but the role gets invoked.
- { role: myapp7, ENV: "{{ENV}}", ACTION: "{{ACTION}}", when: ENV != 'perf' and "'myapp7' in instance_name" }
The same issue is observed in the debug
- debug:
msg: " Instance myapp7"
when: "'myapp7' in instance_name"
- debug:
msg: " Instance myapp7 with multi condition"
when: ENV != 'perf' and "'myapp7' in instance_name"
tags: trigger
- debug:
msg: " Instance myapp7 with brackets"
when: (ENV != 'perf' and "'myapp7' in instance_name")
tags: trigger
I want all the three to fail but only the first condition fails in the above debug.
Can you please suggest how to write a multiple when condition for a role ?
You're over-quoting things. When you write:
when: ENV != 'perf' and "'myapp7' in instance_name"
You are writing:
when: (ENV != 'perf') and "a nonempty string"
And a non-empty string always evaluates to true, so the second half of that expression is a no-op. You want:
when: "ENV != 'perf' and 'myapp7' in instance_name"
I prefer to use alternate YAML quoting mechanisms for when expressions because I think it makes things easier to read. The following expression is exactly equivalent to the previous one:
when: >-
ENV != 'perf' and 'myapp7' in instance_name
How best to setup an Ansible block and run the block if a command does not exist or have specific output?
If the command does not exist then stdout would not be available.
- name: Check if mybin exists
stat:
path: /usr/sbin/mybin
register: mybin
- name: Get mybin status
command: mybin status
register: status
when: mybin.stat.exists
changed_when: false
- name: This node is not yet configured so run this block
block:
- name: Install req packages
....
when:
- mybin.stat.exists == False
- status.stdout.find('successfully setup') == False
The conditional check 'status.stdout.find('successfully setup') == False' failed. The error was: error while evaluating conditional (status.stdout.find('successfully setup') == False): 'dict object' has no attribute 'stdout'
As the play is written, those two conditions can never both be true at the same time. You have written the conditions as and-ed together. You need or:
when: mybin.stat.exists == False or status.stdout.find('successfully setup') == False
Consider this playbook:
---
- name: test
hosts: localhost
gather_facts: no
tasks:
- name: foo
shell: echo foo # skipped when running in check mode
register: result
- debug: msg="foo"
when: (result is defined)
and (result.stdout == 'foo')
I thought the is defined would result in short circuit evaluation as in other languages, but it doesn't.
If this is run in check mode, I get:
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'stdout'
I know I can ignore the error with ignore_errors: "{{ansible_check_mode}}", but I want to learn how to fix this problem.
How do I rewrite the when clause to prevent referencing undefined variables?
Actually, if you debug the var without a condition, you will see it is defined. It simply does not contain a stdout key since the task was skipped. The correct ways to work arround this (non exhaustive list):
- debug: msg="{{ result.stdout | default('no value') }}
when: result.stdout | default('') == 'foo'
- debug: msg="foo"
when:
- result is not skipped
- result.stdout == 'foo'
Note that since your above shell example is not changing anything on the remote target you can also decide to play it even when running in check mode:
- name: foo
shell: echo foo
check_mode: false
changed_when: false
register: result
- debug: msg="foo"
when: result.stdout == 'foo'
I have a playbook (CIS compliance standard) with multiple tasks and I want to produce a "success" or "failed" depending on the ansible return code.
---
- name: 2.2.# Ensure ### Server is not enabled
block:
- name: Check if ### exists
stat: path=/usr/lib/systemd/system/###.service
register: exists
- name: Disable if exists
service:
name: ###
state: stopped
enabled: no
when: exists.stat.exists
register: result
- name: To File
block:
- name: Success
lineinfile:
dest: ./results/{{ customer }}-{{ scan_type }}-{{ inventory_hostname }}.txt
line: "{{ inventory_hostname }} 2.2.9 success"
insertafter: EOF
delegate_to: localhost
check_mode: False
when: ((result is skipped) or (result.enabled == false))
- name: Failed
lineinfile:
dest: ./results/{{ customer }}-{{ scan_type }}-{{ inventory_hostname }}.txt
line: "{{ inventory_hostname }} 2.2.9 failed"
insertafter: EOF
delegate_to: localhost
check_mode: False
when: ((result is not skipped) or (result.enabled == true))
From my observation, 'result' can have two different outputs depending on if the "Disable if exists" block is triggered.
If it is triggered, it'll give an output based on the "service" module.
If it is skipped, it'll give the generic Ansible output.
I'm fine with that, but what I can't seem to work out is the conditional statement.
when: ((result is not skipped) or (result.enabled == true))
This will always try to resolve both options, so if the module triggers, it will fail because "skipped" is not an attribute of the service module. If it skips, it'll pass, but obviously fail if it ever gets triggered. It's like it wants all conditions to exist before evaluating despite the "or" statement.
What am I doing wrong?
Do you mean result is skipped rather than result is not skipped? In any case, you can solve this using the default filter, which provides a default value if the input expression is undefined. For example:
when: result.enabled|default(false) == true
Of course, since that's a boolean, you can further simplify it to:
when: result.enabled|default(false)
I already know that if you have long conditionals with and between them you can use lists to split them on multiple lines.
Still, I am not aware of any solution for the case where you have OR between them.
Practical example from real life:
when: ansible_user_dir is not defined or ansible_python is not defined or ansible_processor_vcpus is not defined
This line is ugly and hard to read, and clearly would not fit a 79 column.
How can we rewrite it to make it easier to read?
Use the YAML folding operator >
when: >
ansible_user_dir is not defined or
ansible_python is not defined or
ansible_processor_vcpus is not defined
As the ansible documentation states:
Values can span multiple lines using | or >. Spanning multiple lines using a Literal Block Scalar | will include the newlines and any trailing spaces. Using a Folded Block Scalar > will fold newlines to spaces; it’s used to make what would otherwise be a very long line easier to read and edit. In either case the indentation will be ignored.
Additional info can be found here:
https://yaml-multiline.info/
https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html
http://yaml.org/spec/1.2/spec.html#id2760844
While you should use yaml operators and syntax for majority of the Ansible code, you don't have to use yaml operators with when: conditional. Take a look at official docs. When conditional can be really flexible depends on your need. I think it works because when conditionals are already an expression and evaluated by Jinja2 directly, hence you don't use {{ }} in when condition.
Another great reference: https://stackoverflow.com/a/57600771/9720375
---
- name: Test multiline when condition
hosts: all
gather_facts: false
strategy: linear
tasks:
- name: Should run
debug:
msg: "print this if works"
when:
- true
- false or
true
- name: Should run
debug:
msg: "print this if works"
when:
- true
- true or
false
- name: Should run
debug:
msg: "print this if works"
when:
- false or true
- name: Should skip
debug:
msg: "print this if works"
when:
- false
- false or true
- name: Should run
debug:
msg: "print this if works"
when:
false or true
- name: Should skip
debug:
msg: "print this if works"
when:
false or
false
- name: Should run
debug:
msg: "print this if works"
when: (false or true) or (false and true)
- name: Should run
debug:
msg: "print this if works"
when: (false or false) or
(true and true)