Unable to set multiple conditions in when clause for ansible roles - ansible

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

Related

When to use "!=" and "is not"

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

Understanding Ansible conditionals

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)

ansible task with condition

I am using the below task in my play book and trying to set condition using "when" to trigger the task.
the blow one is working
ansible-playbook vdomqury.yml -e adom=In-Adom-01 -e FGT=FGVM04TM19001830 -e vdom= -vvv
if i remove var vdom playbook stopped
ansible-playbook vdomqury.yml -e adom=In-Adom-01 -e FGT=FGVM04TM19001830 -vvv
tasks:
- name: USE CUSTOM TYPE TO QUERY AVAILABLE SCRIPTS
f_query:
adom: "{{ adom }}"
object: "custom"
custom_endpoint: "/pm/config/adom/{{ adom }}/_package/status/{{ FGT }}/{{ vdom }}"
register: results
tags: ADDLAG
when: vdom != ""
- name: USE CUSTOM TYPE TO QUERY AVAILABLE SCRIPTS
f_query:
adom: "{{ adom }}"
object: "custom"
custom_endpoint: "/pm/config/adom/{{ adom }}/_package/status/{{ FGT }}"
register: results
tags: ADDLAG
when: vdom == "" or vdom is not defined
Change the conditions to
when: vdom|default("")|length > 0
respectively
when: vdom|default("")|length == 0
You should check if vdom is defined before making a comparison on it, otherwise the interpreter will unsuccessfully try to fetch its value. This should work (can't check right now): when: vdom is defined and vdom != "".

How to nicely split on multiple lines long conditionals with OR on ansible?

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)

Ansible: Multiple and/or conditionals in when clause

I am having issues when trying to use multiple and/or conditionals in a when statement to decide whether a task needs to be ran or not. Basically I am making a playbook to do automated system patching with options for security patches, kernel only patches and to specify packages in a var file.
I run the playbook with the following commands and define the variables through extended variables option (-e)
ansible-playbook site.yml -i inventory --ask-vault -u (username) -e "security=true restart=true" -k -K
By default the playbook will update every package on the system except kernel but I would like to skip that action if I specify any of a few variables. The code I have is the following:
- name: Update all packages
yum:
name: "*"
state: latest
exclude: "kernel*"
when: security is not defined or kernel is not defined or specified_packages
is not defined and ansible_os_family == "RedHat"
Ive tried all of the following combinations:
when: (ansible_os_family == "RedHat") and (security is defined or kernel is defined or specified_packages is defined)
when: (ansible_os_family == "RedHat") and (security == true or kernel == true or specified_packages == true ) <- this case throws a not defined error because i don't define all variables every time i run the playbook
when: ansible_os_family == "RedHat"
when: security is defined or kernel is defined or specified_packages is defined
Note: I am aware and have used an extra variable such as "skip" to skip this task and use the when clause when: ansible_os_family == "RedHat" and skip is not defined but would prefer not have my users need to use an extra variable just to skip this default action.
I also am not using tags as I am gathering a list of packages before and after the upgrade to compare and report in the end so I wont be able to run those as they are local action commands. This is why I'm using one role with multiple tasks turned on and off via extended variables. I am open to any suggestion that rewrites the playbook in a more efficient way as I am sort of a noob.
It was such a simple answer!
The following works:
when: not (security is defined or kernel is defined or specified_packages is defined) and ansible_os_family == "RedHat"
As #techraf noted in comments, defined/undefined is a nasty test...
Refactor like this:
when:
- ansible_os_family == "RedHat"
- security|d('') != '' or kernel|d('') != '' or specified_packages|d('') != ''
Update. Reproducible example:
- hosts: localhost
gather_facts: no
tasks:
- debug:
msg: hello
when:
- '"RedHat" == "RedHat"'
- security|d('') != '' or kernel|d('') != '' or specified_packages|d('') != ''
execution:
ansible-playbook -e kernel=true playbook.yml
PLAY [localhost] ***************************************************************
TASK [debug] *******************************************************************
ok: [localhost] => {
"msg": "hello"
}
PLAY RECAP *********************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0
versions:
$ pip list | grep -iP 'ansible|jinja'
ansible (2.2.1.0)
Jinja2 (2.8)
I just had a similar problem needing to test two different variables to see if they were "true", but they don't always exist in the output json. The basic logic needed is:
( a is defined ) and ( a == 'present' or a == 'reinstalled' )
in this case, a is "install_vs2022_status.invocation.module_args.state", and the following three different scenarios produced the correct result:
direct:
when: (install_vs2022_status.invocation.module_args.state is defined) and (install_vs2022_status.invocation.module_args.state == 'reinstalled' or install_vs2022_status.invocation.module_args.state == 'present')
distributed:
when: (install_vs2022_status.invocation.module_args.state is defined and install_vs2022_status.invocation.module_args.state == 'present') or (install_vs2022_status.invocation.module_args.state is defined and install_vs2022_status.invocation.module_args.state == 'reinstalled')
broken into lines that imply 'and' or 'intersection', except it stops if the first item evaluates to false.
when:
- install_vs2022_status.invocation.module_args.state is defined
- install_vs2022_status.invocation.module_args.state == 'reinstalled' or install_vs2022_status.invocation.module_args.state == 'present'
The most important factor in each case is that the test for existence happens first, in order to prevent evaluation of a variable that doesn't exist.

Resources