Variables, Conditionals, Loops
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.
INVALID VARIABLE NAMES
VALID VARIABLE NAMES
web server
web_server
remote.file
remote_file
1st file
file_1, file1
remoteserver$1
remote_server_1, remote_server1

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:
1
#Sample inventory file with variables-inventory.txt
2
centos http_port=8080 snmp_port=161-162 internal_ip_range=192.168.100.0
Copied!
1
---
2
# sample firewall playbook firewall-playbook.yaml
3
4
-
5
name: Set Firewall Configurations
6
hosts: centos
7
become: true
8
tasks:
9
- firewalld:
10
service: https
11
permanent: true
12
state: enabled
13
14
- firewalld:
15
port: "{{ http_port }}/tcp"
16
permanent: true
17
state: disabled
18
19
- firewalld:
20
port: "{{ snmp_port }}/udp"
21
permanent: true
22
state: disabled
23
24
- firewalld:
25
source: "{{ internal_ip_range }}/24"
26
permanent: true
27
zone: internal
28
state: enabled
Copied!
1
[[email protected] demo-var]$ ansible-playbook -i inventory.txt firewall-playbook.yaml
Copied!
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:
1
---
2
3
#sample firewall playbook with vars firewall-playbook.yaml
4
5
-
6
name: Set Firewall Configurations
7
hosts: centos
8
become: true
9
vars:
10
http_port: 8080
11
snmp_port: 161-162
12
internal_ip_range: 192.168.100.0
13
tasks:
14
- firewalld:
15
service: https
16
permanent: true
17
state: enabled
18
19
- firewalld:
20
port: "{{ http_port }}/tcp"
21
permanent: true
22
state: disabled
23
24
- firewalld:
25
port: "{{ snmp_port }}/udp"
26
permanent: true
27
state: disabled
28
29
- firewalld:
30
source: "{{ internal_ip_range }}/24"
31
permanent: true
32
zone: internal
33
state: enabled
Copied!
1
[[email protected] demo-var]$ ansible-playbook firewall-playbook.yaml
Copied!
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:
1
# vars.yaml
2
http_port: 8080
3
snmp_port: 161-162
4
internal_ip_range: 192.168.100.0
Copied!
1
---
2
3
#sample firewall playbook with var_files firewall-playbook.yaml
4
5
-
6
name: Set Firewall Configurations
7
hosts: centos
8
become: true
9
vars_files:
10
- vars.yaml
11
tasks:
12
- firewalld:
13
service: https
14
permanent: true
15
state: enabled
16
17
- firewalld:
18
port: "{{ http_port }}/tcp"
19
permanent: true
20
state: disabled
21
22
- firewalld:
23
port: "{{ snmp_port }}/udp"
24
permanent: true
25
state: disabled
26
27
- firewalld:
28
source: "{{ internal_ip_range }}/24"
29
permanent: true
30
zone: internal
31
state: enabled
Copied!
1
[[email protected] demo-var]$ ansible-playbook firewall-playbook.yaml
Copied!
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 setup The 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.
1
[[email protected] demo-var]$ ansible ubuntu -m setup
2
ubuntu | SUCCESS => {
3
"ansible_facts": {
4
"ansible_all_ipv4_addresses": [
5
"192.168.100.11"
6
],
7
"ansible_all_ipv6_addresses": [
8
"fe80::20c:29ff:fee3:9b49"
9
],
10
"ansible_apparmor": {
11
"status": "enabled"
12
},
13
"ansible_architecture": "x86_64",
14
"ansible_bios_date": "07/22/2020",
15
"ansible_bios_version": "6.00",
16
"ansible_cmdline": {
17
"BOOT_IMAGE": "/boot/vmlinuz-5.4.0-42-generic",
18
"auto": true,
19
"find_preseed": "/preseed.cfg",
20
"locale": "en_US",
21
"noprompt": true,
22
"priority": "critical",
23
"quiet": true,
24
"ro": true,
25
"root": "UUID=e9b96f31-80d2-48ac-a242-b91e5e1be9b0"
26
},
27
"ansible_date_time": {
28
"date": "2021-06-30",
29
"day": "30",
30
"epoch": "1625048412",
31
"hour": "03",
32
.
33
.
34
.
35
"ansible_userspace_architecture": "x86_64",
36
"ansible_userspace_bits": "64",
37
"ansible_virtualization_role": "guest",
38
"ansible_virtualization_type": "VMware",
39
"discovered_interpreter_python": "/usr/bin/python3",
40
"gather_subset": [
41
"all"
42
],
43
"module_setup": true
44
},
45
"changed": false
46
}
Copied!
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.
1
[[email protected] demo-var]$ ansible ubuntu -m setup -a "filter=*family*"
2
ubuntu | SUCCESS => {
3
"ansible_facts": {
4
"ansible_os_family": "Debian",
5
"discovered_interpreter_python": "/usr/bin/python3"
6
},
7
"changed": false
8
}
Copied!

Using the Ansible playbook

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.
1
---
2
3
#sample playbook for gathering facts facts-playbook.yaml
4
5
- hosts: ubuntu
6
tasks:
7
- debug:
8
var: ansible_facts
Copied!
1
[[email protected] demo-var]$ ansible-playbook facts-playbook.yaml
Copied!
and it will gather and shows all facts!
4. Accessing a specific fact using an Ansible playbook: Fetching the Ansible facts, filtering them, and displaying them using a playbook.
1
---
2
3
#Sample playbook for Accessing specific fact fact-playbook.yaml
4
5
- hosts: all
6
tasks:
7
- debug:
8
var: ansible_facts["cmdline"]
Copied!
1
[[email protected] demo-var]$ ansible-playbook fact-playbook.yaml
2
3
PLAY [all] ******************************************************************************************************************************
4
5
TASK [Gathering Facts] ******************************************************************************************************************
6
ok: [centos]
7
ok: [ubuntu]
8
9
TASK [debug] ****************************************************************************************************************************
10
ok: [ubuntu] => {
11
"ansible_facts[\"cmdline\"]": {
12
"BOOT_IMAGE": "/boot/vmlinuz-5.4.0-42-generic",
13
"auto": true,
14
"find_preseed": "/preseed.cfg",
15
"locale": "en_US",
16
"noprompt": true,
17
"priority": "critical",
18
"quiet": true,
19
"ro": true,
20
"root": "UUID=e9b96f31-80d2-48ac-a242-b91e5e1be9b0"
21
}
22
}
23
ok: [centos] => {
24
"ansible_facts[\"cmdline\"]": {
25
"BOOT_IMAGE": "/vmlinuz-3.10.0-1127.el7.x86_64",
26
"LANG": "en_US.UTF-8",
27
"crashkernel": "auto",
28
"quiet": true,
29
"rd.lvm.lv": "vgos/lvswap",
30
"rhgb": true,
31
"ro": true,
32
"root": "/dev/mapper/vgos-lvroot",
33
"spectre_v2": "retpoline"
34
}
35
}
36
37
PLAY RECAP ******************************************************************************************************************************
38
centos : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
39
ubuntu : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Copied!

Debug Module

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.
1
---
2
3
#Sample playbook for debugging debug-playbook.yaml
4
- hosts: centos
5
vars:
6
- first_var: "HAhaha"
7
8
tasks:
9
- name: show results
10
debug: msg="The variable first_var is set to - {{ first_var }}"
Copied!
1
[[email protected] demo-var]$ ansible-playbook debug-playbook.yaml
2
3
PLAY [centos] ***************************************************************************************************************************
4
5
TASK [Gathering Facts] ******************************************************************************************************************
6
ok: [centos]
7
8
TASK [show results] *********************************************************************************************************************
9
ok: [centos] => {
10
"msg": "The variable first_var is set to - HAhaha"
11
}
12
13
PLAY RECAP ******************************************************************************************************************************
14
centos : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Copied!

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.
1
---
2
3
#Sample playbook for registering vars register-playbook.yaml
4
- hosts: centos
5
vars:
6
- var_thing: "eldorado"
7
8
tasks:
9
- name: say something
10
command: echo -e "{{ var_thing }}!\n Do you believe {{ var_thing }} exist?!\nI like to know about {{ var_thing }}"
11
register: results
12
13
- name: show results
14
debug: msg={{ results }}
Copied!
1
[[email protected] demo-var]$ ansible-playbook register-playbook.yaml
2
3
PLAY [centos] ***************************************************************************************************************************
4
5
TASK [Gathering Facts] ******************************************************************************************************************
6
ok: [centos]
7
8
TASK [say something] *********************************************************************************************************************
9
changed: [centos]
10
11
TASK [show results] *********************************************************************************************************************
12
ok: [centos] => {
13
"msg": {
14
"changed": true,
15
"cmd": [
16
"echo",
17
"-e",
18
"eldorado!\\n Do you believe eldorado exist?!\\nI like to know about eldorado"
19
],
20
"delta": "0:00:00.002305",
21
"end": "2021-06-30 16:57:46.480738",
22
"failed": false,
23
"rc": 0,
24
"start": "2021-06-30 16:57:46.478433",
25
"stderr": "",
26
"stderr_lines": [],
27
"stdout": "eldorado!\n Do you believe eldorado exist?!\nI like to know about eldorado",
28
"stdout_lines": [
29
"eldorado!",
30
" Do you believe eldorado exist?!",
31
"I like to know about eldorado"
32
]
33
}
34
}
Copied!
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:
1
[[email protected] demo-var]$ ansible ubuntu -b -m apt -a "name=apache2 state=absent"
2
ubuntu | SUCCESS => {
3
"ansible_facts": {
4
"discovered_interpreter_python": "/usr/bin/python3"
5
},
6
"changed": false
7
}
8
9
[[email protected] demo-var]$ ansible centos -b -m yum -a "name=httpd state=absent"
10
centos | CHANGED => {
11
"ansible_facts": {
12
"discovered_interpreter_python": "/usr/bin/python"
13
},
14
"changed": true,
15
"changes": {
16
"removed": [
17
"httpd"
18
]
19
},
20
"msg": "",
21
"rc": 0,
22
"results": [
23
"Loaded plugins: fastestmirror\nResolving Dependencies\n--> Running transaction check\n---> Package httpd.x86_64 0:2.4.6-97.el7.centos will be erased\n--> Finished Dependency Resolution\n\nDependencies Resolved\n\n================================================================================\n Package Arch Version Repository Size\n================================================================================\nRemoving:\n httpd x86_64 2.4.6-97.el7.centos @updates 9.4 M\n\nTransaction Summary\n================================================================================\nRemove 1 Package\n\nInstalled size: 9.4 M\nDownloading packages:\nRunning transaction check\nRunning transaction test\nTransaction test succeeded\nRunning transaction\n Erasing : httpd-2.4.6-97.el7.centos.x86_64 1/1 \n Verifying : httpd-2.4.6-97.el7.centos.x86_64 1/1 \n\nRemoved:\n httpd.x86_64 0:2.4.6-97.el7.centos \n\nComplete!\n"
24
]
25
}
Copied!

Conditions based on registered variables:

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:
  1. 1.
    Register the outcome of the earlier task as a variable.
  2. 2.
    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 :
1
---
2
3
#sample playbook for conditionals condition-playbook1.yaml
4
- hosts: all
5
become: yes
6
7
tasks:
8
- name: install apache2
9
apt: name=apache2 state=latest
10
ignore_errors: yes
11
register: results
12
13
- name: install httpd
14
yum: name=httpd state=latest
15
failed_when: "'FAILED' in results"
Copied!
and run:
1
[[email protected] demo-var]$ ansible-playbook condition-playbook1.yaml
2
3
PLAY [all] ******************************************************************************************************************************
4
5
TASK [Gathering Facts] ******************************************************************************************************************
6
ok: [centos]
7
ok: [ubuntu]
8
9
TASK [install apache2] ******************************************************************************************************************
10
[WARNING]: Updating cache and auto-installing missing dependency: python-apt
11
fatal: [centos]: FAILED! => {"changed": false, "cmd": "apt-get update", "msg": "[Errno 2] No such file or directory", "rc": 2}
12
...ignoring
13
changed: [ubuntu]
14
15
TASK [install httpd] ********************************************************************************************************************
16
ok: [ubuntu]
17
changed: [centos]
18
19
PLAY RECAP ******************************************************************************************************************************
20
centos : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1
21
ubuntu : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Copied!

Conditionals based on ansible_facts

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:
1
---
2
#sample conditional with facts condition-playbook2.yaml
3
4
- hosts: all
5
become: yes
6
7
tasks:
8
9
- name: Remove Apache on Ubuntu Server
10
apt: name=apache2 state=absent
11
when: ansible_os_family == "Debian"
12
13
- name: Remove Apache on CentOS Server
14
yum: name=httpd state=absent
15
when: ansible_os_family == "RedHat"
Copied!
and lets run it:
1
[[email protected] demo-var]$ ansible-playbook condition-playbook2.yaml
2
3
PLAY [all] ******************************************************************************************************************************
4
5
TASK [Gathering Facts] ******************************************************************************************************************
6
ok: [centos]
7
ok: [ubuntu]
8
9
TASK [Remove Apache on Ubuntu Server] **************************************************************************************************
10
skipping: [centos]
11
changed: [ubuntu]
12
13
TASK [Remove Apache on CentOS Server] *************************************************************************************************
14
skipping: [ubuntu]
15
changed: [centos]
16
17
PLAY RECAP ******************************************************************************************************************************
18
centos : ok=2 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
19
ubuntu : ok=2 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
Copied!

Loops

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 ... .
1
---
2
3
#sample playbook install packages noloop-playbook.yaml
4
5
- hosts: ubuntu
6
become: yes
7
8
tasks:
9
- yum: name=vim state=present
10
- yum: name=nano state=present
11
- yum: name=apache2 state=present
Copied!
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.

with_items

1
[[email protected] demo-var]$ cat loop-playbook1.yaml
2
---
3
4
# sample loop with with_itemp loop-playbook1.yaml
5
6
- hosts: ubuntu
7
become: yes
8
9
tasks:
10
- name: install pakcages
11
apt: name={{ item }} update_cache=yes state=latest
12
with_items:
13
- vim
14
- nano
15
- apache2
Copied!
and lets run it:
1
[[email protected] demo-var]$ ansible-playbook loop-playbook1.yaml
2
3
PLAY [ubuntu] *********************************************************************************
4
5
TASK [Gathering Facts] ************************************************************************
6
ok: [ubuntu]
7
8
TASK [install pakcages] ***********************************************************************
9
changed: [ubuntu] => (item=[u'vim', u'nano', u'apache2'])
10
11
PLAY RECAP ************************************************************************************
12
ubuntu : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
13
Copied!
and if we run it again:
1
[[email protected] demo-var]$ ansible-playbook loop-playbook1.yaml
2
3
PLAY [ubuntu] ***************************************************************************************************************************
4
5
TASK [Gathering Facts] ******************************************************************************************************************
6
ok: [ubuntu]
7
8
TASK [install pakcages] *****************************************************************************************************************
9
ok: [ubuntu] => (item=[u'vim', u'nano', u'apache2'])
10
11
PLAY RECAP ******************************************************************************************************************************
12
ubuntu : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Copied!
with_items is replaced by loop and the flatten filter.

with_file

1
---
2
3
# sample loop with with_file loop-playbook2.yaml
4
5
- hosts: ubuntu
6
become: yes
7
8
tasks:
9
- name: show file(s) contents
10
debug: msg={{ item }}
11
with_file:
12
- myfile1.txt
13
- myfile2.txt
Copied!
1
[[email protected] demo-var]$ cat myfile1.txt
2
This is myfile1.txt, first line :-)
3
[[email protected] demo-var]$
4
[[email protected] demo-var]$ cat myfile2.txt
5
This is myfile2.txt, first line :-0
Copied!
And lets run it:
1
[[email protected] demo-var]$ ansible-playbook loop-playbook2.yaml
2
3
PLAY [ubuntu] ***************************************************************************************************************************
4
5
TASK [Gathering Facts] ******************************************************************************************************************
6
ok: [ubuntu]
7
8
TASK [show file(s) contents] ************************************************************************************************************
9
ok: [ubuntu] => (item=This is myfile1.txt, first line :-)) => {
10
"msg": "This is myfile1.txt, first line :-)"
11
}
12
ok: [ubuntu] => (item=This is myfile2.txt, first line :-0) => {
13
"msg": "This is myfile2.txt, first line :-0"
14
}
15
16
PLAY RECAP ******************************************************************************************************************************
17
ubuntu : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Copied!

with_sequenece

1
---
2
3
# sample loop with with_sequenece loop-playbook3.yaml
4
5
- hosts: ubuntu
6
become: yes
7
8
tasks:
9
- name: show file(s) contents
10
debug: msg={{ item }}
11
with_sequence: start=1 end=5
Copied!
run:
1
[[email protected] demo-var]$ ansible-playbook loop-playbook3.yaml
2
3
PLAY [ubuntu] ***************************************************************************************************************************
4
5
TASK [Gathering Facts] ******************************************************************************************************************
6
ok: [ubuntu]
7
8
TASK [show file(s) contents] ************************************************************************************************************
9
ok: [ubuntu] => (item=1) => {
10
"msg": "1"
11
}
12
ok: [ubuntu] => (item=2) => {
13
"msg": "2"
14
}
15
ok: [ubuntu] => (item=3) => {
16
"msg": "3"
17
}
18
ok: [ubuntu] => (item=4) => {
19
"msg": "4"
20
}
21
ok: [ubuntu] => (item=5) => {
22
"msg": "5"
23
}
24
25
PLAY RECAP ******************************************************************************************************************************
26
ubuntu : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Copied!
with_subelements is replaced by loop and the subelements filter.
Do not forget that the loop definitions comes at the end of your task.
.
.
.
.
.
Last modified 3mo ago