Elevating IaC Workflows with Spacelift Stacks and Dependencies 🛠️

Register for the July 23 demo →


Ansible Conditionals – When, Register & Other Examples

ansible conditionals

Have you ever wanted to run a single Ansible playbook against different environments with different operating systems or variables? This is where Ansible conditionals can help.

In this article, we explore how Ansible conditionals allow you to make decisions based on predefined criteria, such as the state of your systems and environments, to significantly enhance control over automation tasks. We also look at some examples with the register and when conditions and use them in Ansible roles and with different logical operators. 

What we will cover:

  1. What is an Ansible conditional?
  2. Using register and when conditions in Ansible playbooks
  3. Conditions using Ansible facts
  4. Using when in Ansible roles
  5. Ansible when with multiple conditions

What is an Ansible conditional?

Conditionals are an Ansible feature that allows you to control the execution flow of playbook tasks based on declared conditions. Tasks that include the Ansible conditionals are executed only if specific criteria are met; if the condition is false, the task is skipped. This capability is particularly valuable when managing diverse environments with different operating systems or configurations.

Ansible conditionals have many different use cases, including: 

  • Inputting certain variables based on the environment you are running the playbook against
  • Kicking off DB backup only when the disk space usage is above a certain threshold 
  • Triggering  reboots if certain files were changed or updated 
  • Adding or modifying users based on their roles and departments 
  • Skipping tasks under certain conditions
  • Notifying teams if tasks related to their team were added or modified (e.g., Notify DB team if Database configurations have been changed)

Types of Ansible conditionals

Below are types of Ansible conditionals we can use throughout our playbooks:

  • Simple conditions — Run a task if a single variable is equivalent to the condition listed in the when statement.
  • Registered variables conditions — Capture the result of a previous task using register and use it as a condition on a later task using when
  • Ansible facts conditions — Ansible Facts offer a set of variables you can reference to gather system data such as OS, hardware details, etc. You can set conditions based on the Ansible facts to execute your task. 
  • Logical operators — This includes operators such as AND, OR, and NOT. They allow you to combine different conditions and offer more logical options for running your tasks.

Using when and register conditions in Ansible playbooks

Let’s start by reviewing the when statement and utilizing it alongside the register statement.

Ansible when condition

The following example is a simple scenario using the Ansible when statement to validate that a condition is true. The task will run only if the condition is true. If it is false, the task will be skipped to ensure no misconfigurations with environments occur will take place.

- name: Start service if on production
    name: httpd
    state: started
  when: environment == 'production'

Ansible register condition

Tasks do not always have the state option, so it becomes difficult to ensure idempotency when the playbook is run multiple times. In these cases, you can register a validate step (checking if that application exists) as a variable and place a when condition for this registered variable to exist to run the dependent tasks. The Ansible register conditional ensures the tasks you don’t want to run again are skipped.

For example, we want to install a Tomcat application, but the Download/Extract task shouldn’t run each time during our Playbook runs because it could slow down the run time or possibly replace config files we have already set up. To do this, we can easily utilize the register/when conditionals.

- name: Check if Tomcat Webapp Exists
    path: /opt/tomcat/webapps/ROOT/WEB-INF/web.xml
  register: tomcat_extracted

- name: Download Apache Tomcat
    url: "https://downloads.apache.org/tomcat/tomcat-9/v{{ tomcat_version }}/bin/apache-tomcat-{{ tomcat_version }}.tar.gz"
    dest: "/tmp/apache-tomcat-{{ tomcat_version }}.tar.gz"
  when: not tomcat_extracted.stat.exists

- name: Extract Tomcat
    src: "/tmp/apache-tomcat-{{ tomcat_version }}.tar.gz"
    dest: /opt/tomcat
    remote_src: true
    extra_opts: ["--strip-components=1"]
  when: not tomcat_extracted.stat.exists

Note: The syntax for the conditionals is based on the return values for the Ansible stat module. When registering a variable under a module, you receive a dictionary of all the return values you can reference in your when conditionals.

For this example, we are referencing the stat’ dictionary with a return value of the boolean ‘exists’. You can find these in Ansible’s documentation for the module you use. 

If we run this playbook a second time, it would automatically skip the Download and Extract tasks because the file path already exists on the target host:

module.myapp.module.ansible-my-app.null_resource.ansible_run (remote-exec): TASK [tomcat_role : Check if Tomcat Webapp Exists] *****************************

module.myapp.module.ansible-my-app.null_resource.ansible_run (remote-exec): ok: []

module.myapp.module.ansible-my-app.null_resource.ansible_run (remote-exec): TASK [tomcat_role : Download Apache Tomcat] ************************************

module.myapp.module.ansible-my-app.null_resource.ansible_run (remote-exec): skipping: []

module.myapp.module.ansible-my-app.null_resource.ansible_run (remote-exec): TASK [tomcat_role : Extract Tomcat] ********************************************

module.myapp.module.ansible-my-app.null_resource.ansible_run (remote-exec): skipping: []

Read more about Ansible playbooks: Ansible Playbooks: Complete Guide with Examples.

Conditions using Ansible Facts

So far, we have covered how to use conditionals with specific values assigned to variables, but what if we already have that information, and we can validate whether a conditional is true or false based on the ‘fact’ of the target host machine? 

Ansible when conditional in Ansible Facts

Ansible_facts allows you to take facts related to aspects of your target host, such as OS, device, IP address, and filesystems. You can use the Ansible when condition to validate if a specific fact is true or not in order for your task to run. 

For example, if you have a different Linux OS across your environment and you want to ensure you install the correct application per OS, you can use a condition like the following to make sure your playbooks run successfully:

- name: Install a package ONLY on Red Hat-based systems
    name: httpd
    state: present
  when: ansible_os_family == "RedHat"

To find the ansible_os_family value you can run a test playbook against each of your Linux OS target hosts (Ubuntu, RedHat) using the following debug module to retrieve the entire dictionary of all the ansible_facts you can reference in your conditional: 

- name: Print all available facts
    var: ansible_facts

This will give you a dictionary resembling the following (only it will show the output for the section that includes ‘ansible_os_family’ because this generates a large output), which you can reference in many ways to ensure you are running the correct tasks against the correct machines:

    "ansible_nodename": "centos-7-rax-dfw-0003427354",
    "ansible_os_family": "RedHat",
    "ansible_pkg_mgr": "yum",
    "ansible_processor": [
        "Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz",
        "Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz",
        "Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz",
        "Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz",
        "Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz",
        "Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz",
        "Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz",
        "Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz"

Ansible when and register conditionals in Ansible Facts

Another common technique for improving consistency in your Ansible playbooks is to use both when and register with ansible_facts in the same task. 

For example, we may have tasks that need to be installed on a specific OS distro and the dependent tasks can only run if the initial task is true. We want to ensure NTP is installed across all of our Linux boxes because CentOS and Ubuntu have different package names and different package managers. 

To install Chrony (the CentOS NTP package) on CentOS/RHEL machines only and NTP on Ubuntu/Debian machines, we can utilize the when condition with the ansible_fact and register/when to create a dependency on the initial task. 

 - name: Install Chrony on CentOS/RHEL
        name: chrony
        state: present
      when: ansible_facts['os_family'] == "RedHat"
      register: package_installed

    - name: Ensure Chrony service is enabled and running on CentOS/RHEL
        name: chronyd
        state: started
        enabled: yes
      when: package_installed is changed

    - name: Install NTP on Ubuntu/Debian
        name: ntp
        state: present
      when: ansible_facts['os_family'] == "Debian"
      register: package_installed

    - name: Ensure NTP service is enabled and running on Ubuntu/Debian
        name: ntp
        state: started
        enabled: yes
      when: package_installed is changed

Using the when condition in Ansible roles

As a best practice, you should use Ansible roles in your playbooks. They allow you to break up your playbook into reusable components, which can be referenced throughout to make the applications or tasks become more modularized and decoupled. 

When using conditionals in Ansible Roles, you can enforce logical conditionals on the playbook level as well as within the role’s tasks, improving flexibility. 

Here are three key ways to enforce Ansbile conditionals within roles. 

1. Applying Ansible conditionals under roles on the playbook level

The first way is to apply conditionals under roles on the playbook level, which applies to all the tasks that are under the role. This can be used when we want to point our playbook to deploy to ‘all’ host machines but want certain roles to apply to specific machine inventory groups in the Ansible inventory file

To achieve this goal, we can use the following setup: The Apache role is deployed only to the webservers group, and Postgresql is deployed only to the databases group in the inventory file. This is only run under the roles option, making it suitable for your role-based playbooks. 

- hosts: all
    - role: apache
      when: "'webservers' in group_names"
    - role: postgresql
      when: "'databases' in group_names"

2. Applying Ansible conditionals to import roles under tasks on the playbook level

The second way is to apply conditionals to import roles under tasks on the playbook level. This method gives you the flexibility to use task-level features in your roles, such as loops, tagging, etc. You can also mix roles with other tasks in your playbooks, which can be helpful in certain scenarios. 

For example, this conditional import role allows us to loop through each application variable in apps and use tags to import the proper role that way. This allows us to determine which environment to assign our application to on the variable level and just have the playbook loop through it:

- hosts: all
    environment: staging
      - name: app1
        deploy_in: ['dev', 'staging']
      - name: app2
        deploy_in: ['staging', 'production']
      - name: app3
        deploy_in: ['dev']
    - name: Deploy apps for each environment
        name: "{{ item.name }}_deployment"
      loop: "{{ apps }}"
      when: environment in item.deploy_in
        - deploy

3. Applying conditionals to the tasks in roles from the playbook level

The third approach lets you apply conditionals to the tasks in the roles themselves from the playbook level. This is the only way that enables you to skip or select specific role tasks in our role. 

Here, we are using the include_role feature in our playbook and adding the when statement under it to specify the conditional. It is useful for complex roles where only specific tasks need to be executed under certain conditions instead of the entire role.

In this example, we are configuring a web server, but specific tasks under the role should only run for the hosts that meet the criteria (based on environment and inventory group name).


- name: Install HTTP server
    name: apache2
    state: present
  when: "'web' in inv_groups"

- name: Configure HTTP server for production
    src: production.conf.j2
    dest: /etc/apache2/sites-available/sample.conf
  when: environment == 'production'

- name: Configure HTTP server for staging
    src: staging.conf.j2
    dest: /etc/apache2/sites-available/sample.conf
  when: environment == 'staging'


- hosts: all
    environment: "{{ lookup('env', 'ENVIRONMENT') }}"
    - name: Dynamically include web server configuration role
        name: web_server_configuration
      when: "'web' in inv_groups"

This setup ensures the web server is not deployed to any machine outside the web inventory group and the application is deployed to the proper environment. Maximizing flexibility and control allows you to write specific roles that can be included dynamically in various playbooks across different environments. 

Ansible when condition with multiple conditions

Using the when condition with logical AND operator

The AND logical operator is very helpful when you want to create precise control flows within your Ansible playbook and ensure multiple conditionals are true for that task, role, or block to run. It can also be used when handling complex logic because it allows you to place different types of checks under one when statement. 

In the following example, we are using the AND operator with the Ansible when conditional to ensure that this task only runs on production machines that are running CentOS because yum will not work for other distros:

- hosts: all
    - name: Deploy updates to production servers
        name: '*'
        state: latest
      when: ansible_facts['distribution'] == "CentOS" and inventory_hostname in groups['production']

You can also use the AND operator in a single line instead of writing out ‘and’ whenever conditionals become lengthy.

- hosts: all
    - name: Check Tomcat version
      shell: tomcat9 version | grep 'Server number'
      register: tomcat_version
      changed_when: false

    - name: Update Tomcat on older versions during weekends
        name: tomcat9
        state: latest
        - "'9.0.82' in tomcat_version.stdout"
        - ansible_date_time.weekday in ['Saturday', 'Sunday']

Using when condition with logical OR operator

The OR operator allows one or other conditional in the when statement to be true for that task to run. It also reduces complexity in a similar way to the AND operator, because you can use one playbook for different operations. 

In the following example, we want to ensure any host with a distribution of Debian or Ubuntu, will use ‘apt’, and any host with a distribution of CentOS or RedHat will use yum to install Tomcat. 

- hosts: all
    - name: Install Tomcat on Debian or Ubuntu
        name: tomcat9
        state: present
      when: ansible_facts['distribution'] == "Debian" or ansible_facts['distribution'] == "Ubuntu"

    - name: Install Tomcat on CentOS or RHEL
        name: tomcat
        state: present
      when: ansible_facts['distribution'] == "CentOS" or ansible_facts['distribution'] == "RedHat"

Here is another example of using both AND/OR logical operators in the Ansible when condition to check the logs to send out email notifications when certain variable status outputs are detected and if the email_enabled option is set to true:

- hosts: all
    email_enabled: true
    - name: Check for critical errors in logs
      shell: grep "CRITICAL ERROR" /var/log/myapp.log
      register: log_check
      ignore_errors: True

    - name: Send alert email for errors
        to: user@domain.com
        subject: 'Critical: Error Detected'
        body: 'A critical error has occurred on {{ inventory_hostname }}.'
        - (log_check.rc == 0 or server_status == 'critical') and email_enabled  

Using when condition with logical NOT operator

The logical NOT operator specifies certain tasks that should only execute when a specific condition is NOT true. It helps define the opposite conditionals to make your playbooks more intuitive and easily exclude hosts, variables, and states. 

In the following example, we are using the NOT operator to ensure the mounting directory is created only if the variable mount_point_stat does NOT exist:

- hosts: all
    - name: Check if mount point directory exists
        path: "{{ mount_point }}"
      register: mount_point_stat

    - name: Create mount point directory
        path: "{{ mount_point }}"
        state: directory
        mode: 755
      when: not mount_point_stat.stat.exists

You can also use the NOT operator to check if the variable or ansible_fact does not equal a specific value: 

- hosts: all
    - name: Install a package using apt
        name: curl
        state: present
      when: ansible_facts['os_family'] != "Windows"

The NOT operator can be helpful if you want to ensure a specific task never runs on a production environment:

- hosts: all
    environment: development
    - name: Deploy configuration files
        src: config.conf
        dest: /etc/app/config.conf
      when: environment != "production"

Key points

Ansible conditionals increase your control over playbook task execution, allowing you to customize which tasks or roles run. With conditionals, you can ensure tasks/roles execute on specific environments or Linux distributions, using register and when conditions to declare variables and run based on their values.

A single task or role can accomplish more than you might think, simplifying playbooks for better understanding. You also have the flexibility to mix and match roles with tasks, utilizing different conditional statements like AND/OR in a single when statement. This approach saves time, reduces errors, and significantly increases the scalability and reliability of your playbooks.

We encourage you to explore how Spacelift’s vibrant ecosystem and excellent GitOps flow can assist you in managing and orchestrating Ansible. By introducing Spacelift on top of Ansible, you can easily create custom workflows based on pull requests and apply any necessary compliance checks for your organization. Another advantage of using Spacelift is that you can manage different infrastructure tools like Ansible, Terraform, Pulumi, AWS CloudFormation, and even Kubernetes from the same place and combine their Stacks with workflows across tools.

If you want to learn more about using Spacelift with Ansible, check our documentation, read our Ansible guide, or book a demo with one of our engineers.

Manage Ansible Better with Spacelift

Spacelift helps you manage the complexities and compliance challenges of using Ansible. It brings with it a GitOps flow, so your infrastructure repository is synced with your Ansible Stacks, and pull requests show you a preview of what they’re planning to change. It also has an extensive selection of policies, which lets you automate compliance checks and build complex multi-stack workflows.

Learn More

The Practitioner’s Guide to Scaling Infrastructure as Code

Transform your IaC management to scale

securely, efficiently, and productively

into the future.

ebook global banner
Share your data and download the guide