Control structures refer to anything and everything that have an effect on a program’s execution flow. Control structures are mainly of the following two types:
- Conditional
- Iterative
At times, we need to execute code conditionally based on a value of a variable, type of platform, or even a result of some other command. There are times when we also need to iterate multiple objects, such as list hashes or multilevel variables.
We will cover following 3 different kinds of conditional statements in this section
whenfailed_whenchanged_when
Using when statement
The value of the when key is a Jinja2 expression without the curly braces. The task is only executed if this Jinja2 expression evaluates to true. This Jinja2 expression can evaluate the value of a variable. It can contain Jinja2 filters, and it can also include logical operators and groupings.
We will use our existing playbook from last exercise to first understand
how when condition works:
---
- name: when conditional operator
hosts: localhost
gather_facts: false
vars:
x: 10
y: 15
list: [10,20,30]
tasks:
- debug:
msg:
- "The value of x is {{ x }} and the value of y is {{ y }}"
- "Our list contains: {{ list }}"
when: x == y
- debug:
msg: "x is present in the list"
when: x in list
when statement. It should
have same indentation as used for the module name (without hyphen). In
my case I have placed when statement with the same number of spaces as
given to debug module or else you will get syntax error
In this conditional-operator.yml playbook I have defined a condition
using “when” for the first task using debug module. So if the
condition is TRUE i.e. x == y then only the first task will be
executed or else it will be skipped
Similarly in the second task I have added another condition i.e.
if x is present in the list then only the second task will be executed
or else it will be skipped
Let us execute the playbook
[ansible@controller ~]$ ansible-playbook conditional-operator.yml
PLAY [when conditional operator] ***********************************************************************************
TASK [debug] *******************************************************************************************************
skipping: [localhost]
TASK [debug] *******************************************************************************************************
ok: [localhost] => {
"msg": "x is present in the list"
}
PLAY RECAP *********************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
So our first task was skipped as x is not equal to y while the
second task has been executed because x was part of the list
Example-1: Install HTTPD
Let us take some practical example now:
---
- name: Installing HTTPD
hosts: server2
become: true
gather_facts: false
vars:
pkg: httpd
tasks:
- shell: rpm -q httpd | head -n 1
register: result
- set_fact:
result: "{{ result.stdout }}"
- debug:
msg: "Is the {{ pkg }} installed: {{ result }}"
- yum:
name: "{{ pkg }}"
state: latest
when: result | regex_search("not installed")
We will use this playbook install-httpd.yml to install httpd package
on server2 but with a condition i.e. the playbook should install the
package only if the package is “not installed” already. If the pkg
is already installed then “do nothing”
I have written 4 TASKS in this playbook
- Check if
httpdpackage is installed usingshellmodule and register the output intoresultvariable - Use
set_factto store the output intoresultvariable as we are doing some operation runtime and storing the variable we couldn’t usevars(this is covered in Ansible Variables and Data Types chapter) - Using
debugmodule get the output of our variable to make sure it is getting proper output - Use
yummodule to install thepkgbased on thewhencondition. We have usedregex_searchto search for string “not installed” in theresultvariable
Let us execute this playbook. Since I am using rpm command, ansible is
throwing the warning but since we are using rpm command just to get
the package list, the warning can be ignored.
[ansible@controller ~]$ ansible-playbook install-httpd.yml
PLAY [Installing HTTPD] ********************************************************************************************
TASK [shell] *******************************************************************************************************
[WARNING]: Consider using the yum, dnf or zypper module rather than running 'rpm'. If you need to use command
because yum, dnf or zypper is insufficient you can add 'warn: false' to this command task or set
'command_warnings=False' in ansible.cfg to get rid of this message.
changed: [server2]
TASK [set_fact] ****************************************************************************************************
ok: [server2]
TASK [debug] *******************************************************************************************************
ok: [server2] => {
"msg": "Is the httpd installed: httpd-2.4.37-21.module_el8.2.0+494+1df74eae.x86_64"
}
TASK [yum] *********************************************************************************************************
skipping: [server2]
PLAY RECAP *********************************************************************************************************
server2 : ok=3 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
As per the debug module output, since httpd was already installed
our TASK4 was skipped so no installation was performed. Now I have
removed the httpd package from server2 and let’s re-run this
playbook:
[ansible@controller ~]$ ansible-playbook install-httpd.yml
PLAY [Installing HTTPD] ********************************************************************************************
TASK [shell] *******************************************************************************************************
[WARNING]: Consider using the yum, dnf or zypper module rather than running 'rpm'. If you need to use command
because yum, dnf or zypper is insufficient you can add 'warn: false' to this command task or set
'command_warnings=False' in ansible.cfg to get rid of this message.
changed: [server2]
TASK [set_fact] ****************************************************************************************************
ok: [server2]
TASK [debug] *******************************************************************************************************
ok: [server2] => {
"msg": "Is the httpd installed: package httpd is not installed"
}
TASK [yum] *********************************************************************************************************
changed: [server2]
PLAY RECAP *********************************************************************************************************
server2 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
So this time httpd package was successfully installed
Using failed_when statement
If we take our last example playbook which we used to install the
httpd package. I had written 4 TASKS to achieve our agenda, although
we could remove the debug task but there is another way to handle such
use case.
We can use failed_when condition to intentionally fail the script
based on the condition check. I have updated the same playbook with
failed_check condition and also added warn: false to avoid the warning
for using rpm command
---
- name: Installing HTTPD
hosts: server2
become: true
gather_facts: false
vars:
pkg: httpd
tasks:
- shell: rpm -q httpd | head -n 1
args:
warn: false
register: result
failed_when: "'not installed' not in result.stdout"
- yum:
name: "{{ pkg }}"
state: present
Now in this playbook under shell task, we query the httpd package and
check the stdout. If the stdout contains “not installed” then it
means that httpd package is not installed so the playbook can go ahead
and install the pkg but if not found then it means httpd package is
already in installed state so mark the playbook as failed and exit
without executing any further tasks.
Let us execute this playbook:
[ansible@controller ~]$ ansible-playbook install-httpd.yml
PLAY [Installing HTTPD] ********************************************************************************************
TASK [shell] *******************************************************************************************************
fatal: [server2]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"}, "changed": true, "cmd": "rpm -q httpd | head -n 1", "delta": "0:00:00.015037", "end": "2020-09-25 07:39:33.713691", "failed_when_result": true, "rc": 0, "start": "2020-09-25 07:39:33.698654", "stderr": "", "stderr_lines": [], "stdout": "httpd-2.4.37-21.module_el8.2.0+494+1df74eae.x86_64", "stdout_lines": ["httpd-2.4.37-21.module_el8.2.0+494+1df74eae.x86_64"]}
PLAY RECAP *********************************************************************************************************
server2 : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
So the playbook is marked as failed because httpd package is already
installed on server2. You may use “-vvv” with the ansible-playbook
command to get a verbose output which can you you more idea on the
OUTPUT of our task.
Print message with fail
In the above example we did fail the playbook based on a condition match
but there was no output on the console so operator may get confuse if
the playbook has a BUG or the failure was intentional. So we can use
fail module instead of failed_when to be able to print a message on
the console:
I have updated our playbook with fail module and added one debug
module to check if the playbook reaches that stage or exits at the
failed stage itself.
---
- name: Installing HTTPD
hosts: server2
become: true
gather_facts: false
vars:
pkg: httpd
tasks:
- shell: rpm -q httpd | head -n 1
args:
warn: false
register: result
- fail:
msg: "Failed because {{ pkg }} is already installed"
when: "'not installed' not in result.stdout"
- debug:
msg: do we get here
- yum:
name: "{{ pkg }}"
state: present
Let us execute this playbook:
[ansible@controller ~]$ ansible-playbook install-httpd.yml
PLAY [Installing HTTPD] ********************************************************************************************
TASK [shell] *******************************************************************************************************
changed: [server2]
TASK [fail] ********************************************************************************************************
fatal: [server2]: FAILED! => {"changed": false, "msg": "Failed because httpd is already installed"}
PLAY RECAP *********************************************************************************************************
server2 : ok=1 changed=1 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
So the playbook has exited with our custom message and the remaining part of the playbook was never called.
Using changed_when statement
We will continue to use our existing install-httpd.yml playbook file.
If you observe the SUMMARY from the output of our last example:
PLAY RECAP *********************************************************************************************************
server2 : ok=1 changed=1 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
It shows changed=1 even when we know nothing was changed. The
playbook just did a query of httpd package and since it was already in
installed state the playbook was marked as failed and it exited without
performing any further tasks.
It is possible some module thinks it changed the state of the target
host which is when it reports the changed status but in this case
since we know nothing is changed as part of this module we can suppress
that output using “changed: false”
Here is the updated version of our playbook:
---
- name: Installing HTTPD
hosts: server2
become: true
gather_facts: false
vars:
pkg: httpd
tasks:
- shell: rpm -q httpd | head -n 1
args:
warn: false
register: result
changed_when: false
- fail:
msg: "Failed because {{ pkg }} is already installed"
when: "'not installed' not in result.stdout"
- debug:
msg: do we get here
- yum:
name: "{{ pkg }}"
state: present
In this playbook I have added “changed: false” for the shell module
task. Let us execute this play:
[ansible@controller ~]$ ansible-playbook install-httpd.yml
PLAY [Installing HTTPD] ********************************************************************************************
TASK [shell] *******************************************************************************************************
ok: [server2]
TASK [fail] ********************************************************************************************************
fatal: [server2]: FAILED! => {"changed": false, "msg": "Failed because httpd is already installed"}
PLAY RECAP *********************************************************************************************************
server2 : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
So the playbook execution is marked as failed as expected and
changed=0 so no change was done on the remote host.
Let me manually remove the httpd package from server2 and see if the
playbook succeeds
[ansible@controller ~]$ ansible-playbook install-httpd.yml
PLAY [Installing HTTPD] ********************************************************************************************
TASK [shell] *******************************************************************************************************
ok: [server2]
TASK [fail] ********************************************************************************************************
skipping: [server2]
TASK [debug] *******************************************************************************************************
ok: [server2] => {
"msg": "do we get here"
}
TASK [yum] *********************************************************************************************************
changed: [server2]
PLAY RECAP *********************************************************************************************************
server2 : ok=3 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
So our remaining tasks are also executed and httpd was installed with
changed=1
What’s Next
Next in our Ansible tutorial we will learn all about Ansible handlers and how to use handlers in Ansible playbook.


