Ansible Template Conditionals
Introduction
When managing infrastructure with Ansible, you'll often need to create configuration files that vary based on different conditions. Ansible templates, powered by the Jinja2 templating engine, provide powerful conditional capabilities that let you generate dynamic content.
In this guide, we'll explore how to use conditionals in Ansible templates to create flexible and adaptable configuration files. These conditionals help you build templates that can adjust their output based on variable values, host facts, or other criteria.
Understanding Template Conditionals
Ansible templates use Jinja2's conditional syntax to control what content appears in the final rendered file. This allows you to:
- Include or exclude sections of configuration based on conditions
- Set different values depending on environment, operating system, or other factors
- Create highly customized configurations from a single template
Basic Conditional Syntax
The most basic form of a conditional in Jinja2 templates uses the if
-else
-endif
structure.
Simple If Statement
{% if condition %}
This will appear if the condition is true
{% endif %}
If-Else Statement
{% if condition %}
This will appear if the condition is true
{% else %}
This will appear if the condition is false
{% endif %}
If-Elif-Else Statement
{% if condition1 %}
This will appear if condition1 is true
{% elif condition2 %}
This will appear if condition1 is false but condition2 is true
{% else %}
This will appear if both condition1 and condition2 are false
{% endif %}
Practical Examples
Let's explore some practical examples of using conditionals in Ansible templates.
Example 1: Configuring a Web Server Based on Environment
Here's a template for an Nginx configuration file that adjusts settings based on the deployment environment:
server {
listen 80;
server_name {{ server_name }};
{% if environment == "production" %}
# Production-specific settings
worker_connections 1024;
keepalive_timeout 10;
{% elif environment == "staging" %}
# Staging-specific settings
worker_connections 512;
keepalive_timeout 30;
{% else %}
# Development settings
worker_connections 256;
keepalive_timeout 65;
{% endif %}
# Common configuration continues here
root /var/www/html;
index index.html;
}
In your Ansible playbook, you would set the environment
variable:
- name: Configure Nginx
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/default
vars:
environment: "production"
server_name: "example.com"
Example 2: OS-Specific Configuration
This template uses Ansible facts to determine the operating system and generates appropriate configuration:
# Package management configuration
{% if ansible_facts['os_family'] == "Debian" %}
package_manager: apt
update_command: apt-get update
{% elif ansible_facts['os_family'] == "RedHat" %}
package_manager: yum
update_command: yum update
{% else %}
# Default configuration
package_manager: unknown
update_command: echo "Unknown OS family"
{% endif %}
Example 3: Feature Toggles
You can use conditionals to enable or disable features in your configuration:
# Application configuration file
# Core settings
app_port: 8080
log_level: info
{% if enable_monitoring | default(false) %}
# Monitoring settings
monitoring_port: 9090
metrics_endpoint: /metrics
prometheus_scrape: true
{% endif %}
{% if enable_ssl | default(false) %}
# SSL Configuration
ssl_enabled: true
ssl_cert_path: /etc/ssl/certs/app.crt
ssl_key_path: /etc/ssl/private/app.key
{% endif %}
In your playbook, you control which features are enabled:
- name: Generate application config
template:
src: app.conf.j2
dest: /etc/app/config.yaml
vars:
enable_monitoring: true
enable_ssl: false
Conditional Operators
Jinja2 offers various operators for creating conditions:
Comparison Operators
==
: Equal to!=
: Not equal to>
: Greater than>=
: Greater than or equal to<
: Less than<=
: Less than or equal to
Logical Operators
and
: Logical ANDor
: Logical ORnot
: Logical NOT
Example with Multiple Conditions
{% if (environment == "production" and enable_ssl) or force_ssl %}
# SSL is enabled - configure secure settings
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
{% endif %}
Checking if Variables Exist
Before using a variable, you might need to check if it's defined:
Using the defined
Test
{% if special_config is defined %}
# Use the special configuration
special_option: {{ special_config }}
{% else %}
# Use default configuration
special_option: default
{% endif %}
Using the default
Filter
The default
filter provides a fallback value if a variable is undefined:
debug_mode: {{ debug | default(false) }}
timeout: {{ request_timeout | default(30) }}
Conditional Loops
You can combine conditionals with loops to filter items:
Available servers:
{% for server in servers %}
{% if server.status == "active" %}
server {{ server.name }} ({{ server.ip }})
{% endif %}
{% endfor %}
Real-World Example: Multi-Environment Database Configuration
Here's a more complete example of a database configuration template that adapts to different environments:
# Database configuration for {{ application_name }}
# Generated by Ansible on {{ ansible_date_time.date }}
[database]
driver = {{ db_driver | default('postgresql') }}
host = {{ db_host }}
port = {{ db_port | default(5432) }}
name = {{ db_name }}
{% if environment == "production" %}
# Production database settings
pool_size = 20
max_overflow = 10
pool_timeout = 30
pool_recycle = 3600
ssl_mode = require
{% elif environment == "staging" %}
# Staging database settings
pool_size = 10
max_overflow = 5
pool_timeout = 60
pool_recycle = 1800
ssl_mode = prefer
{% else %}
# Development database settings
pool_size = 5
max_overflow = 2
pool_timeout = 90
pool_recycle = 300
ssl_mode = disable
{% endif %}
[credentials]
{% if db_use_env_vars | default(true) %}
# Use environment variables for credentials
use_env_vars = true
username_var = DB_USERNAME
password_var = DB_PASSWORD
{% else %}
# Hardcoded credentials (not recommended for production)
use_env_vars = false
username = {{ db_username }}
password = {{ db_password }}
{% endif %}
{% if db_backup_enabled | default(false) %}
[backup]
enabled = true
schedule = {{ db_backup_schedule | default('0 2 * * *') }}
retention_days = {{ db_backup_retention | default(7) }}
storage_path = {{ db_backup_path | default('/var/backups/db') }}
{% endif %}
This template adjusts connection pool settings based on environment and conditionally includes backup configuration when enabled.
Advanced Techniques
Inline Conditionals (Ternary Operator)
For simple conditions, you can use the ternary operator syntax:
debug_level: {{ "debug" if debug_mode else "info" }}
max_connections: {{ 100 if environment == "production" else 20 }}
Using the is
and in
Operators
The is
operator is used with tests, and in
checks for containment:
{% if ansible_facts['distribution'] is in ['Ubuntu', 'Debian'] %}
# Debian-based system configuration
{% endif %}
{% if username is string and username is not empty %}
user: {{ username }}
{% endif %}
Using not
with Tests
You can combine not
with tests:
{% if app_version is not defined or app_version is none %}
app_version: 1.0.0 # Default version
{% endif %}
Best Practices
- Keep logic simple: Move complex logic to Ansible tasks or roles rather than templates
- Use defaults: Provide sensible defaults with the
default
filter - Validate inputs: Check if variables are defined before using them
- Comment your conditions: Explain the purpose of conditional blocks
- Consistent indentation: Maintain proper indentation for readability
- Group related conditions: Keep related conditional blocks together
Flow Diagram for Template Processing
Summary
Conditionals in Ansible templates provide powerful flexibility when generating configuration files. By mastering these techniques, you can create templates that adapt to different environments, operating systems, and deployment scenarios.
With Jinja2's conditional syntax, you can:
- Include or exclude configuration sections based on conditions
- Provide environment-specific settings
- Implement feature toggles
- Set defaults for missing variables
- Generate highly customized configurations from a single template
This approach allows you to maintain a single source of truth while deploying appropriately configured files across your infrastructure.
Additional Resources
Exercises
-
Create a template for an Apache virtual host configuration that changes settings based on whether it's a development, staging, or production environment.
-
Design a template for a logging configuration that enables different log levels and destinations based on the environment and available disk space.
-
Build a template for a load balancer configuration that dynamically includes backend servers from an Ansible inventory group, but only if they have a specific tag or property.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)