Working with Ansible conditional statements

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

  • when
  • failed_when
  • changed_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

NOTE:

You should observe the indentation used for 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 httpd package is installed using shell module and register the output into result variable
  • Use set_fact to store the output into result variable as we are doing some operation runtime and storing the variable we couldn't use vars (this is covered in Ansible Variables and Data Types chapter)
  • Using debug module get the output of our variable to make sure it is getting proper output
  • Use yum module to install the pkg based on the when condition. We have used regex_search to search for string "not installed" in the result variable

 

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.

 

 

Leave a Comment

Please use shortcodes <pre class=comments>your code</pre> for syntax highlighting when adding code.