Ansible Filters
Introduction
When working with Ansible templates, you'll often need to transform, manipulate, or format data before using it in your configurations. This is where Ansible filters come into play. Filters are powerful tools in the Jinja2 templating engine (which Ansible uses) that allow you to process data on the fly.
Think of filters as functions that take an input, process it, and return a transformed output. They're applied using the pipe symbol (|
) and can dramatically simplify many common tasks in your Ansible workflows.
Understanding Filters in Ansible
Filters in Ansible follow this basic syntax:
{{ variable | filter_name }}
You can also chain multiple filters together:
{{ variable | filter1 | filter2 | filter3 }}
Let's explore some of the most useful filter categories and practical examples of each.
String Filters
String filters allow you to manipulate text data in various ways.
Basic String Manipulation
# Input variable
name: "ansible automation"
# Templates using filters
Uppercase: {{ name | upper }}
Lowercase: {{ name | lower }}
Title Case: {{ name | title }}
Capitalize: {{ name | capitalize }}
Output:
Uppercase: ANSIBLE AUTOMATION
Lowercase: ansible automation
Title Case: Ansible Automation
Capitalize: Ansible automation
String Operations
# Replace a substring
{{ "Hello World" | replace("World", "Ansible") }}
# Get string length
{{ "Ansible" | length }}
# Default value if variable is undefined
{{ undefined_var | default("Default Value") }}
Output:
Hello Ansible
7
Default Value
List Filters
List filters help you work with arrays and collections of data.
Basic List Operations
# Sample list
users:
- alice
- bob
- charlie
# Join elements with a delimiter
{{ users | join(", ") }}
# Get the first item
{{ users | first }}
# Get the last item
{{ users | last }}
# Get the minimum value
{{ [3, 5, 1, 8, 2] | min }}
# Get the maximum value
{{ [3, 5, 1, 8, 2] | max }}
Output:
alice, bob, charlie
alice
charlie
1
8
Advanced List Manipulation
# Create a unique list (remove duplicates)
{{ [1, 2, 3, 2, 1, 4] | unique }}
# Sort a list
{{ [3, 1, 5, 2, 4] | sort }}
# Shuffle a list (randomize order)
{{ [1, 2, 3, 4, 5] | shuffle }}
# Filter a list (keep only items that are divisible by 2)
{{ [1, 2, 3, 4, 5] | select('divisibleby', 2) | list }}
Output:
[1, 2, 3, 4]
[1, 2, 3, 4, 5]
[3, 1, 5, 4, 2] # (random order)
[2, 4]
Dictionary Filters
Dictionary filters help you work with key-value data structures.
# Sample dictionary
server:
hostname: web-server
ip: 192.168.1.100
ports:
- 80
- 443
enabled: true
# Convert to JSON
{{ server | to_json }}
# Get all keys
{{ server | dict2items | map(attribute='key') | list }}
# Get all values
{{ server | dict2items | map(attribute='value') | list }}
Output:
{"hostname": "web-server", "ip": "192.168.1.100", "ports": [80, 443], "enabled": true}
["hostname", "ip", "ports", "enabled"]
["web-server", "192.168.1.100", [80, 443], true]
Path and File Filters
Ansible provides filters for working with file paths and names.
# Sample path
file_path: "/etc/ansible/hosts.yml"
# Get the base name (filename with extension)
{{ file_path | basename }}
# Get the directory name
{{ file_path | dirname }}
# Get the filename without extension
{{ file_path | splitext | first | basename }}
# Get the file extension
{{ file_path | splitext | last }}
Output:
hosts.yml
/etc/ansible
hosts
.yml
Data Transformation Filters
These filters help you convert between different data formats.
# Convert YAML to JSON
{{ yaml_data | to_json }}
# Convert JSON to YAML
{{ json_data | to_yaml }}
# Convert to nice, human-readable JSON
{{ complex_data | to_nice_json }}
# Convert to nice, human-readable YAML
{{ complex_data | to_nice_yaml }}
Math Filters
Math filters help with numerical operations.
# Basic math
{{ 10 | int + 5 }}
# Round a number
{{ 10.7 | round }}
# Round down (floor)
{{ 10.7 | round(0, 'floor') }}
# Round up (ceil)
{{ 10.3 | round(0, 'ceil') }}
Output:
15
11
10
11
Date and Time Filters
Date and time filters are useful for timestamp formatting and calculations.
# Format current timestamp
{{ now() | to_datetime | strftime('%Y-%m-%d %H:%M:%S') }}
# Add days to a date
{{ '2023-01-01' | to_datetime | dateadd(days=7) | strftime('%Y-%m-%d') }}
# Human-readable time difference
{{ '2023-01-01 12:00:00' | to_datetime | human_to_seconds }}
Output:
2023-06-15 14:37:22 # (will vary based on current time)
2023-01-08
1672574400
IP Address Filters
Ansible provides specialized filters for working with IP addresses and networks.
# Check if an IP is in a network
{{ '192.168.1.100' | ipaddr('192.168.1.0/24') }}
# Get network address
{{ '192.168.1.100/24' | ipaddr('network') }}
# Get netmask
{{ '192.168.1.100/24' | ipaddr('netmask') }}
Output:
True
192.168.1.0
255.255.255.0
Custom Filters with map
and select
The map
and select
filters are particularly powerful as they allow you to perform operations on each item in a collection.
The map
Filter
The map
filter applies a transform to every item in a list:
# Sample list of dictionaries
servers:
- name: web1
ip: 192.168.1.101
role: web
- name: db1
ip: 192.168.1.102
role: database
- name: app1
ip: 192.168.1.103
role: application
# Extract all server names
{{ servers | map(attribute='name') | list }}
# Create a list of server connection strings
{{ servers | map('regex_replace', '^(.*)$', '\\1.example.com') | list }}
Output:
['web1', 'db1', 'app1']
['web1.example.com', 'db1.example.com', 'app1.example.com']
The select
Filter
The select
filter lets you filter items based on a condition:
# Get only web servers
{{ servers | selectattr('role', 'equalto', 'web') | list }}
# Get servers with IP ending in .10x
{{ servers | selectattr('ip', 'match', '\.10[0-9]$') | list }}
Output:
[{'name': 'web1', 'ip': '192.168.1.101', 'role': 'web'}]
[{'name': 'web1', 'ip': '192.168.1.101', 'role': 'web'}, {'name': 'db1', 'ip': '192.168.1.102', 'role': 'database'}, {'name': 'app1', 'ip': '192.168.1.103', 'role': 'application'}]
Real-World Examples
Let's look at some practical examples of how filters are used in real Ansible templates.
Example 1: Generating a Configuration File
# inventory.yml
webservers:
hosts:
web1:
ip: 192.168.1.101
open_ports: [80, 443]
web2:
ip: 192.168.1.102
open_ports: [80, 443, 8080]
Template file (nginx.conf.j2):
# Generated by Ansible - DO NOT EDIT MANUALLY
http {
upstream web_backend {
{% for host in groups['webservers'] %}
server {{ hostvars[host].ip }}:80 weight={{ 100 // groups['webservers'] | length }};
{% endfor %}
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://web_backend;
}
}
# Security headers
{% for header in security_headers | default([]) %}
add_header {{ header.name }} "{{ header.value }}";
{% endfor %}
}
Example 2: Creating a Host Inventory Report
# template: host_report.j2
# Host Inventory Report
# Generated on: {{ now() | to_datetime | strftime('%Y-%m-%d %H:%M:%S') }}
## System Overview
Total Hosts: {{ groups['all'] | length }}
Web Servers: {{ groups['webservers'] | default([]) | length }}
Database Servers: {{ groups['databases'] | default([]) | length }}
## Host Details
| Hostname | IP Address | Role | OS | Memory |
|----------|------------|------|----|---------|
{% for host in groups['all'] %}
| {{ host }} | {{ hostvars[host].ansible_host | default(hostvars[host].ip) | default('N/A') }} | {{ hostvars[host].role | default('N/A') }} | {{ hostvars[host].ansible_distribution | default('N/A') }} | {{ (hostvars[host].ansible_memtotal_mb | default(0) / 1024) | round(1) }} GB |
{% endfor %}
## Service Distribution
{% set roles = [] %}
{% for host in groups['all'] %}
{% set role = hostvars[host].role | default('unknown') %}
{% if role not in roles %}
{% set _ = roles.append(role) %}
{% endif %}
{% endfor %}
{% for role in roles | sort %}
- {{ role | capitalize }}: {{ groups['all'] | selectattr('role', 'equalto', role) | list | length }}
{% endfor %}
Example 3: Dynamic Firewall Rules
# template: firewall.j2
# Firewall Configuration
# Generated on: {{ now() | to_datetime | strftime('%Y-%m-%d') }}
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
# Allow established connections
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Allow SSH from admin network
-A INPUT -p tcp -s {{ admin_network | default('10.0.0.0/8') }} --dport 22 -j ACCEPT
# Service-specific rules
{% for service in services | default([]) %}
{% if service.enabled | default(true) %}
# {{ service.name | upper }} Service
{% for port in service.ports | default([]) %}
-A INPUT -p {{ service.protocol | default('tcp') }} --dport {{ port }} -j ACCEPT
{% endfor %}
{% endif %}
{% endfor %}
# Log and drop everything else
-A INPUT -j LOG --log-prefix "DROPPED: "
-A INPUT -j DROP
COMMIT
Custom Filter Creation
For advanced use cases, you can create your own custom Ansible filters. Here's a simple example of a custom filter plugin:
# filter_plugins/my_filters.py
def reverse_string(string):
return string[::-1]
def add_prefix(string, prefix):
return prefix + string
class FilterModule(object):
def filters(self):
return {
'reverse': reverse_string,
'add_prefix': add_prefix
}
You can then use these custom filters in your templates:
{{ "hello world" | reverse }}
{{ "example.com" | add_prefix("https://") }}
Output:
dlrow olleh
https://example.com
Filter Debugging Tips
When working with complex filter chains, it can be challenging to debug issues. Here are some helpful tips:
- Use the
debug
module to inspect variables:
- name: Debug variables
debug:
var: my_complex_variable | to_json
- Break down complex filter chains into smaller steps:
- name: Break down complex operations
set_fact:
step1: "{{ my_list | map(attribute='name') | list }}"
step2: "{{ step1 | select('match', '^web') | list }}"
final_result: "{{ step2 | join(', ') }}"
- Use the
verbose
flag when running playbooks:
ansible-playbook -v playbook.yml
Summary
Ansible filters are extremely powerful tools that can transform, manipulate, and format data within your templates and playbooks. They allow you to:
- Manipulate strings, lists, and dictionaries
- Transform data between different formats
- Process IP addresses and networks
- Format dates and times
- Create custom data processing functions
By mastering filters, you can make your Ansible templates more dynamic, efficient, and maintainable. Filters enable you to perform complex data transformations directly within your templates, reducing the need for pre-processing or custom scripts.
Additional Resources
- Ansible Filters Documentation
- Jinja2 Template Designer Documentation
- Ansible Galaxy - Filter Collections
Exercises
- Create a template that takes a list of server information and generates a hosts file with comments.
- Write a template that formats a complex data structure into a readable table.
- Develop a custom filter that converts file sizes between different units (bytes, KB, MB, etc.).
- Create a template that uses the
regex_replace
filter to transform a configuration file. - Build a report template that uses the
selectattr
andmap
filters to analyze and summarize inventory data.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)