Ansible - Defining Variables As Dictionaries
This post is to go through an example of defining Ansible variables as dictionaries rather than lists. I wanted to throw this together as I have not found a decent example of doing this.
NOTE: Dictionaries are composed of
key: value
pairs.
When using Ansible you will generally find examples which use lists for variable definitions rather than dictionaries. So below I will show an example of a list and then compare it to a dictionary.
In this example I will be defining what would end up becoming the /etc/network/interfaces
file on a Debian
based system. The example of the list definitions will be a
short excerpt compared to the example of using dictionaries.
Lists
When using lists all definitions begin with a "- "
:
NOTE: Notice the space after the
-
.
network_interfaces:
- name: enp0s3
configure: true
method: dhcp
parameters:
- param: pre-up sleep
val: 2
- name: enp0s8
configure: true
method: static
address: 192.168.250.10
netmask: 255.255.255.0
- name: enp0s9
configure: true
comment: bond0 member
method: manual
parameters:
- param: bond_master
val: bond0
- name: enp0s10
configure: true
comment: bond0 member
method: manual
parameters:
- param: bond_master
val: bond0
- name: enp0s16
configure: true
comment: br0 member
method: manual
As you can see from the above definitions this tends to be the traditional way of defining variables in Ansible. (Even for me!!)
Dictionaries
When using dictionaries the defintions are in a simple key: value
pair.
NOTE: Notice the space after the
:
.
Below is an example of the same definitions in our list example but changed to be used as dictionaries.
network_interfaces:
enp0s3:
configure: true
method: dhcp
parameters:
pre-up: sleep 2
enp0s8:
address: 10.0.0.100
configure: true
gateway: 10.0.0.1
method: static
netmask: 255.255.255.0
enp0s09:
configure: true
method: manual
parameters:
bond_master: bond0
enp0s10:
configure: true
method: manual
parameters:
bond_master: bond0
enp0s16:
configure: true
method: manual
enp0s17:
configure: true
method: manual
parameters:
pre-up: ifconfig $IFACE up
post-down: ifconfig $IFACE down
lo:
configure: true
method: loopback
As you can see, the format is extremely different but tends to be a bit cleaner looking in my opinion.
Usage
So how could we use dictionaries along with a Jinja2
template to generate our
/etc/network/interfaces
file? Let’s look at the example below.
Definitions
Let’s first define a more elaborate set of variables using dictionaries.
---
network_bonds:
bond0:
address: 192.168.1.10
comment: Bond Group 0
configure: true
method: static
netmask: 255.255.255.0
parameters:
bond_mode: active-backup
bond_miimon: 100
primary: enp0s9
slaves:
- enp0s9
- enp0s10
network_bridges:
br0:
address: 192.168.1.11
comment: Bridge 0
configure: true
method: static
netmask: 255.255.255.0
gateway: 192.168.1.1
parameters:
bridge_stp: off
bridge_fd: 0
ports:
- enp0s16
network_interfaces:
enp0s3:
configure: true
method: dhcp
parameters:
pre-up: sleep 2
enp0s8:
address: 10.0.0.100
configure: true
gateway: 10.0.0.1
method: static
netmask: 255.255.255.0
enp0s09:
configure: true
method: manual
parameters:
bond_master: bond0
enp0s10:
configure: true
method: manual
parameters:
bond_master: bond0
enp0s16:
configure: true
method: manual
enp0s17:
configure: true
method: manual
parameters:
pre-up: ifconfig $IFACE up
post-down: ifconfig $IFACE down
lo:
configure: true
method: loopback
We have now defined some interfaces, bonds, and bridges.
Template
Let’s now look at an example Jinja2
template on how to use our defintions to
generate our /etc/network/interfaces
file.
NOTE: Pay close attention to some of the
Jinja2
voodoo going on here. There are a few interesting tricks going on.
###
# Beginning of network interfaces
###
{% for int in network_interfaces %}
{% set _int = network_interfaces[int] %}
{% if _int['configure'] %}
{% if _int['comment'] is defined %}
# {{ _int['comment'] }}
{% endif %}
auto {{ int }}
iface {{ int }} inet {{ _int['method'] }}
{% if _int['method']|lower == "static" or _int['method']|lower == "manual" %}
{% if _int['address'] is defined %}
address {{ _int['address'] }}
{% endif %}
{% if _int['netmask'] is defined %}
netmask {{ _int['netmask'] }}
{% endif %}
{% if _int['gateway'] is defined %}
gateway {{ _int['gateway'] }}
{% endif %}
{% endif %}
{% endif %}
{% if _int['parameters'] is defined %}
{% for k, v in _int['parameters'].iteritems() %}
{{ k }} {% if v is not iterable or v is string %}{{ v }}{% elif v is iterable and v is not string %}{{ v|join(" ") }}{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
###
# End of network interfaces
###
###
# Beginning of network bonds
###
{% for bond in network_bonds %}
{% set _bond = network_bonds[bond] %}
{% if _bond['configure'] %}
{% if _bond['comment'] is defined %}
# {{ _bond['comment'] }}
{% endif %}
auto {{ bond }}
iface {{ bond }} inet {{ _bond['method'] }}
{% if _bond['method']|lower == "static" or _bond['method']|lower == "manual" %}
{% if _bond['address'] is defined %}
address {{ _bond['address'] }}
{% endif %}
{% if _bond['netmask'] is defined %}
netmask {{ _bond['netmask'] }}
{% endif %}
{% if _bond['gateway'] is defined %}
gateway {{ _bond['gateway'] }}
{% endif %}
{% endif %}
{% if _bond['parameters'] is defined %}
{% for k, v in _bond['parameters'].iteritems() %}
{{ k }} {% if v is not iterable or v is string %}{{ v }}{% elif v is iterable and v is not string %}{{ v|join(" ") }}{% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% endfor %}
###
# Beginning of network bonds
###
{% for bridge in network_bridges %}
{% set _bridge = network_bridges[bridge] %}
{% if _bridge['configure'] %}
{% if _bridge['comment'] is defined %}
# {{ _bridge['comment'] }}
{% endif %}
auto {{ bridge }}
iface {{ bridge }} inet {{ _bridge['method'] }}
{% if _bridge['method']|lower == "static" or _bridge['method']|lower == "manual" %}
{% if _bridge['address'] is defined %}
address {{ _bridge['address'] }}
{% endif %}
{% if _bridge['netmask'] is defined %}
netmask {{ _bridge['netmask'] }}
{% endif %}
{% if _bridge['gateway'] is defined %}
gateway {{ _bridge['gateway'] }}
{% endif %}
{% endif %}
{% if _bridge['parameters'] is defined %}
{% for k, v in _bridge['parameters'].iteritems() %}
{{ k }} {% if v is not iterable or v is string %}{{ v }}{% elif v is iterable and v is not string %}{{ v|join(" ") }}{% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% endfor %}
###
# End of network bonds
###
Playbook
---
- hosts: node0
tasks:
- name: Generating /etc/network/interfaces
template:
src: interfaces.j2
dest: /etc/network/interfaces
Final Configuration
Now that we have our dictionaries defined and our Jinja2 template defined, we can see what our final configuration would look like below:
###
# Beginning of network interfaces
###
auto enp0s3
iface enp0s3 inet dhcp
pre-up sleep 2
auto enp0s8
iface enp0s8 inet static
address 10.0.0.100
netmask 255.255.255.0
gateway 10.0.0.1
auto enp0s09
iface enp0s09 inet manual
bond_master bond0
auto enp0s10
iface enp0s10 inet manual
bond_master bond0
auto enp0s16
iface enp0s16 inet manual
auto enp0s17
iface enp0s17 inet manual
pre-up ifconfig $IFACE up
post-down ifconfig $IFACE down
auto lo
iface lo inet loopback
###
# End of network interfaces
###
###
# Beginning of network bonds
###
# Bond Group 0
auto bond0
iface bond0 inet static
address 192.168.1.10
netmask 255.255.255.0
bond_mode active-backup
bond_miimon 100
primary enp0s9
slaves enp0s9 enp0s10
###
# Beginning of network bonds
###
# Bridge 0
auto br0
iface br0 inet static
address 192.168.1.11
netmask 255.255.255.0
gateway 192.168.1.1
bridge_stp off
bridge_fd 0
ports enp0s16
###
# End of network bonds
###
So there you have it, we now have a very cleanly defined /etc/network/interfaces
file by using Ansible variables defined as dictionaries.
Enjoy!
Leave a comment