Ansible: Multiple and/or conditionals in when clause - ansible

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.

Related

Unable to set multiple conditions in when clause for ansible roles

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

Ansible undefined variables in check mode

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'

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 != "".

Checking for multiple conditions using "when" on single task in ansible

I want to evaluate multiple condition in ansible using when, here is my playbook:
- name: Check that the SSH Key exists
local_action:
module: stat
path: "/home/{{ login_user.stdout }}/{{ ssh_key_location }}"
register: sshkey_result
- name: Generating a new SSH key for the current user it's not exists already
local_action:
module: user
name: "{{ login_user.stdout }}"
generate_ssh_key: yes
ssh_key_bits: 2048
when: sshkey_result.rc == 1 and ( github_username is undefined or github_username |lower == 'none' )
here is my var file for reference:
---
vpc_region: eu-west-1
key_name: my_github_key
ssh_key_location: .ssh/id_rsa.pub
When I try to execute this playbook, I am getting this error:
TASK: [test | Check that the SSH Key exists] **********************************
ok: [localhost -> 127.0.0.1]
TASK: [test | Generating a new SSH key for the current user it's not exists already] ***
fatal: [localhost] => error while evaluating conditional: sshkey_result.rc == 1 and ( github_username is undefined or github_username |lower == 'none' )
FATAL: all hosts have already failed -- aborting
Can somebody point me out that how we can use multiple conditions with ansible on single task.
Thanks
You can use like this.
when: condition1 == "condition1" or condition2 == "condition2"
Link to official docs: The When Statement.
Also Please refer to this gist:
https://gist.github.com/marcusphi/6791404
Adding to https://stackoverflow.com/users/1638814/nvartolomei answer, which will probably fix your error.
Strictly answering your question, I just want to point out that the when: statement is probably correct, but would look easier to read in multiline and still fulfill your logic:
when:
- sshkey_result.rc == 1
- github_username is undefined or
github_username |lower == 'none'
https://docs.ansible.com/ansible/latest/user_guide/playbooks_conditionals.html#the-when-statement
The problem with your conditional is in this part sshkey_result.rc == 1, because sshkey_result does not contain rc attribute and entire conditional fails.
If you want to check if file exists check exists attribute.
Here you can read more about stat module and how to use it.
You can use logical operators to combine conditions. When you have multiple conditions that all need to be true (that is, a logical and), you can specify them as a list:
tasks:
- name: Shut down CentOS 6 systems
ansible.builtin.command: /sbin/shutdown -t now
when:
- ansible_facts['distribution'] == "CentOS"
- ansible_facts['distribution_major_version'] == "6"
Link to the doc : https://docs.ansible.com/ansible/latest/user_guide/playbooks_conditionals.html#the-when-statement
Also you can use default() filter. Or just a shortcut d()
- name: Generating a new SSH key for the current user it's not exists already
local_action:
module: user
name: "{{ login_user.stdout }}"
generate_ssh_key: yes
ssh_key_bits: 2048
when:
- sshkey_result.rc == 1
- github_username | d('none') | lower == 'none'

Ansible Using --extra-vars for conditional includes

I am using Ansible to deploy an environment that may have services distributed or not. I would like to conditionally include playbooks based on arguments I pass to ansible-playbook.
create_server.yml
---
- include: launch_ec2_instance.yml
- include install_postgres.yml
when {{db}} == "Y"
- include install_redis.yml
when {{redis}} == "Y"
Here is how I am calling create_server.yml
ansible-playbook create_server.yml -i local --extra-vars "db=Y redis=N"
Is it possible to do this and if so, how?
Yes. It's possible. You are missing a colon(:) on your when statement.
---
- include: launch_ec2_instance.yml
- include install_postgres.yml
when: {{ db }} == "Y"
- include install_redis.yml
when: {{ redis }} == "Y"
You can also omit the braces ({{ }}):
---
- include: launch_ec2_instance.yml
- include install_postgres.yml
when: db == "Y"
- include install_redis.yml
when: redis == "Y"
#Rico's answer is correct except that it only applies when your include statement is part of a task.
Eg.
---
tasks:
- include install_postgres.yml
when: db == "Y"
If your playbook is just a bunch of includes as your 'create_server.yml' seems to be then 'when' wont work.

Resources