âChapter 5 - Ansible Playbooks - Beyond the Basicsâ
âJeff Geerling, âAnsible for DevOpsâ
âChapter 5. Variables and Factsâ
âBas Meijer, Lorin Hochstein, Rene Moser, âAnsible: Up and Runningâ
Core Idea
Variables parameterize plays and facts describe the target: define variables in files or inline, register results into new variables, and gather facts to branch on real system state.
- Variables: defining them, variable files, interpolation, registering module results, and reading dictionary keys.
- Facts: viewing all or a subset, module-generated facts, and local facts.
- Two book chapters (Geerling and Ansible Up & Running) anchor the note.
Variables
Define Variables
vars:
tls_dir: /etc/nginx/ssl/
key_file: nginx.key
server_name: localhostUsing var file
vars_files:
- nginx.yml# nginx.yml
tls_dir: /etc/nginx/ssl/
key_file: nginx.key
server_name: localhostVariable Interpolation
- name: Varible
debug:
msg: "The file used was {{ conf_file }}"- name: Concatanate variable
debug:
msg: "The URL is https://{{ sever_name ~'.'~ domain +_name }}"Registering Variables
Ansible module returns results in JSON format. To use these results, you create a registered variable using the register clause when invoking a module.
- name: Capture and display the current user
hosts: localhost
tasks:
- name: Capture output of whoami
ansible.builtin.command: whoami
register: login
- name: Show output
ansible.builtin.debug:
msg: "The user running this playbook is: {{ login.stdout }}"Accessing Dictionary Keys
If a variable contains a dictionary, you can access the keys of the dictionary by using either a dot (.) or a subscript ([]).
{{ result.stat }}We could have used subscript notation instead:
{{ result['stat'] }}This rule applies to multiple de-references, so all of the following are equivalent:
result['stat']['mode']
result['stat'].mode
result.stat['mode']
result.stat.modeFacts
When Ansible gathers facts, it connects to the hosts and queries it for all kinds of details about the hosts: CPU architecture, operating system, IP addresses, memory info, disk info, and more.
- name: 'Ansible facts.'
hosts: all
gather_facts: true
tasks:
- name: Print out OS details
debug:
msg: >-
os_family: {{ ansible_facts.os_family }},
distro:
{{ ansible_facts.distribution }}
{{ ansible_facts.distribution_version }},
kernel:
{{ ansible_facts.kernel }# View all facts
ansible server -m setup
# Viewing a Subset of Facts
ansible all -m setup -a 'filter=ansible_all_ipv6_addresses'Module Generated Facts
- Some Ansible modules return a dictionary with the keyÂ
ansible_facts. - These facts are automatically associated with the active host as variables.
- Modules ending withÂ
_info return information about non-unique host objects (e.g., services, packages).
- name: Get services facts
service_facts:
- name: Debug SSHD service state
debug: var=ansible_facts['services']['sshd.service']Local Facts
- Files placed inÂ
/etc/ansible/facts.d on a host are treated as custom facts. - Supported formats:
.ini- JSON
- Executables outputting JSON
- name: Print book title
debug: msg="The title is {{ ansible_local.example.book.title }}"Set Facts
- Defines new variables dynamically within a playbook.
- Useful for simplifying variable references or defining new facts based on conditions.
- name: Set nginx state
when: ansible_facts.services.nginx.state is defined
set_fact:
nginx_state: "{{ ansible_facts.services.nginx.state }}"Magic / Built-in variables
đ Discovering variables: facts and magic variables â Ansible Community Documentation
| Parameter | Description |
|---|---|
hostvars | A dict whose keys are Ansible hostnames and values are dicts that map variable names to values |
inventory_hostname | The name of the current host as known in the Ansible inventory, might include domain name |
inventory_hostname_short | Name of the current host as known by Ansible, without the domain name (e.g., myhost) |
group_names | A list of all groups that the current host is a member of |
groups | A dict whose keys are Ansible group names and values are a list of hostnames that are members of the group. Includes all and ungrouped groups: {âallâ: [âŠ], âwebâ: [âŠ], âungroupedâ: [âŠ]} |
ansible_check_mode | A boolean that is true when running in check mode (see âCheck Modeâ) |
ansible_play_batch | A list of the inventory hostnames that are active in the current batch (see âRunning on a Batch of Hosts at a Timeâ) |
ansible_play_hosts | A list of all of the inventory hostnames that are active in the current play |
ansible_version | A dict with Ansible version info: {âfullâ: 2.3.1.0â, âmajorâ: 2, âminorâ: 3, ârevisionâ: 1, âstringâ: â2.3.1.0â} |
hostvarsis computed when you run Ansible, whilehost_varsis a directory that you can use to define variables for a particular system.
Variable Precedence
đ Using Variables
1. The when Statement
- Purpose: Executes tasks conditionally based on variables or previous task outputs.
- Examples:
- Use boolean variables (
when: is_db_server). - Handle undefined variables (
when: is_db_server is defined and is_db_server). - Evaluate registered variables (
when: 'ready' in myapp_result.stdout).
- Use boolean variables (
2. changed_when and failed_when
changed_when: Overrides the default behavior for determining task changes.- Example: Detect changes based on specific output (e.g.,
'Nothing to install' not in composer.stdout).
- Example: Detect changes based on specific output (e.g.,
failed_when: Customizes failure conditions.- Example: Ignore errors if a specific string is present in
stderr.
- Example: Ignore errors if a specific string is present in
3. ignore_errors
- Purpose: Prevents Ansible from failing when a task encounters errors.
- Caution: Use sparingly, as it may hide actual issues.
4. Delegation and Local Actions
- Some tasks, like sending a notification, communicating with load balancers, or making changes to DNS, networking, or monitoring servers, require Ansible to run the task on the host machine (running the playbook) or another host besides the one(s) being managed by the playbook. Ansible allows any task to be delegated to a particular host using
delegate_to:
- name: Run a task on the control node
hosts: webservers
tasks:
- name: Add a server to the load balancer
ansible.builtin.command: "add-to-lb {{ inventory_hostname }}"
delegate_to: localhost- Local Execution: Use
local_actionas shorthand for tasks run on the control machine.- Example: Manage load balancers or check system states locally.
5. Pausing Execution with wait_for
- Purpose: Wait for resources (e.g., ports, files, or drained connections) to become available.
- Features:
- Wait for ports to open or close.
- Check for file presence or absence.
- Introduce delays for synchronization.
- name: Wait for web server to start
local_action:
module: wait_for
host: "{{ inventory_hostname }}"
port: "{{ webserver_port }}"
delay: 10
timeout: 300
state: started6. Running Playbooks Locally
- Use Case: Run playbooks on the same machine as the Ansible control host using
--connection=local. - Benefits:
- Speeds up execution by avoiding SSH overhead.
- Useful for self-provisioning or CI/CD pipelines.
- Example:
- hosts: 127.0.0.1
gather_facts: no
tasks:
- name: Get the current date
command: date
register: date
- debug:
var: date.stdoutExtras
Prompts
private: If set toyes, the userâs input will be hidden on the command line.default: You can set a default value for the prompt, to save time for the end user.
- hosts: all
vars_prompt:
- name: Username
prompt: "Enter Your Username?"
private: FalseTags
Tags allow you to run (or exclude) subsets of a playbookâs tasks.
You can tag roles, included files, individual tasks, and even entire plays.
- hosts: servers
tags: deploy
roles:
- role: tomcat
tags: ['tomcat','app']
tasks:
- name: Notify
local_action:
module: osx_say
msg: "{{inventory_hostname}} is finished"
tags:
- notificationadding more than one tag, you have to use YAMLâs list syntax, for example:
# shorthand list
tags: ['red', 'green']
# Explicit List
tags:
- red
- greenBlocks
blocks are a way to group a set of tasks together in a playbook. Blocks allow you to apply conditional checks, error handling, and other features to a group of tasks at once, making your playbooks more organized and efficient.
- hosts: web
tasks:
- block:
- dnf: name=https state=present
- template: src=httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf
- service: name=httpd state=started enabled=yes
when: ansible_os_family = 'RedHat'
become: yes
- block:
- apt: name=apache2 state=present
- template: src=httpd.conf.j2 dest=/etc/apache2/apache2.conf
- service: name=apache2 state=started enabled=yes
when: ansible_os_family == 'Debian'
become: yesTasks inside the block will be run first. If there is a failure in any task in block, tasks inside rescue will be run. The tasks inside always will always be run, whether or not there were failures in either block or rescue.
It may not be necessary to use block/rescue/always.