Conditional statements – making decisions in Ansible code

In a playbook or in a role sometimes we want to run different tasks based on different conditions. In most cases it depends on a fact (detail about the managed host) or some data collected during the playbook run. Ansible conditionals are there to make it possible to run different tasks based on different conditions, or skip executing tasks entirely.

I’ve already wrote it before that loops and conditionals are a double edged sword. They can make simple tasks complex, and complex tasks impossible. This is why we have to use just the necessary amount of loops and conditionals in our code if we want to write futureproof playbooks and roles.

We can create simple conditional statements with the when keyword.

Conditional statements after tasks or blocks will look at the condition and decide what to do with the task or block.

If the certain condition is met then a true value is returned, and the task will be executed. If the condition isn’t met with the expectations, it returns a false value and the task execution will be skipped.

Most of our conditions will be based on facts gathered on a managed node and variables registered while running Ansible playbooks.

Fact based conditionals

If we want to use facts in our Ansible roles (and playbooks), we have to use the fact names listed by the ansible.builtin.debug module, like:

"distribution": "Debian",
"distribution_file_variety": "Debian",
"distribution_major_version": "11",
"distribution_release": "bullseye",
"distribution_version": "11",

We can use these variable names in our playbooks and roles.

Here is a simple task to display the facts available on a managed node from a playbook:

- name: Show facts available on the system
  ansible.builtin.debug:
    var: ansible_facts

Ansible uses the Jinja2 templating engine for testing conditional statements.

Ansible uses Jinja2 tests and filters in conditionals. Ansible supports all the standard tests and filters, and adds some unique ones as well.

Ansible Docs

There are many options to control execution flow in Ansible. You can find more examples of supported conditionals at https://jinja.palletsprojects.com/en/latest/templates/#comparisons.

Ansible Docs

Often you want to execute or skip a task based on facts. Facts are attributes of individual hosts, including IP address, operating system, the status of a filesystem, and many more. With conditionals based on facts:

  • You can install a certain package only when the operating system is a particular version.
  • You can skip configuring a firewall on hosts with internal IP addresses.
  • You can perform cleanup tasks only when a filesystem is getting full.
Ansible Docs

So according to the Ansible documentation we can make sure to only run a task when a specific condition is met. Our play-test.yml playbook from the previous article is still on our filesystem, and we can use it to test some conditionals on the localhost (on our Ansible control node). That’s also a Debian Bullseye server by the way.

When we want to use facts in conditional statements we have list them with the ansible.builtin.debug module:

- name: Show facts available on the system
  ansible.builtin.debug:
    var: ansible_facts

This task will show us all the possible fact variables for the when keyword.

Now we can extend the play-test.yml playbook and we can just play with the possibilities and practice:

---
- name: Test playbook for looping
  hosts: ansible

  tasks:

    # - name: Show facts available on the system
    #   ansible.builtin.debug:
    #     var: ansible_facts

    - name: Iteration through a list
      ansible.builtin.debug:
        msg: "{{ item }}"
      loop:
        - test1
        - test2
      when: ansible_facts['distribution'] == 'Debian'

    - name: Iteration through a list of dictionaries
      ansible.builtin.debug:
        msg: "{{ item.first_id }} and {{ item.second_id }}"
      loop:
        - { first_id: test1, second_id: secret1 }
        - { first_id: test2, second_id: secret2 }
      when: ansible_facts['distribution'] == 'CentOS'

    - name: Iteration through a dictionary
      ansible.builtin.debug:
        msg: "{{ item.key }} - {{ item.value }}"
      loop: "{{ users | dict2items }}"
      vars:
        users:
          user1: tamas
          user2: tom
      when: ansible_facts['hostname'] != 'LaptopDebtop'

When we run this playbook we will see how Ansible behaves when it hits conditional statements in tasks, and the conditions are not met:

TASK [Iteration through a list of dictionaries] ************************************************************************************************************
skipping: [ansible] => (item={'first_id': 'test1', 'second_id': 'secret1'}) 
skipping: [ansible] => (item={'first_id': 'test2', 'second_id': 'secret2'}) 
skipping: [ansible]

When a condition is not met, Ansible skips the task and continues with executing the other tasks in the playbook.

We can use logical operators like and/or to write multiple conditions:

- name: Iteration through a dictionary
  ansible.builtin.debug:
    msg: "{{ item.key }} - {{ item.value }}"
  loop: "{{ users | dict2items }}"
  vars:
    users:
      user1: tamas
      user2: tom
  when: ansible_facts['hostname'] != 'LaptopDebtop' or
        ansible_facts['distribution'] == 'Debian'

Conditions on registered variables during run time

Playbooks can save (register) variables during run time with the register keyword. A registered variable contains the task attributes and the output the task generated.

We can see the content of the output in variable.stdout and variable.stderr. Our example will explain it better below.

Let’s capture a variable in our test playbook and look into its contents!

- name: Register a variable
  ansible.builtin.shell: cat /etc/hosts
  register: hosts_file

- name: Use a variable
  ansible.builtin.debug:
    var: hosts_file

- name: Condition based on a registered variable
  ansible.builtin.debug:
    var: hosts_file
  when: hosts_file.stderr

The first task will capture the data about the task, and it will capture the output of the shell command. Then the second task simply shows us the captured data from the first task. The third task uses the registered variable to determine if we had any error message. If the first task failed then the third task will run. If the first task was successful, then the third task gets skipped.

We should avoid using the shell module in our code. We used it here only for practicing with registered variables!

Conditional statements reference: https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_conditionals.html#conditionals

Now we can start automating our personal practice lab!

If you have anything to share then please visit my Tom’s IT Cafe Discord Server!

Leave a comment