Now that we know how ansible works, lets get familiar with more advanced options in ansible. By using these options complicated tasks seem a lot easier.
variables
Just like any other Scripting or programming language we can use variable in ansible playbooks. Variables could store different values for different items. Variables help us to have shorter and more readable playbooks. Imagine we want to apply patches on hundreds of servers, the only thing we need is single playbook with some variables for all hundred servers! It's the variables that store information about different IP addresses, host names, username or passwords,...
Naming Variables
Variable names must start with a letter, and they can only contain letters, numbers, and underscores. The following table illustrates the difference between invalid and valid variable names.
Defining Variables
Variables can be defined in a variety of places in an Ansible project. However, this can be simplified to three basic scope levels:
Global scope: Variables set from the command line or Ansible configuration.
Play scope: Variables set in the play and related structures.
Host scope: Variables set on host groups and individual hosts by the inventory, fact gathering, or registered tasks.
If the same variable name is defined at more than one level, the level with the highest precedence wins. A narrow scope takes precedence over a wider scope: variables defined by the inventory are overridden by variables defined by the playbook, which are overridden by variables defined on the command line.
Host scope: We have already seen using variables when we talked about Ansible inventory files. As as example lets write down a playbook to configure multiple firewall configuration. We want to make it reusable for some one else to change ports, for that lets move variables to the inventory file:
#Sample inventory file with variables-inventory.txt
centos http_port=8080 snmp_port=161-162 internal_ip_range=192.168.100.0
play scope: Ansible playbook supports defining the variable in two forms, Either a Single liner variable declaration like we do in any common programming languages or as a separate file with full of variables and values like a properties file.
vars to define inline variables within the playbook
vars_files to import files with variables
Lets repeat previous example by moving variables in to the playbook ,It can be done with vars like this:
If you want to keep the variables in a separate file and import it with vars_filesYou have to first save the variables and values in the same format you have written in the playbook and the file can later be imported using vars_files like this:
Jinja2 templating : the format {{ }} we are using to use variables is called Jinja2 templating. Be careful about Quotes :
source: {{ http_port }}
source: "{{ http_port }}"
source: " Somthing {{ http_port }} Somthing "
Ansible facts
Ansible facts are data gathered about target nodes and returned back to controller nodes. Ansible facts are stored in JSON format and are used to make important decisions about tasks based on their statistics. Facts are in an ansible_facts variable, which is managed by Ansible Engine. Ansible facts play a major role in syncing with hosts in accordance with real-time data.
Normally, every play runs the setup module automatically before the first task in order to gather facts. This is reported as the Gathering Facts task in Ansible 2.3 and later, or simply as setup in older versions of Ansible. By default, you do not need to have a task to run setup in your play. It is normally run automatically for you.
Some of the facts gathered for a managed host might include:
The hostname
The kernel version
The network interfaces
The IP addresses
The version of the operating system
Various environment variables
The number of CPUs
The available or free memory
The available disk space
Accessing the facts
Ansible facts use the setup module for gathering facts every time before running the playbooks.
Using the Ansible ad-hoc commands
1. Access Ansible facts using ad-hoc commands: ansible all -m setupThe setup module fetches all the details from the remote hosts to our controller nodes and dumps them directly to our screen for the facts to be visible to users.
2. Filtering out a specific value from Ansible facts: ansible all -m setup -a "filter=YourFilterHere"Here, the setup module is used to fetch the facts about the system, and further, it will use the filter argument to display the value from the Ansible facts.
To access the variables from Ansible facts in the Ansible playbook, we need to use the actual name without using the ansible keyword.
ansible_facts["ansible_system"] ❌
ansible_facts["system"] ✔️
The gather_facts module from the Ansible playbook runs the setup module by default at the start of each playbook to gather the facts about remote hosts.
3. Accessing facts using Ansible playbook: Fetch the Ansible facts and display them using a playbook.
When you are working with Ansible playbooks, it’s great to have some debug options. Ansible provides a debug module that makes this task easier. It is used to print the message in the log output. The message is nothing but any variable values or output of any task.
---
#Sample playbook for debugging debug-playbook.yaml
- hosts: centos
vars:
- first_var: "HAhaha"
tasks:
- name: show results
debug: msg="The variable first_var is set to - {{ first_var }}"
[user1@controller demo-var]$ ansible-playbook debug-playbook.yaml
PLAY [centos] ***************************************************************************************************************************
TASK [Gathering Facts] ******************************************************************************************************************
ok: [centos]
TASK [show results] *********************************************************************************************************************
ok: [centos] => {
"msg": "The variable first_var is set to - HAhaha"
}
PLAY RECAP ******************************************************************************************************************************
centos : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Register Variables
Ansible registers are used when you want to capture the output of a task to a variable. You can then use the value of these registers for different scenarios like a conditional statement, logging etc.
The variables will contain the value returned by the task. The common return values are documented in Ansible docs. Some of the modules like shell, command etc. have module specific return values. These will be documented in the module docs.
Each registered variables will be valid on the remote host where the task was run for the rest of the playbook execution.
---
#Sample playbook for registering vars register-playbook.yaml
- hosts: centos
vars:
- var_thing: "eldorado"
tasks:
- name: say something
command: echo -e "{{ var_thing }}!\n Do you believe {{ var_thing }} exist?!\nI like to know about {{ var_thing }}"
register: results
- name: show results
debug: msg={{ results }}
[user1@controller demo-var]$ ansible-playbook register-playbook.yaml
PLAY [centos] ***************************************************************************************************************************
TASK [Gathering Facts] ******************************************************************************************************************
ok: [centos]
TASK [say something] *********************************************************************************************************************
changed: [centos]
TASK [show results] *********************************************************************************************************************
ok: [centos] => {
"msg": {
"changed": true,
"cmd": [
"echo",
"-e",
"eldorado!\\n Do you believe eldorado exist?!\\nI like to know about eldorado"
],
"delta": "0:00:00.002305",
"end": "2021-06-30 16:57:46.480738",
"failed": false,
"rc": 0,
"start": "2021-06-30 16:57:46.478433",
"stderr": "",
"stderr_lines": [],
"stdout": "eldorado!\n Do you believe eldorado exist?!\nI like to know about eldorado",
"stdout_lines": [
"eldorado!",
" Do you believe eldorado exist?!",
"I like to know about eldorado"
]
}
}
use debug: msg={{ results.stdout_lines }} in playbook to see just the output results.
Conditionals
Some time we need to add a condition to each task and saying when we want the task to run. As an example lets try to install/remove apache and httpd packages on both ubuntu and centos, but this time lets use conditions.
First lets make sure no apache or httpd is installed on both targets:
Often in a playbook you want to execute or skip a task based on the outcome of an earlier task. To create a conditional based on a registered variable:
Register the outcome of the earlier task as a variable.
Create a conditional test based on the registered variable.
You create the name of the registered variable using the register keyword. A registered variable always contains the status of the task that created it as well as any output that task generated. You can use registered variables in conditional , also it is possible to access the string contents of the registered variable :
Often you want to execute or skip a task based on facts. As we mentioned before Facts are attributes of individual hosts, including IP address, operating system, the status of a filesystem, and many more. With conditionals based on facts:
You can install a certain package only when the operating system is a particular version.
You can skip configuring a firewall on hosts with internal IP addresses.
You can perform cleanup tasks only when a filesystem is getting full.
Not all facts exist for all hosts. For example, the ‘lsb_major_release’ fact only exists when the lsb_release package is installed on the target host.
In example below, we remove web server package from each server based on its os family:
---
#sample conditional with facts condition-playbook2.yaml
- hosts: all
become: yes
tasks:
- name: Remove Apache on Ubuntu Server
apt: name=apache2 state=absent
when: ansible_os_family == "Debian"
- name: Remove Apache on CentOS Server
yum: name=httpd state=absent
when: ansible_os_family == "RedHat"
When automating server setup, sometimes you’ll need to repeat the execution of the same task using different values. For instance, you may need to install multiple packages , or ... .
To avoid repeating the task several times in your playbook file, it’s better to use loops instead. There are different kinds of loops. Here we take a look at standard ones.
[user1@controller demo-var]$ cat myfile1.txt
This is myfile1.txt, first line :-)
[user1@controller demo-var]$
[user1@controller demo-var]$ cat myfile2.txt
This is myfile2.txt, first line :-0
And lets run it:
[user1@controller demo-var]$ ansible-playbook loop-playbook2.yaml
PLAY [ubuntu] ***************************************************************************************************************************
TASK [Gathering Facts] ******************************************************************************************************************
ok: [ubuntu]
TASK [show file(s) contents] ************************************************************************************************************
ok: [ubuntu] => (item=This is myfile1.txt, first line :-)) => {
"msg": "This is myfile1.txt, first line :-)"
}
ok: [ubuntu] => (item=This is myfile2.txt, first line :-0) => {
"msg": "This is myfile2.txt, first line :-0"
}
PLAY RECAP ******************************************************************************************************************************
ubuntu : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0