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
- name: Task description
module_name:
param1: value1
param2: value2
when: condition
Simple Example
- 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 trueor
: At least one condition must be truenot
: Negates a condition(...)
: Group conditions to control precedence
Example with Multiple Conditions
- 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:
- The OS family is RedHat
- The environment is production
- 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
- name: Display a variable if defined
debug:
msg: "The proxy is {{ proxy_url }}"
when: proxy_url is defined
Using the undefined
Test
- 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 existssuccess
/failed
: Check the status of a previous taskchanged
: Check if a previous task changed somethingfile
/directory
/link
: Check file types
Examples
# 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
- 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:
- We run a command to check service status
- The result is stored in
service_status
- 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:
- 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
- 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
- name: Include OS-specific tasks
include_tasks: "{{ ansible_os_family }}.yml"
when: ansible_os_family in ['Debian', 'RedHat']
Conditional Import
- 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.
- 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
---
- 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
---
- 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
---
- 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
- 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
-
Keep conditions readable: Use the YAML list format for multiple conditions
yamlwhen:
- condition1
- condition2
- condition3 -
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 -
Test your conditions: Use
debug
tasks to verify complex conditionals -
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" -
Document complex conditionals: Add comments to explain the reasoning behind complex conditions
Troubleshooting Conditional Statements
Common Issues and Solutions
-
Condition always evaluates to true
- Make sure you're using
==
for comparison, not=
- Use the
| bool
filter for boolean evaluation
- Make sure you're using
-
String comparison problems
- Remember that YAML considers "yes", "true", "on" as boolean
true
- Use quotes around strings that might be interpreted as booleans
- Remember that YAML considers "yes", "true", "on" as boolean
-
Using undefined variables
- Always check if a variable is defined before using it
Debugging Conditionals
To debug conditions, use the debug
module:
- 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
-
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
-
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
-
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
-
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! :)