1. Overview

Ansible is an open-source automation tool that enables system management and configuration. Conditionals are an important part of Ansible playbooks. They support decision-making and provide a way to take specific actions based on the outcomes of commands or tasks.

In this tutorial, we’ll see practical examples of how to use conditionals in Ansible playbooks based on the output of a command.

2. Installing Ansible

Ansible can be installed on various operating systems including Linux.

On Ubuntu, we can install Ansible using the apt package manager. However, we’ll use Ansible’s official PPA (Personal Package Archive) to install the latest version. Let’s add this PPA using the apt-add-repository command:

$ sudo apt-add-repository ppa:ansible/ansible

After adding ansible/ansible, we update the repository information and install Ansible:

$ sudo apt update && sudo apt install ansible -y

Let’s verify the installation using the option —**version:

$ ansible --version

The above approach ensures that we get the most recent version of Ansible.

3. Using Conditionals in Ansible Playbooks

Conditionals in Ansible are used to run tasks or define playbook behavior based on specific conditions. They enable us to create dynamic and responsive automation scripts.

Usually, with conditionals, we use the when statement, which evaluates whether a given condition is true or false.

The when clause is a YAML keyword that takes a boolean expression as its argument. If the boolean expression is true, the task runs. Otherwise, the task is skipped.

4. Test Environment Setup

Before we get into how to use conditionals, let’s set up a test environment.

4.1. Setting Up the Virtual Machines

We’re using Ubuntu 20.04 virtual machines for this tutorial with VirtualBox and Vagrant. Specifically, we’ve Ansible installed on a control node. There are also two Vagrant boxes which are essentially our remote clients.

The control node and clients are configured with their respective IP addresses:

  • control node: 192.168.221.163
  • client1: 192.168.221.171
  • client2: 192.168.221.172

Let’s now see how Ansible manages the remote clients.

4.2. Inventory File

Basically, inventory files define the hosts and groups of hosts that Ansible manages. In our case, the inventory file resides on the control node inside our project folder, /home/vagrant/ansible.

So, let’s use cat to see the current inventory file:

$ cat project_inventory.ini
[client1]
192.168.221.171
[client2]
192.168.221.172
[webserver:children]
client1
client2

Here, we’ve defined the client machines in two groups: client1 and client2. The third group, webserver, creates a parent-child relationship between these clients.

4.3. Creating SSH Key Pair

Furthermore, we need to set up the control node to be able to ping the clients using the Ansible ping module. For this, we create an SSH key pair on the control node using the ssh-keygen command:

$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/vagrant/.ssh/id_...
...

Next, we use the ssh-copy-id command to copy the above key to the remote client, client1:

$ ssh-copy-id [email protected]
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be ...
The authenticity of host '192.168.221.171 ...
...

Similarly, we copy the key to client2:

$ ssh-copy-id [email protected]
usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed...
The authenticity of host '192.168.221.172...

Now, let’s move on to checking the connectivity between the control node and clients.

4.4. Checking Connectivity

Let’s use the Ansible ping module with -m to see if the control node can ping all the client machines based on the inventory file:

$ ansible all -m ping -i /home/vagrant/ansible/project_inventory.ini
192.168.221.172 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
192.168.221.171 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

Notably, a successful connection returns a pong message in the output.

5. Playbook Examples With Conditionals

Basically, an Ansible playbook is a set of statements in YAML. It defines a batch of tasks to be run on one or more hosts.

Typically, in Ansible, we can store or register the output of a command or play in a variable. We can then use the variable in the tasks that follow.

5.1. With Simple Registered Variable

In this example, we’ll use the output of a command to set a variable. Then, we’ll apply a conditional statement based on the value of the variable.

Let’s first create a playbook date.yml:

$ cat date.yml
---
- name: Using Conditionals
  hosts: webserver
  tasks:
    - name: Run a command
      command: "date +%H"
      register: current_hour
    - name: Perform action based on time
      debug:
        msg: "Good {{ 'morning' if current_hour.stdout|int < 12 else 'afternoon' }}"

In this example, the playbook is named Using Conditionals. Further, the first task of this playbook runs the date +%H command and stores the output in the current_hour variable.

The next task then uses a conditional statement to print Good morning or Good afternoon depending on the value of current_hour.

Let’s run this playbook against our inventory file:

$ ansible-playbook date.yml -i project_inventory.ini
PLAY [Using Conditionals] *************************
TASK [Gathering Facts] ****************************
ok: [192.168.221.172]
ok: [192.168.221.171]
TASK [Run a command] *****************************
changed: [192.168.221.172]
changed: [192.168.221.171]
TASK [Perform action based on time] *************
ok: [192.168.221.171] => {
"msg": "Good morning"
...

Here, the option -i again specifies our inventory file with a relative path. Hence, the above playbook returns the message Good Morning based on the current time.

5.2. With String Type Registered Variable

In this example, we store the contents of a file and then check for a particular string in the file. In particular, we check the contents of stdout as generated by the command run.

We use stdout to read the string value stored in the registered variable. Then, we can see if a certain word is in the file. For example, let’s check if the word nologin exists in the file /etc/passwd.

First, we create a playbook string.yml:

$ cat string.yml
---
- hosts: client1
  tasks:
    - name: Register the contents of the /etc/passwd file
      command: cat /etc/passwd
      register: passwd_contents
    - name: Check if the word 'nologin' exists in the /etc/passwd file
      debug:
        msg: "The word 'nologin' exists in the /etc/passwd file: {{ passwd_contents.stdout.find('nologin') != -1 }}"
      when: passwd_contents.stdout.find('nologin') != -1
    - name: Display a message when the word 'nologin' is not found
      debug:
        msg: "The word 'nologin' is not found in the /etc/passwd file."
      when: passwd_contents.stdout.find('nologin') == -1

The first task performs two main actions:

  1. uses the command module to read the contents of passwd using the cat command
  2. stores the contents in the registered variable passwd_contents

Furthermore, the second task executes three main operations:

  1. uses the debug module with a when conditional statement to check if the string nologin exists in the register variable passwd_contents
  2. gets the register variable using the stdout field
  3. uses Python’s built-in string function find to perform a search for nologin

If nologin is found, the second task prints a message indicating that nologin was found in the file. Otherwise, the third task prints its message that nologin wasn’t found in the file.

Let’s run the playbook:

$ ansible-playbook string.yml -i project_inventory.ini
PLAY [client1] ...
TASK [Gathering Facts] ...
ok: [192.168.221.171]
TASK [Register the contents of the /etc/passwd file] 
changed: [192.168.221.171]
TASK [Check if the word 'nologin' exists ... ]
ok: [192.168.221.171] => {
"msg": "The word 'nologin' exists in the /etc/passwd file: True"...
}
TASK [Display a message when the word 'nologin' is not found]...
...

In this case, as is usual, the passwd file has the nologin entry. Thus, the second task runs successfully while the third one is skipped.

In summary, such a playbook can check whether a user account with a nologin shell exists or not.

5.3. With List Type Registered Variable

We can also use conditionals with list-type variables. Let’s suppose we want to archive a file abc.txt if it exists in the list of files in the /etc directory.

First, we create a playbook list.yml:

$ cat list.yml
---
- hosts: client2
  become: yes
  tasks:
    - name: Retrieve the list of files in the /etc directory using the ls command
      ansible.builtin.command: ls /etc
      register: directory_files
    - name: Create an archive of the file abc.txt if it exists in the list
      ansible.builtin.command: tar -cf /home/vagrant/archive.tar.gz /etc/abc.txt
      when: "'abc.txt' in directory_files.stdout_lines"

In the first task, the command module runs the ls command on client2. The variable directory_files then stores the output of ls.

In the second task, the when conditional statement checks if the file abc.txt exists in the variable directory_files. If it does, the command module makes an archive of the file using the tar command.

Notably, from Ansible 2.10 onwards, the configuration parser uses the Fully Qualified Collection Name (FQCN) for module naming. For example, we used the FQCN ansible.builtin.command for the command module. Basically, FQCN avoids ambiguity when multiple modules with the same name exist.

Let’s run the example:

$ ansible-playbook list.yml -i project_inventory.ini
PLAY [client2] **********************************
TASK [Gathering Facts] **************************
ok: [192.168.221.172]
TASK [Retrieve the list of files in the /etc  ***
changed: [192.168.221.172]
...

At this point, since the file exists, an archive should show up on the client2 machine.

6. Conclusion

In this article, we looked at how to use Ansible’s register variables with conditional statements, as well as how to configure a practical lab environment.

In conclusion, register variables can store the output of a previous task. We can then use it to develop conditional statements for future tasks.