Chapter 5: Implementing Task Control

Running tasks conditionally

Not every task belongs on every host. Conditionals let a task decide, host by host, whether to run: only on RHEL, only with enough memory, only if a variable is set, only if an earlier command succeeded.

Why conditionals

Conditionals let one playbook treat hosts differently according to what they are. Any variable can be tested: playbook variables, registered results and facts. Typical uses:

The when keyword

Add when to a task with a condition as its value. If the condition is true on a host, the task runs there; if it is false, the task is skipped on that host.

- name: Simple Boolean Task Demo
  hosts: all
  vars:
    run_my_task: true
  tasks:
    - name: httpd package is installed
      ansible.builtin.dnf:
        name: httpd
      when: run_my_task

Notice that the condition is not wrapped in {{ }}. when is already evaluated as a Jinja2 expression, so you write variable names directly.

Trying conditions

Pick a condition and see which of three hosts would run the task. servera and serverb run RHEL 9; fedora1 runs Fedora 34; only two of them define my_service.

Operators you will use

TestExample
Equal (string)ansible_facts['machine'] == "x86_64"
Equal (number)max_memory == 512
Less than / greater thanmin_memory < 128, min_memory > 256
Less / greater or equalmin_memory <= 256, min_memory >= 512
Not equalmin_memory != 512
Variable existsmin_memory is defined
Variable does not existmin_memory is not defined
Boolean is true (1, True, yes also count)memory_available
Boolean is false (0, False, no also count)not memory_available
Value is in a listansible_facts['distribution'] in supported_distros

Strings are quoted; numbers are not. is defined is especially useful for optional settings: the task simply skips on hosts where the variable was never set, instead of failing.

- name: Test Variable is Defined Demo
  hosts: all
  vars:
    my_service: httpd
  tasks:
    - name: "{{ my_service }} package is installed"
      ansible.builtin.dnf:
        name: "{{ my_service }}"
      when: my_service is defined

The in test compares one value with a list:

- name: Demonstrate the "in" keyword
  hosts: all
  gather_facts: true
  vars:
    supported_distros:
      - RedHat
      - Fedora
  tasks:
    - name: Install httpd using dnf, where supported
      ansible.builtin.dnf:
        name: httpd
        state: present
      when: ansible_facts['distribution'] in supported_distros

Booleans and the string trap

YAML 1.1, which Ansible uses, also accepts yes/no, True/False and 1/0 as Booleans. The YAML 1.2 standard only accepts true and false, and the Ansible community is gradually standardising on those. You will see both in existing content.

Several conditions

Combine conditions with or (either is enough) and and (both are needed):

when: ansible_facts['distribution'] == "RedHat" or ansible_facts['distribution'] == "Fedora"

The home-lab VMs run Rocky Linux, so ansible_facts['distribution'] is "Rocky" and a condition that tests for "RedHat" is false there. Two ways to practise these examples at home:

Check what your hosts report: ansible all -m ansible.builtin.setup -a 'filter=ansible_distribution*'.

when: ansible_facts['distribution_version'] == "9.0" and ansible_facts['kernel'] == "5.14.0-70.13.1.el9_0.x86_64"

A list under when means all of its conditions must be true (an implicit and), and is easier to read:

when:
  - ansible_facts['distribution_version'] == "9.0"
  - ansible_facts['kernel'] == "5.14.0-70.13.1.el9_0.x86_64"

Use parentheses to group more complex logic, and the YAML > folded style to split it over several lines:

when: >
  ( ansible_facts['distribution'] == "RedHat" and
    ansible_facts['distribution_major_version'] == "9" )
  or
  ( ansible_facts['distribution'] == "Fedora" and
    ansible_facts['distribution_major_version'] == "34" )

Loops and conditions together

When a task has both loop and when, the condition is checked for each item. Here ansible_facts['mounts'] is a list of dictionaries, one per mounted file system, and the package is installed only if / has more than 300 MB free:

- name: install mariadb-server if enough space on root
  ansible.builtin.dnf:
    name: mariadb-server
    state: latest
  loop: "{{ ansible_facts['mounts'] }}"
  when: item['mount'] == "/" and item['size_available'] > 300000000

Conditions on registered results

A common pattern: run a check, register the result, and decide what to do next. This play restarts Apache only if Postfix is running:

- name: Restart HTTPD if Postfix is Running
  hosts: all
  tasks:
    - name: Get Postfix server status
      ansible.builtin.command: /usr/bin/systemctl is-active postfix
      register: result

    - name: Restart Apache HTTPD based on Postfix status
      ansible.builtin.service:
        name: httpd
        state: restarted
      when: result.rc == 0

systemctl is-active exits with 0 when the service is active, so result.rc == 0 means "Postfix is running". (If Postfix is not running, the first task fails; in the task failure section you will see how to keep the play going with ignore_errors.)

Takeaway

Run a task only when a condition is true, with when. Use the chapter lab to check this on a real host.