In our last article we discussed what Ansible playbooks are and how to create and run tasks through playbooks. In this article, we will go one step further and learn how to use variables in Ansible playbooks.
Table of Contents
What Are Ansible Variables?
Variables are basic concepts in all the programming languages which are used to store data and later be used in the code. Similarly, ansible has variables that store some values, and they are later used in the playbook for different processing.
Ansible supports declaring variables in multiple places like playbooks, host_vars & group_vars files, and command-line arguments.
In the upcoming sections, we will discuss how to define variables in different places and learn about variable precedence.
Variable - Key, Value Mapping
Variables can be defined at the play level and task level using the "vars
" keyword. After the keyword vars, the variable with key and value is given. These variables can be accessed by all the tasks in the play.
I have created one task which is using the debug module to print a message by using variable values. The variables should be enclosed with double curly braces {{ variable }}.
- name: Print vars hosts: m1 gather_facts: false vars: os_name: "PoP!_OS Desktop" version: "21.10" tasks: - name: Task1 - Substitute variables debug: msg: "My current desktop OS is {{ os_name }} - {{ version }}" Key-value variable Mapping
Now to run the playbook, run the following command in the terminal.
$ ansible-playbook playbook_name.yml
From the below image you can see both my variables are replaced with their values when the msg is printed to stdout.
Variable - List Of Elements
You can create a list of elements in a variable. If you take a look at the below definition, I have created a list variable in two ways.
- Standard YAML way of defining a list of items/elements. Here I have given the variable names as "top_linux_desktops".
- Python syntax for creating a list of items/elements. Here I have given the variable names as "top_desktops".
vars: top_linux_desktops: - MXlinux - pop-os - Linux Mint - Manjaro - Fedora top_desktops: [MXLinux, pop-os, Linux Mint, Manjaro, Fedora]
You can print all the elements from the list using the variable name. I have created two tasks. The first task will print elements from the top_linux_desktops variable and the second task will print elements from the top_desktops variable.
tasks: - name: Task1 - List of elements debug: msg: "My fav linux desktops are {{ top_linux_desktops }}" - name: Task2 - List of elements using Python syntax debug: msg: "My fav linux desktops are {{ top_desktops }}"
In list, you can also access the individual element using its Index position similar to how you access elements from a python list. There are two ways to do it.
- Using dot notation(variable.index)
- Python notation(variable[index])
I have created two tasks. The first task uses dot notation and the second task uses python notation to print the element from the first and second positions.
tasks: - name: Task3 - Accessing List element using dot notation debug: msg: "My fav linux desktops are {{ top_linux_desktops.1 }} and {{ top_desktops[2] }}" - name: Task4 - Accessing List element using python notation(list[i]) debug: msg: "My fav linux desktops are {{ top_linux_desktops[1] }} and {{ top_desktops[2] }}"
Variable - Dictionary
You can create a dictionary object and assign it to the variable. This is similar to python dictionaries. Dictionaries can be created in two ways.
- standard YAML syntax to define dictionary
- Python dictionary notation
If you can see the below playbook snippet, I have created two dictionary variables. The first variable "release_info" follows the YAML syntax approach and the second variable "new_release_info" follows python dictionary syntax.
vars: release_info: name: PoP!_OS Desktop version: 22.04 release_month: April, 2022 new_release_info: { name: PoP!_OS Desktop, version: 22.04, release_month: "April, 2022"}
You can either grab all the values from the variable or a particular dictionary element using its key. Similar to the list, the dictionary also follows dot and python notation to print the value of a key.
I have created two tasks, the first task will use the dot notation to grab the element from the dictionary and the second task will use python notation to grab the element.
tasks: - name: Task1 - Accessing dictionary values using its key with dot notation(dict.key) debug: msg: "{{ release_info.name }} version {{ release_info.version }} is released on {{ release_info.release_month }}" - name: Task2 - Accessing dictionary values using its key with python notation(dict['key']) debug: msg: "{{ new_release_info['name'] }} version {{ new_release_info['version'] }} is released on {{ new_release_info['release_month'] }}"
Play Vs Task Level Precedence
Variables can be defined at both task level and play level but variables defined at the task level take higher precedence over the variables defined at the play level.
In the below example, I have created vars at both play level and task level with the same variable names. Now when I run the playbook it will use task variable names.
vars: os_name: "PoP!_OS Desktop" version: "21.10" tasks: - name: Task1 - Substitute variables vars: os_name: "Linux Mint" version: "20.03" debug: msg: "My current desktop OS is {{ os_name }} - {{ version }}"
Playbook Vs Command Line Argument Precedence
You can override the variable passed in the playbook by using -e
flag. Higher precedence is given to the variables passed through -e
flag.
I am running the same playbook from the previous section again overriding the variables with the -e
flag.
$ ansible-playbook 4_var_precedence.yml -e "os_name=fedora" -e "version=35"
You can pass variables to -e
flag in Json
, YAML
or ini
format.
# INI FORMAT $ ansible-playbook 4_var_precedence.yml -e "os_name=fedora" -e "version=35" # JSON FORMAT $ ansible-playbook 4_var_precedence.yml -e '{"os_name": "fedora"}' -e '{"version": 35}' # YAML FORMAT $ ansible-playbook 4_var_precedence.yml -e "{os_name: fedora}" -e "{version: 35}"
You can also create a separate file for variable and pass it through -e
flag. The syntax will be as follows. Here I have created a file named vars.yml
and grouped all my variables. Now when the file is passed to the -e
flag all the variables are imported into the playbook.
$ ansible-playbook 4_var_precedence.yml -e @vars.yml
Variable File
Instead of defining the variable in the playbook, you can create a file and declare all variables in the file. I have created a file called vars.yml
and grouped all the variables we have discussed in previous sections in this file.
Instead of using the keyword vars, you should use vars_files In the playbook and pass the file name. Now when you run the playbook ansible will pick the variables from the file. The file can be in any path.
Host And Group Variables
You can define host level and group level variables in the inventory file. You can refer to the following article where we have briefly discussed how to create host level and group level variables.
As a best practice, you should not define the variables inside the inventory file instead you can create directories for host_vars
and group_vars
and ansible will automatically pick the files in the directory. Create a directory called host_vars
.
$ mkdir host_vars
Inside host_vars
directory, you can define host-level variables i.e. you can create an INI, YAML or JSON format file and store variables for a particular host. If you take a look at my inventory file below I have two hosts named “ubuntu” and “rocky” and created the variable file for each host.
Heads Up: The files should be named the same as the host/alias name in your inventory file.
# Inventory file [m1] ubuntu ansible_host=ubuntu.anslab.com [m2] rocky ansible_host=rocky.anslab.com
$ mkdir host_vars/ubuntu.yml $ echo "message: This variable is read from host_vars/ubuntu.yml file" > host_vars/ubuntu.yml" $ mkdir host_vars/rocky.yml $ echo "message: This variable is read from host_vars/rocky.yml file" > host_vars/rocky.yml
I have added a variable named "message
" in both the variable files. Now if I run my playbook, variables will be picked up from these two files.
Similarly, you can also create variable files for groups. Create a directory "group_vars
" and create a file with the group name as per the inventory file.
[m1] ubuntu ansible_host=ubuntu.anslab.com [m2] rocky ansible_host=rocky.anslab.com [servers:children] m1 m2
I have created a child group called "servers", so I am creating the file name as servers.yml
.
$ mkdir group_vars $ mkdir group_vars/servers.yml $ echo "message: This variable is read from group_vars/servers.yml file" > group_vars/servers.yml
Now if I run the playbook it will read the servers.yml
from group_vars.
Heads Up: If you have both host_vars
and group_vars
, ansible will first search for host_vars
definitions, and if not found it will go to group_vars
.
Magic Variables
Ansible provides some internal variables where the state of the variable is defined when you run the playbook. We can use these variables through the playbook. To get the list of available special variables you can refer to the following link.
For example, there is a variable called inventory_dir
which stores the absolute path for the inventory file that is used by the playbook.
- name: Magic Variables - Get inventory directory path debug: msg: "{{ inventory_dir }}"
An important magic variable is hostvars
. This variable is going to print a collection of some magic variables in the json format.
- name: Magic Variables - hostvars debug: msg: "{{ hostvars }}"
The output contains information in str, list, and dictionary format. Let’s say I want to check in which group my ubuntu host is in then I can get it in the following way. I am using python syntax notation here.
- name: Magic Variables debug: msg: "{{ hostvars['ubuntu']['group_names'] }}"
Not all magic variables are useful in day-to-day operations. Take a look at all the magic variables and see which one fits your use case.
Facts As Variable
When you run the playbook ansible will use the setup module and collect facts from the targeted hosts and keep the output in memory so it can be used in the playbook. Facts are also called playbook variables.
First, get to know about the facts output so you can grab particular attributes. Run the following command which will collect the facts output and store it in a file.
$ ansible all -m setup --tree /tmp/facts_result
Separate output file will be created for each host. If you take a look at the output it is nothing but JSON format output.
The attributes in the facts output will be in List, Dictionary, and AnsibleUnsafeText format. Below are a few examples of different types.
- name: Facts output - AnsibleUnsafeText debug: msg: "{{ discovered_interpreter_python }}" - name: Facts output - List debug: msg: "{{ ansible_all_ipv4_addresses }}" - name: Facts output - Dictionary debug: msg: "{{ ansible_python }}"
There are tons of information collected by facts so take your time to go through the output and see what fits your requirement.
Conclusion
In this article, we have discussed what is ansible variable and how to declare variables in different places. Variable precedence is very important when declaring variables and that is covered in this article. We also discussed magic variables and their use case. Finally, we covered what is facts and how to use facts output as variables.