Skip to main content

Ansible Conditionals

Introduction

When automating infrastructure with Ansible, you'll often need your playbooks to behave differently based on certain conditions. For example, you might want to:

  • Install different packages depending on the operating system
  • Skip certain tasks when a file already exists
  • Execute specific commands only when a service is running

Ansible conditionals give you this flexibility by allowing you to control when tasks run based on variables, facts, or the outcome of previous tasks. In this guide, we'll explore how to use conditionals to make your Ansible playbooks more dynamic and intelligent.

Basic Conditional: The when Statement

The most common way to implement conditional logic in Ansible is using the when statement. This allows you to specify a condition that must evaluate to true for a task to run.

Syntax

yaml
- name: Task description
module_name:
param1: value1
param2: value2
when: condition

Simple Example

yaml
- name: Install Apache on Debian/Ubuntu
apt:
name: apache2
state: present
when: ansible_os_family == "Debian"

- name: Install Apache on RedHat/CentOS
yum:
name: httpd
state: present
when: ansible_os_family == "RedHat"

What's happening here?

  • Ansible automatically collects system information (facts) about managed hosts
  • ansible_os_family is one such fact that identifies the OS family
  • The first task only runs on Debian-based systems
  • The second task only runs on RedHat-based systems

Using Logical Operators

You can create more complex conditions using logical operators.

Available Operators

  • and: Both conditions must be true
  • or: At least one condition must be true
  • not: Negates a condition
  • (...): Group conditions to control precedence

Example with Multiple Conditions

yaml
- name: Restart service only on production CentOS servers
service:
name: myapp
state: restarted
when:
- ansible_os_family == "RedHat"
- env == "production"
- ansible_memtotal_mb > 2048

In this example, the service will only restart when all three conditions are true:

  1. The OS family is RedHat
  2. The environment is production
  3. The server has more than 2GB of memory

Checking if Variables Exist

Before using a variable, you might want to verify it exists to avoid errors.

Using the defined Test

yaml
- name: Display a variable if defined
debug:
msg: "The proxy is {{ proxy_url }}"
when: proxy_url is defined

Using the undefined Test

yaml
- name: Set default value if variable is not defined
set_fact:
app_port: 8080
when: app_port is undefined

Testing Variable Values

Ansible provides several tests to check variable properties.

Common Tests

  • defined/undefined: Check if a variable exists
  • success/failed: Check the status of a previous task
  • changed: Check if a previous task changed something
  • file/directory/link: Check file types

Examples

yaml
# Check if a string is empty
- name: Display message if username provided
debug:
msg: "Username is {{ username }}"
when: username | length > 0

# Check if a variable is a boolean true
- name: Run when boolean flag is true
debug:
msg: "Feature is enabled"
when: feature_enabled | bool

# Check if a file exists
- name: Back up file if it exists
command: cp /path/to/file /path/to/file.bak
when: ansible_facts.path.exists('/path/to/file')

Using Registered Variables

You can store the result of a task and use it in conditional statements.

Basic Example

yaml
- name: Check if service is running
command: systemctl status myapp
register: service_status
ignore_errors: yes

- name: Start service if not running
service:
name: myapp
state: started
when: service_status.rc != 0

In this example:

  1. We run a command to check service status
  2. The result is stored in service_status
  3. We start the service only if the command's return code indicates failure

Accessing Registered Variable Properties

Registered variables can contain complex data. Here's how to access different properties:

yaml
- name: Find configuration files
find:
paths: /etc/app/
patterns: "*.conf"
register: conf_files

- name: Display message if configs exist
debug:
msg: "Found {{ conf_files.files | length }} configuration files"
when: conf_files.matched > 0

- name: Display warning if no configs exist
debug:
msg: "Warning: No configuration files found!"
when: conf_files.matched == 0

Using Conditionals with Loops

You can combine conditionals with loops to filter items.

Example

yaml
- name: Install packages if not already installed
apt:
name: "{{ item }}"
state: present
loop:
- git
- vim
- curl
- nginx
when: item not in ansible_facts.packages

Conditional Imports and Includes

You can conditionally include or import entire files of tasks.

Conditional Include

yaml
- name: Include OS-specific tasks
include_tasks: "{{ ansible_os_family }}.yml"
when: ansible_os_family in ['Debian', 'RedHat']

Conditional Import

yaml
- name: Import database tasks based on DB type
import_tasks: "{{ db_type }}_db_setup.yml"
when: db_type in ['mysql', 'postgresql']

Block-Level Conditionals

You can apply a condition to multiple tasks using blocks.

yaml
- name: Configuration tasks for web servers
block:
- name: Install Nginx
apt:
name: nginx
state: present

- name: Copy Nginx configuration
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf

- name: Start Nginx service
service:
name: nginx
state: started
enabled: yes
when: "'web' in group_names"

This entire block only runs on hosts in the 'web' group.

Real-World Examples

Let's look at some practical examples of using conditionals in Ansible playbooks.

Example 1: Environment-Specific Configuration

yaml
---
- hosts: all
vars:
# Load environment-specific variables
env: "{{ lookup('env', 'ENVIRONMENT') | default('development', true) }}"

tasks:
- name: Include environment variables
include_vars: "{{ item }}"
with_first_found:
- "vars/{{ env }}.yml"
- "vars/default.yml"

- name: Deploy debug logging configuration
template:
src: logging-debug.conf.j2
dest: /etc/app/logging.conf
when: env == "development"

- name: Deploy production logging configuration
template:
src: logging-prod.conf.j2
dest: /etc/app/logging.conf
when: env == "production"

- name: Enable service monitoring
include_tasks: monitoring.yml
when: env in ["staging", "production"]

Example 2: Cross-Platform Compatibility

yaml
---
- hosts: all
tasks:
- name: Gather package facts
package_facts:
manager: auto

- name: Install dependencies based on OS
block:
- name: Install Debian/Ubuntu dependencies
apt:
name:
- python3-pip
- python3-dev
- build-essential
state: present
when: ansible_os_family == "Debian"

- name: Install RedHat/CentOS dependencies
yum:
name:
- python3-pip
- python3-devel
- gcc
state: present
when: ansible_os_family == "RedHat"

- name: Set platform-specific paths
set_fact:
config_path: "{{ '/etc/application' if ansible_os_family == 'RedHat' else '/opt/application' }}"

- name: Create configuration directory
file:
path: "{{ config_path }}"
state: directory
mode: '0755'

Example 3: Handling Failures Gracefully

yaml
---
- hosts: database_servers
tasks:
- name: Check if database exists
command: psql -lqt | cut -d \| -f 1 | grep -qw mydb
register: db_exists
ignore_errors: yes
changed_when: false

- name: Create database if it doesn't exist
command: createdb mydb
when: db_exists is failed

- name: Back up database before migration
shell: pg_dump mydb > /backup/mydb_$(date +%Y%m%d).sql
register: backup_result

- name: Run database migrations
command: flask db upgrade
environment:
DATABASE_URL: "postgresql://user:pass@localhost/mydb"
when: backup_result is success
register: migration_result

- name: Send notification on failure
mail:
to: [email protected]
subject: "Database migration failed!"
body: "Migration failed on {{ inventory_hostname }}. Please check logs."
when: migration_result is failed

Using Conditionals with Custom Plugins

Ansible allows you to create custom plugins, which can be useful for complex conditionals.

Example with a Custom Filter

yaml
- name: Check if a server needs an update
debug:
msg: "Server needs an update"
when: ansible_facts.packages | outdated_packages | length > 0

In this example, outdated_packages would be a custom filter that checks which packages need updates.

Best Practices for Conditionals

  1. Keep conditions readable: Use the YAML list format for multiple conditions

    yaml
    when:
    - condition1
    - condition2
    - condition3
  2. Use meaningful variable names: Good variable names make conditions self-explanatory

    yaml
    # Good
    when: is_production_environment and backup_enabled

    # Less clear
    when: env == "prod" and b_en
  3. Test your conditions: Use debug tasks to verify complex conditionals

  4. Avoid unnecessary conditionals: When possible, use dynamic includes or variables instead

    yaml
    # Instead of this:
    - include_tasks: debian.yml
    when: ansible_os_family == "Debian"
    - include_tasks: redhat.yml
    when: ansible_os_family == "RedHat"

    # Do this:
    - include_tasks: "{{ ansible_os_family | lower }}.yml"
  5. Document complex conditionals: Add comments to explain the reasoning behind complex conditions

Troubleshooting Conditional Statements

Common Issues and Solutions

  1. Condition always evaluates to true

    • Make sure you're using == for comparison, not =
    • Use the | bool filter for boolean evaluation
  2. String comparison problems

    • Remember that YAML considers "yes", "true", "on" as boolean true
    • Use quotes around strings that might be interpreted as booleans
  3. Using undefined variables

    • Always check if a variable is defined before using it

Debugging Conditionals

To debug conditions, use the debug module:

yaml
- name: Debug conditional values
debug:
msg: >
os_family: {{ ansible_os_family }}
memory: {{ ansible_memtotal_mb }}
env: {{ env | default('undefined') }}

Summary

Conditionals in Ansible allow you to create dynamic, intelligent playbooks that can adapt to different environments and situations. In this guide, we've covered:

  • Using the when statement for basic conditionals
  • Creating complex conditions with logical operators
  • Testing variable existence and values
  • Using registered variables in conditions
  • Applying conditionals to blocks, includes, and imports
  • Real-world examples and best practices

By mastering conditionals, you can write more flexible and robust Ansible playbooks that handle edge cases gracefully and work across diverse environments.

Additional Resources and Exercises

Resources

Exercises

  1. Basic Conditional Practice

    • Write a playbook that installs different packages based on the OS family
    • Use the when statement to run specific tasks only on servers with more than 4GB of RAM
  2. Working with Registered Variables

    • Create a playbook that checks if a service is running
    • If it's not running, start it and notify an administrator
  3. Advanced Conditionals

    • Write a playbook that performs a rolling update of an application
    • Use conditionals to check if each server is healthy before moving to the next one
    • Include error handling for failed updates
  4. Custom Environment Playbook

    • Create a playbook that deploys different configurations based on environment (dev/staging/prod)
    • Use conditionals to control debugging levels and monitoring settings


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)