Understanding Ansible conditionals - ansible

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)

Related

Use skip_reason as a condition in a task

Is it possible to use the skip_reason as condition to another task?
Here is the task:
- name: PSP Validation
script: roles/OS_minor_upgrade/files/PSP_validation.sh
ignore_errors: true.
register: PSP_VAL
when: >
not 'VMware' in HWMODEL.stdout
Which output:
TASK [OS_minor_upgrade : PSP Postwork] ******************************************************************************************************************************************************
task path: /home/ansible/linuxpatching_OS_Upgrade/roles/OS_minor_upgrade/tasks/upgrade.yml:264
skipping: [server123] => {
"changed": false,
"skip_reason": "Conditional result was False"
}
Now I want to use the above as condition to execute another task, I tried with the task below but it seem like it is not working.
- name: OSupgrade done
shell: echo {{ inventory_hostname }} "OS Upgrade Done" > OUTGOING-OSUPGRADE-PATCHCOMPLETION/inventory_{{ inventory_hostname }}_{{ '%y%m%d%H%M%S' | strftime }}_Offlineoutput
delegate_to: localhost
when: >
fs_check.rc == 0 and val_etrust.rc == 0 and 'PSP Installation is successfully completed' in PSP_VAL.stdout or 'Conditional result was False' in PSP_VAL.skip_reason
How can this be achieved?
Technically, you can use 'skip_reason' as any other variable, but I STRONGLY suggest you not to.
The reason is that person, reading your code (you, 1 week later) would be in a total loss over such decision.
If you have important information about your host, you can use set_fact module to update your host information. Further tasks can use this to make decisions.
- name: Update vmware info
set_fact:
vmware_flag: ('VMware' in HWMODEL.stdout)
- name: PSP Validation
script: roles/OS_minor_upgrade/files/PSP_validation.sh
failed_when: false
register: PSP_VAL
when: not vmware_flag
- name: OSupgrade done
delegate_to: localhost
copy:
dest: OUTGOING-OSUPGRADE-PATCHCOMPLETION/inventory_{{ inventory_hostname }}_{{ '%y%m%d%H%M%S' | strftime }}_Offlineoutput
content: '{{ inventory_hostname }} "OS Upgrade Done"'
when: (some other conditions) or vmware_flag
There are specific ansible tests to verify the registered result of a task
In your specific case:
when: <...all other conditions...> or PSP_VAL is skipped

Select with_items with conditional on each item in Ansible task?

I have a list of proxies that need to be selected based on the cluster location. For example, my cluster names are 'abc' and 'def'. All nodes on clusters start with the cluster name (e.g. abc1023.net for abc etc.)
I want to select the proxies for pip based on the current inventory_hostname and provide it in the arguments. I tried to use the map of with_items and 'creating' the when condition within the map as per the code below:
- name: run pip on proxy
pip:
name: <package_name>
extra_args: "--proxy item.proxy"
when: "item.when"
with_items:
- {proxy: 'http:abc_proxy:port', when: "'abc' in {{inventory_hostname|lower}}"}
- {proxy: 'http:def_proxy:port', when: "'def' in {{inventory_hostname|lower}}"}
The problem I am facing is that this condition is always perceived as true. I tried replacing when: "'abc' in {{inventory_hostname|lower}}" to when: false and that actually works. That is, making it an explicit false actually returns false but not when I check the condition in string quotes. I think this when within the map is just perceived as true if it contains any value.
How do I explicitly check this condition in the when map? Removing the quotes does not help as it throws syntactical error:
We could be wrong, but this one looks like it might be an issue
with missing quotes. Always quote template expression brackets when
they start a value. For instance:
with_items:
- {{ foo }}
Should be written as:
with_items:
- "{{ foo }}"
exception type: <class 'yaml.parser.ParserError'>
Other solutions tried
Added the vars within the task
- name: run pip on proxy
vars:
abc_status: "{{ true if 'abc' in {{inventory_hostname|lower}} else false }}"
def_status: "{{ true if 'def' in {{inventory_hostname|lower}} else false }}"
pip:
name: <package_name>
extra_args: "--proxy item.proxy"
when: "item.when"
with_items:
- {proxy: 'http:abc_proxy:port', when: abc_status}
- {proxy: 'http:def_proxy:port', when: def_status}
2.Added the task to set_fact
- set_fact:
abc_status: true
when: inventory_hostname|lower is match('abc.*')
- set_fact:
def_status: true
when: inventory_hostname|lower is match('def.*')
Tested the false case
I tested the false case in the following ways on abc cluster:
- name: run pip on proxy
vars:
abc_status: "{{ true if 'abc' in {{inventory_hostname|lower}} else false }}"
def_status: "{{ true if 'def' in {{inventory_hostname|lower}} else false }}"
pip:
name: <package_name>
extra_args: "--proxy item.proxy"
when: "item.when"
with_items:
- {proxy: 'http:def_proxy:port', when: def_status}
This should always fail as the proxy as well as the when condition is checking on def cluster whereas it is running on abc cluster. But I get the following Ansible output:
TASK [<project> : run pip on proxy] ************************************************
changed: [abc1023.net] => (item={u'when': u'def_status', u'proxy': u'http:def_proxy:port'})
This is the output I always get with other tried solutions as well.
Question
Even after trying above different solutions, when: "item.when" always return true (even when it should return false). How can I fix this? Is there any better solution to implement my use case?
For completeness, I am using ansible 2.4.1.0.
TL;DR;
Here, you are trying to assess that the string "item.when" is a boolean that is true, which it is, because, a non empty string will result in a true statement.
Remove your doubles quotes around the when condition and you should be good to go.
The warning you get from Ansible is about when and only this statement, which is always a raw Jinja2 expression.
This is easy to do in Ansible with the when clause, which contains a raw Jinja2 expression without double curly braces (see group_by – Create Ansible groups based on facts).
Source: https://docs.ansible.com/ansible/latest/user_guide/playbooks_conditionals.html#the-when-statement, emphasis, mine.
Given your actual playbook, a possible solution could be:
- name: run pip on proxy
pip:
name: <package_name>
extra_args: "--proxy {{ item.proxy }}"
when: item.proxy_ref in inventory_hostname|lower
with_items:
- proxy: 'http:abc_proxy:port'
proxy_ref: 'abc'
- proxy: 'http:def_proxy:port'
proxy_ref: 'def'
Another one could be:
- name: run pip on proxy
pip:
name: <package_name>
extra_args: "--proxy {{ item.proxy }}"
when: item.when
with_items:
- proxy: 'http:abc_proxy:port'
when: "{{ 'abc' in inventory_hostname|lower }}"
- proxy: 'http:def_proxy:port'
when: "{{ 'def' in inventory_hostname|lower }}"
So, in short, here, you are trying to assess that the string "item.when" is a boolean that is true, which it is, because, a non empty string will result in a true statement.
Remove your doubles quotes around the when condition and you should be good to go.
PS: try not to mix the JSON syntax with the YAML one, when possible
Q: "Is there any better solution to implement my use case?"
A: You might be better off with the concatenation of the proxy. For example
- name: run pip on proxy
pip:
name: "{{ package_name }}"
extra_args: "--proxy {{ my_proxy }}"
vars:
my_prefix: "{{ inventory_hostname[0:3] }}"
my_proxy: "{{ 'http:' ~ my_prefix ~ '_proxy:port' }}"

Invoke multiple roles in Ansible with where condition

Is there a possibility to invoke roles depends on the when condition OR may be use Ansible handlers?
I have a below playbook which gets the current status of deployment on remote host and if and only required then perform the next steps. Below is the validation.yml from validations role (1st one to invoke) which does the validations -
---
- name: Getting the status of current deployment
stat:
path: "{{ tomcat_symlink_path }}"
register: p
- set_fact:
current_release: "{{ p.stat.lnk_target.split('/')[4] | regex_replace('^Release(.*)$', '\\1') }}"
- debug:
msg: "The currently deployed release is : {{ p.stat.lnk_target.split('/')[4] | regex_replace('^Release(.*)$', '\\1') }}"
- name: Copying Application Configuration files and get the checksum
template:
src: "{{ item }}"
dest: "{{config_location}}/{{ item | basename | regex_replace('.j2$', '') }}"
mode: 0755
with_fileglob:
- /temp/env/*.j2
register: config_var
- block:
- name: "Exit the deployment if no changes required...."
debug:
msg: "Target Release and currently deployed release is same OR no configuration changed required.. so Exiting the Deployment!!!!"
- meta: end_play
when: myvm_release_version == current_release and config_var.changed == false
Now depends on the above 2 variables. I need to invoke roles. For example -
if config_var.changed == true and myvm_release_version == current_release then invoke only roles stoptomcat and starttomcat and exit the deployment because it is just the config change so only restart tomcat is required.
if only config_var.changed == false and myvm_release_version != current_release then continue with the playbook which will execute everything and all the roles
This may be a weird requirement but may be someone expert can throw some light on it.
It's a common requirement
You can include role with when condition as simple as following..
Solution: 1
you can not refer two of more than two task with when condition, only one task is allowed,
simple hack can be include a external playbook In that conditional task.
Solution: 2
Your Ansible code till "register: config_var"
- name: include conditional role
include_role: "{{item}}"
when: config_var.changed == true and myvm_release_version == current_release
with_items:
- "stoptomcat"
- 'starttomcat"
- name: block of code
block:
// conditional ansible tasks
when: config_var.changed == false and myvm_release_version != current_release
I was able to find a solution as below using meta
- block:
- name: "Doing Configuration Changes...."
include_role:
name: '{{ roleinputvar }}'
loop:
- stoptomcat
- starttomcat
loop_control:
loop_var: roleinputvar
- meta: end_play
when: config_var.changed == true and myvm_release_version == current_release
Just posting so it might help others.

Unwrap ansible result variable

This is something I often do
- name: check whether ready
shell: do_something_complex
changed_when: false
ignore_errors: true
register: result
- name: make it ready
when: result.rc != 0
It is messy when there are many tasks after that, which perform the same check.
I would like instead to wrap the result somehow, and use it like this:
- name: do this if ready
when: isReady
- name: do that if not ready
when: not isReady
How can I do that? (Preferably without intermediate dummy tasks that exist just to set variables.)
That's the thing that import_tasks:, or even custom modules, are designed to solve:
# tasks/make_ready.yml
- name: check whether ready
shell: '{{ check_ready_shell }}'
changed_when: false
# you'll want to be **very careful** using this
ignore_errors: true
register: isReady
- name: make it ready
shell: '{{ make_ready_shell }}'
when: isReady.rc != 0
then, in your main playbook:
- import_tasks: make_ready.yml
vars:
check_ready_shell: echo 'hello from checking'
make_ready_shell: systemctl start-the-thing-or-whatever
- import_tasks: make_ready.yml
vars:
check_ready_shell: echo 'hello from other checking'
make_ready_shell: systemctl start-the-other-thing

Conditionally define variable in Ansible

I want to conditionally define a variable in an Ansible playbook like this:
my_var: "{{ 'foo' if my_condition}}"
I would like the variable to remain undefined if the condition does not resolve to true.
Ansible gives the following error if I try to execute the code:
fatal: [foo.local] => {'msg': 'AnsibleUndefinedVariable: One or more undefined
variables: the inline if-expression on line 1 evaluated
to false and no else section was defined.', 'failed': True}
Why is this an error anyway?
The complete case looks like this:
{role: foo, my_var: "foo"}
If my_var is defined, the role does something special. In some cases, I don't want the role to do this. I could use when: condition, but then I would have to copy the whole role block. I could also use an extra bool variable, but I would like a solution without having to change the "interface" to the role.
Any ideas?
You could use something like this:
my_var: "{{ 'foo' if my_condition else '' }}"
The 'else' will happen if condition not match, and in this case will set a empty value for the variable. I think this is a short, readable and elegant solution.
This code may help you to define a variable with condition.
- hosts: node1
gather_facts: yes
tasks:
- name: Check File
shell: ls -ld /etc/postfix/post-install
register: result
ignore_errors: yes
- name: Define Variable
set_fact:
exists: "{{ result.stdout }}"
when: result|success
- name: Display Variable
debug: msg="{{ exists }}"
ignore_errors: yes
So here the exists will display only if the condition is true.
My example, after https://stackoverflow.com/a/43403229/5025060:
vars:
sudoGroup: "{{ 'sudo' if ansible_distribution == 'Ubuntu' else 'wheel' }}"
Because of the different sudo conventions used by Ubuntu versus other platforms, here I am telling Ansible to set a variable named sudoGroup to sudo if the platform is Ubuntu, otherwise set it to wheel.
Later in my playbook, I combine the variable with Ansible's user module to add either sudo or wheel to an account's secondary groups depending on the OS Ansible is running on:
- name: Add or update bob account
user:
name: bob
uid: 3205
groups: "{{ sudoGroup }}"
append: yes
NOTES:
Double quotes around the {{ variable }} are required in the user: groups: definition above.
Once I define sudoGroup as above in my playbook's global vars: section, Ansible configures it at run time (based on ansible_distribution) for each target I define in my hosts: section.
I believe you're after the default(omit) filter. (Reference).
As per the example, mode will behave like it wasn't set at all for the first two items in the loop.
- name: touch files with an optional mode
file:
dest: "{{item.path}}"
state: touch
mode: "{{item.mode|default(omit)}}"
loop:
- path: /tmp/foo
- path: /tmp/bar
- path: /tmp/baz
mode: "0444"
This can be set as with bool:
- name: Conditional (true and false)
set_fact:
my_boolean_set_to_be: "{{ 'true' if my_var == 'foo' else 'false' }}"
- name: Display Variable
debug: msg="{{ my_boolean_set_to_be }}"
This can be set as for more conditionals like 'if-ifelse-else' statements:
- name: Conditional for 'my_var' (2 options and one default)
set_fact:
my_var_set_to_be: "{{ 'breakfast' if my_var == 'morning' else 'lunch' if my_var == 'afternoon' else 'dinner' }}"
- name: Display Variable
debug: msg="{{ my_var_set_to_be }}"

Resources