Magic Variables Usage
As I've continued my Ansible journey, I've found that mastering magic variables is essential for creating truly dynamic and intelligent automation. Magic variables are special predefined variables in Ansible that provide valuable information about your hosts, groups, and execution context. Unlike regular variables, they're automatically available and can't be directly set by users - Ansible manages them internally.
In this post, I'll share how you can leverage magic variables to create more flexible and intelligent automation for both Linux and Windows environments. These special variables allow your playbooks to adapt dynamically to your infrastructure context.
What are Magic Variables?
Magic variables in Ansible are special variables that provide information about Ansible's environment and your inventory. They're part of Ansible's special variables category and give you valuable insights into your hosts, groups, and execution context.
The most commonly used magic variables include:
hostvars
- Access variables from other hostsgroups
- Access all groups and their hostsgroup_names
- Check which groups a host belongs toinventory_hostname
- Reference the current host name
Let's explore each of these with practical examples for both Linux and Windows environments.
hostvars - Accessing Other Host Variables
The hostvars
magic variable allows you to access facts and variables from any host in your inventory. This is particularly useful when you need to configure one host based on information from another.
Example: Using hostvars in a Template
Let's say we have a load balancer configuration that needs to include information about all web servers:
---
# playbook: configure_loadbalancer.yml
- name: Configure load balancer settings
hosts: loadbalancers
tasks:
- name: Create nginx configuration from template
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
when: ansible_facts['os_family'] == "Debian" or ansible_facts['os_family'] == "RedHat"
- name: Create IIS configuration from template
template:
src: web.config.j2
dest: C:\inetpub\wwwroot\web.config
when: ansible_facts['os_family'] == "Windows"
In our Jinja2 template for Linux (nginx.conf.j2
):
http {
upstream webservers {
{% for host in groups['webservers'] %}
server {{ hostvars[host]['ansible_facts']['default_ipv4']['address'] }}:80;
{% endfor %}
}
server {
listen 80;
location / {
proxy_pass http://webservers;
}
}
}
For Windows systems, in our web.config.j2
template:
<configuration>
<system.webServer>
<applicationInitialization>
{% for host in groups['webservers'] %}
<add initializationPage="{{ hostvars[host]['ansible_facts']['fqdn'] }}" />
{% endfor %}
</applicationInitialization>
</system.webServer>
</configuration>
Important: For hostvars
to work properly, Ansible needs to have gathered facts from the target hosts. This happens automatically when running a playbook, but if you need to access facts from hosts not included in the current play, you may need to use a setup task or fact caching.
groups - Accessing Groups and Their Hosts
The groups
magic variable provides access to all groups defined in your inventory and the hosts within each group. This is very useful for iterating through hosts in specific groups.
Example: Configuring a Monitoring System
---
# playbook: configure_monitoring.yml
- name: Configure monitoring for all hosts
hosts: monitoring_server
tasks:
- name: Create Linux hosts configuration file
template:
src: linux_hosts.conf.j2
dest: /etc/nagios/conf.d/linux_hosts.cfg
- name: Create Windows hosts configuration file
template:
src: windows_hosts.conf.j2
dest: /etc/nagios/conf.d/windows_hosts.cfg
Linux hosts template (linux_hosts.conf.j2
):
# Linux Servers Configuration
{% for host in groups['linux_servers'] %}
define host {
host_name {{ hostvars[host]['inventory_hostname'] }}
alias {{ hostvars[host]['inventory_hostname'] }}
address {{ hostvars[host]['ansible_facts']['default_ipv4']['address'] }}
check_command check-host-alive
max_check_attempts 5
check_interval 5
retry_interval 1
check_period 24x7
notification_interval 30
notification_period 24x7
notification_options d,u,r
}
{% endfor %}
Windows hosts template (windows_hosts.conf.j2
):
# Windows Servers Configuration
{% for host in groups['windows_servers'] %}
define host {
host_name {{ hostvars[host]['inventory_hostname'] }}
alias {{ hostvars[host]['inventory_hostname'] }}
address {{ hostvars[host]['ansible_facts']['ansible_ip_addresses'][0] }}
check_command check-host-alive
max_check_attempts 5
check_interval 5
retry_interval 1
check_period 24x7
notification_interval 30
notification_period 24x7
notification_options d,u,r
}
{% endfor %}
group_names - Checking Host Group Membership
The group_names
magic variable contains a list of all groups that the current host is a part of. This allows for conditional execution based on group membership.
Example: Conditional Configuration Based on Group Membership
---
# playbook: configure_services.yml
- name: Configure services based on group membership
hosts: all
tasks:
- name: Install web server software
package:
name: "{{ web_server_package }}"
state: present
vars:
web_server_package: "{{ 'httpd' if ansible_facts['os_family'] == 'RedHat' else 'apache2' if ansible_facts['os_family'] == 'Debian' else 'IIS-WebServerRole' }}"
when: "'webservers' in group_names"
- name: Install database server
package:
name: "{{ db_server_package }}"
state: present
vars:
db_server_package: "{{ 'mysql-server' if ansible_facts['os_family'] != 'Windows' else 'SQL2019-SSEI-Expr' }}"
when: "'dbservers' in group_names"
- name: Install monitoring agent
package:
name: "{{ monitoring_agent }}"
state: present
vars:
monitoring_agent: "{{ 'nagios-nrpe-server' if ansible_facts['os_family'] != 'Windows' else 'NSClient++' }}"
when: "'production' in group_names"
You can also use group_names
in templates to create configuration files that differ based on the host's role:
{% if 'webservers' in group_names %}
# Web server specific configuration
MaxClients 200
{% endif %}
{% if 'dbservers' in group_names %}
# Database server specific configuration
max_connections = 500
{% endif %}
{% if 'production' in group_names %}
# Production environment settings
log_level = WARNING
{% elif 'development' in group_names %}
# Development environment settings
log_level = DEBUG
{% endif %}
inventory_hostname - Reference Current Host
The inventory_hostname
magic variable contains the name of the current host as defined in the inventory file. This is useful when you need to refer to the current host's name, especially when gather_facts
is disabled.
Example: Using inventory_hostname in Templates and Tasks
---
# playbook: configure_hostname.yml
- name: Configure hostname settings
hosts: all
gather_facts: false
tasks:
- name: Configure Linux hostname
template:
src: hostname.j2
dest: /etc/hostname
when: "'linux_servers' in group_names"
- name: Configure Windows hostname
win_shell: Rename-Computer -NewName "{{ inventory_hostname_short }}" -Force
when: "'windows_servers' in group_names"
In the hostname.j2
template:
{{ inventory_hostname }}
The inventory_hostname_short
variable contains the first part of the inventory hostname up to the first period, which is useful for setting computer names that don't support FQDNs.
Other Useful Magic Variables
ansible_play_hosts and ansible_play_batch
These variables contain a list of all hosts still active in the current play:
- name: Show all hosts in the current play
debug:
msg: "All hosts in play: {{ ansible_play_hosts }}"
ansible_version
This variable provides information about the Ansible version being used:
- name: Display Ansible version information
debug:
msg:
- "Ansible version: {{ ansible_version.full }}"
- "Major version: {{ ansible_version.major }}"
- "Minor version: {{ ansible_version.minor }}"
A Practical Sequence: Configuring Cross-Server Communication
Let's visualize how magic variables help in a real-world scenario where we need to configure cross-server communication:
Complete Example: Multi-Tier Application Deployment
Here's a complete example that uses multiple magic variables to deploy a multi-tier application across both Linux and Windows hosts:
---
# playbook: deploy_application.yml
- name: Gather facts from all hosts first
hosts: all
tasks:
- name: Just gather facts
debug:
msg: "Gathering facts from {{ inventory_hostname }}"
- name: Configure database servers
hosts: dbservers
tasks:
- name: Configure PostgreSQL to allow connections
template:
src: pg_hba.conf.j2
dest: /var/lib/pgsql/data/pg_hba.conf
when: ansible_facts['os_family'] == 'RedHat'
- name: Configure SQL Server to allow connections
win_template:
src: sql_server_config.j2
dest: C:\Program Files\Microsoft SQL Server\MSSQL15.SQLEXPRESS\MSSQL\DATA\config.ini
when: ansible_facts['os_family'] == 'Windows'
- name: Configure web servers
hosts: webservers
tasks:
- name: Create Apache virtual host configuration
template:
src: vhost.conf.j2
dest: /etc/apache2/sites-available/myapp.conf
when: ansible_facts['os_family'] == 'Debian'
- name: Create IIS site configuration
win_template:
src: iis_site.j2
dest: C:\inetpub\wwwroot\myapp\web.config
when: ansible_facts['os_family'] == 'Windows'
Database template for PostgreSQL (pg_hba.conf.j2
):
# Allow connections from web servers
{% for host in groups['webservers'] %}
{% if 'linux_servers' in hostvars[host]['group_names'] %}
host all all {{ hostvars[host]['ansible_facts']['default_ipv4']['address'] }}/32 md5
{% endif %}
{% endfor %}
Web server template for Apache (vhost.conf.j2
):
<VirtualHost *:80>
ServerName {{ inventory_hostname }}
DocumentRoot /var/www/html/myapp
<Directory /var/www/html/myapp>
AllowOverride All
</Directory>
# Database connection settings
SetEnv DB_HOST {% for host in groups['dbservers'] %}{% if 'linux_servers' in hostvars[host]['group_names'] %}{{ hostvars[host]['ansible_facts']['default_ipv4']['address'] }}{% endif %}{% endfor %}
SetEnv DB_NAME myapp
SetEnv DB_USER appuser
</VirtualHost>
IIS site configuration (iis_site.j2
):
<configuration>
<appSettings>
<add key="DatabaseServer" value="{% for host in groups['dbservers'] %}{% if 'windows_servers' in hostvars[host]['group_names'] %}{{ hostvars[host]['ansible_facts']['ansible_ip_addresses'][0] }}{% endif %}{% endfor %}" />
<add key="DatabaseName" value="myapp" />
<add key="DatabaseUser" value="appuser" />
</appSettings>
</configuration>
Best Practices for Using Magic Variables
Gather Facts First: Ensure facts are gathered before using
hostvars
to access facts from other hosts.Use Fact Caching: For large deployments, enable fact caching to improve performance:
# ansible.cfg [defaults] fact_caching = jsonfile fact_caching_connection = /path/to/facts_cache fact_caching_timeout = 7200
Error Handling: Add checks for variables that might not exist:
{% if hostvars[host]['ansible_facts']['default_ipv4'] is defined %} {{ hostvars[host]['ansible_facts']['default_ipv4']['address'] }} {% endif %}
Iterate with Care: When iterating through large inventories, consider the performance impact.
Conclusion
Magic variables are powerful tools in the Ansible ecosystem that allow for dynamic and intelligent automation across both Linux and Windows environments. They provide critical context about your infrastructure, enabling playbooks to adapt to different scenarios and configurations.
By understanding and effectively using magic variables like hostvars
, groups
, group_names
, and inventory_hostname
, you can create more flexible, reusable, and maintainable automation.
The examples in this post demonstrate how magic variables can simplify cross-server configurations, conditional logic based on group membership, and environment-specific settings. These capabilities are especially valuable in heterogeneous environments where both Linux and Windows systems need to be managed cohesively.
In my next post, I'll explore Ansible play recap and return values, which provide valuable insights into playbook execution results and how to handle them effectively. Stay tuned!
Last updated