Variables¶
Using Ansible we can also use variables to use the same playbooks, plays, tasks, etc. We can also create variables by registering the results from previous tasks. Variables can also be used in Conditionals and Loops.
Rules of Variables¶
Variables must:
- start with a letter
- not include a
-
, a space, or.
- not be a number
Examples of invalid variables are 123
, variable-name
, variable name
, and variable.name
.
Valid ones can be variable
, variable_name
, and variable1
You can also use dictionaries which are supported to map keys to values.
variable:
key1: value
key2: value
The previous variable can be referenced by either variable['key1']
or by variable.key1
however you cannot define them in that way, this only works one direction.
Using “dot” notation the following are reserved as they are python methods:
add
, append
, as_integer_ratio
, bit_length
, capitalize
, center
, clear
, conjugate
, copy
, count
, decode
, denominator
, difference
, difference_update
, discard
, encode
, endswith
, expandtabs
, extend
, find
, format
, fromhex
, fromkeys
, get
, has_key
, hex
, imag
, index
, insert
, intersection
, intersection_update
, isalnum
, isalpha
, isdecimal
, isdigit
, isdisjoint
, is_integer
, islower
, isnumeric
, isspace
, issubset
, issuperset
, istitle
, isupper
, items
, iteritems
, iterkeys
, itervalues
, join
, keys
, ljust
, lower
, lstrip
, numerator
, partition
, pop
, popitem
, real
, remove
, replace
, reverse
, rfind
, rindex
, rjust
, rpartition
, rsplit
, rstrip
, setdefault
, sort
, split
, splitlines
, startswith
, strip
, swapcase
, symmetric_difference
, symmetric_difference_update
, title
, translate
, union
, update
, upper
, values
, viewitems
, viewkeys
, viewvalues
, zfill
Inventory Variables¶
Host vars¶
To specify variables in your inventory files for a specific host, you would use the following key=value
format.
host1 key1=value1 key2=value2
This will provide the host with key1
and key2
, which will be used in the playbooks and ad-hoc commands referencing this host.
Group vars¶
To specify variables in your inventory files you would put the variable on the same line in key=value
format.
To specify group variables in an inventory file:
[mygroup]
host1
host2
[mygroup:vars]
variable1=value1
variable2=value2
variable3=value3
This would then provide each of these 3 variables for all hosts in mygroup
.
Playbook Variables¶
In playbooks we can define variables in plays by the following.
- hosts: all
vars:
variable1: value1
Included files and roles¶
We’ve already covered this previously. To specify a variable to an include:
tasks:
- include: tasks.yml variable1=value
You can also specify variables this way as well.
tasks:
- include: tasks.yml
vars:
variable1: value
To use the value in the tasks.yml file we will reference the var as {{ variable1 }}
.
Registered Variables¶
An extremely useful feature of Ansible is the ability to register the output of a task into a variable so that it can be referenced later. To view possible output of a task that would be in the registered variable, you can look at the output of -v
. What is included in the results
value is what would be contained in the registered variable.
For example:
- hosts: all
tasks:
- stat: path=/tmp
register: tmp_folder_data
- debug: msg={{ tmp_folder_data }}
This sniplet would look for /tmp
on the remote host, and get the information of that folder as per the stat
module, and then provide us with all the information of that folder by the debug module and printing it to output.
Accessing Variable Data¶
Sometimes our variables may have more data to them than just a single value. For example the previous example of using stat
module. It returned a bunch of information to us.
{
"tmp_data": {
"changed": false,
"stat": {
"atime": 1481748353.0,
"ctime": 1492640380.9926686,
"dev": 1,
"executable": true,
"exists": true,
"gid": 0,
"gr_name": "root",
"inode": 281474977014021,
"isblk": false,
"ischr": false,
"isdir": true,
"isfifo": false,
"isgid": false,
"islnk": false,
"isreg": false,
"issock": false,
"isuid": false,
"mode": "1777",
"mtime": 1492640380.9926686,
"nlink": 2,
"path": "/tmp",
"pw_name": "root",
"readable": true,
"rgrp": true,
"roth": true,
"rusr": true,
"size": 0,
"uid": 0,
"wgrp": true,
"woth": true,
"writeable": true,
"wusr": true,
"xgrp": true,
"xoth": true,
"xusr": true
}
}
}
To access a specific item for example exists
, in this object we can use two types of notation.
{{ tmp_data["stat"]["exists"] }}
{{ tmp_data.stat.exists }}
Both will return true
as the result.
To access the first element of an array we would use data[0]
.
Variable Precedence¶
Because of how many possible places we can put a variable, we will need to understand variable precedence. Top of the list is the weakest, bottom is the strongest.
- role defaults
- inventory INI or script group vars
- inventory group_vars/all
- playbook group_vars/all
- inventory group_vars/*
- playbook group_vars/*
- inventory INI or script host vars
- inventory host_vars/*
- playbook host_vars/*
- host facts
- play vars
- play vars_prompt
- play vars_files
- role vars (defined in role/vars/main.yml)
- block vars (only for tasks in block)
- task vars (only for the task)
- role (and include_role) params
- include params
- include_vars
- set_facts / registered vars
- extra vars (always win precedence)
Extra vars are what we specify on the command line as we talked about earlier with -e
or --extra-vars
.
There are also 3 types of variable scopes, Global, Play, and Host.
- Global is set via command line, Environment Variable, or using the config file.
- Play is set in the play, using vars entries, include_vars, role defaults, and vars.
- Host is set in the inventory, facts, or registered output from tasks.
Ansible Facts¶
Ansible by default will gather facts about the remote host. You can see all the facts gathered from a remote host by using the command:
ansible hostname -m setup
Of course replace hostname with the name of the host, the host will need to be in your inventory file. Once it runs it will return a JSON object with all the information Ansible knows of the host. It can return interface information, disk information, kernel information, OS information, and much more.
To turn off Ansible Facts on a host you would use the following:
- hosts: all
gather_facts: no
Setting gather_facts
to no
will disable the gathering of facts from the remote host.
Ansible also has Local Facts, which can be provided by custom facts modules. For more information please visit: http://docs.ansible.com/ansible/playbooks_variables.html#local-facts-facts-d
Using Variables in Jinja Templates¶
Ansible uses the Jinja template system to create files and handle variables within playbooks. An example of a a template task and the jinja template would be:
vars:
server_port: 9060
server_ip: 192.168.1.20
tasks:
- template: src=server.j2 dest=/etc/app/server.conf mode=0644
port={{ server_port }}
serverIP={{ server_ip }}
So the end result of the file located at /etc/app/server.conf would be:
port=9060
serverIP=192.168.1.20
Using Variables in Tasks¶
Ansible allows us to use Jinja within playbooks as well. Making reusing tasks much easier as well as customizing tasks for a different operating system, or any configuration that may differ from server to server.
For example we can change the variables based on the os distribution. Then use those to define a package name. This allows you to support cases in which Apache on CentOS is httpd
but on Ubuntu is apache
. We can load the variables specific to that OS and use those.
Ubuntu.yml
package_name: apache
CentOS.yml
package_name: httpd
Playbook Excerpt
- name: Include OS Specific Variables
include_vars: "{{ ansible_distribution }}.yml"
- name: Install Package
package: name={{ package_name }} state=present
If you didn’t notice, when we did the include_vars
the value had ""
(double quotes) around it. Any value that starts with a variable will need quotes around it. This is a YAML syntax usage correction. Failure to do this will cause Ansible to hit an error on execution.
Jinja Filters¶
There are many filters that can be extremely useful in modifying playbooks, values, and even dynamically handling data for variables. We can force things to be uppercase, lowercase, combine items, and much more. Jinja has a list of built-in filters documented here: http://jinja.pocoo.org/docs/2.9/templates/#builtin-filters
We will go over a few of these filters that have been common throughout our experience, and provide you some examples.
Math Operators¶
Jinja will also let us perform mathmatical actions on values. For example
- hosts: all
vars:
some_number: 2
tasks:
- debug: msg={{ some_number + 1 }}
The result of this would give us a message with the number 3
.
- +
- Adds objects together, it’s not recommended to use this for strings, for strings use
~
which will concatenate strings. - -
- Will subtract the second number from the first
- /
- Divides two numbers and will return a float
- //
- Divides two numbers and will return a truncated integer, this does not round, it just drops everything after the .
- %
- Provides the remainder of an integer division
- *
- Multiplies the left operand with the right.
{{ 2 * 4 }}
will return8
.{{ '#' * 40 }}
would return 40#
symbols - **
- Raises the left operated to the power of the right.
Comparisons¶
- ==
- Compares two objects for equality.
- !=
- Compares two objects for inequality.
- >
- true if the left hand side is greater than the right hand side.
- >=
- true if the left hand side is greater or equal to the right hand side.
- <
- true if the left hand side is lower than the right hand side.
- <=
- true if the left hand side is lower or equal to the right hand side.
These are extremely common in when
portions of tasks.
Logic¶
- and
- Return true if the left and the right operand are true.
- or
- Return true if the left or the right operand are true.
- not
- negate a statement (see below).
- (expr)
- group an expression.
These can be useful when handing when
statements or if
statements in your jinja templates.
Other Operators¶
The following operators are very useful but don’t fit into any of the other two categories:
- in
- Perform a sequence / mapping containment test. Returns true if the left operand is contained in the right. {{ 1 in [1, 2, 3] }} would, for example, return true.
- is
- Performs a test.
- ~
- Converts all operands into strings and concatenates them.
{{ "Hello " ~ name ~ "!" }}
would return (assuming name is set to ‘John’)Hello John!
. - ()
- Call a callable: {{ post.render() }}. Inside of the parentheses you can use positional arguments and keyword arguments like in Python:
{{ post.render(user, full=true) }}
. - . / []
- Get an attribute of an object.
ipaddr¶
For instance lets validate the ip address we pass as a variable.
To use ipaddr
we will need to install netaddr
.
pip install netaddr
ansible-playbook playbook.yml --extra-vars "controller_ip=10.23.222.10"
- hosts: any
roles:
- role: avinetworks.avicontroller
con_controller_ip: {{ controller_ip | ipaddr }}
If the supplied controller_ip
isn’t a valid IP, the value of con_controller_ip
will be “False” which would result in a failure of the execution.
default¶
The default()
filter allows us to provide a default to a variable if it’s not defined. Preventing an error if a value isn’t provided. For example:
{{ my_string|default('You didn't provide a string')}}
Would provide the result You didn't provide a string
if my_string
wasn’t defined.
In Ansible Use
tasks:
- debug: msg={{ my_string | default('You didn't provide a string')}}
When executing this if you don’t provide a variable named my_string
then the debug
module will return the message You didn't provide a string
. This can be useful when not requiring variables.
Python Methods¶
In practice we found that Python methods can also be useful when parsing text that is put into a variable, replace text, and many more python methods.
split¶
For instance, we want to split up a comma seperated list that was provided as a string variable to Ansible.
To do this we can do the following using the split
python method.
tasks:
- name: build server list
set_fact:
servers: "{{ servers|default([]) + [{'ip': {'addr': item, 'type': 'V4'}}] }}"
with_items: "{{ pool_servers.split(',') }}"
Using that task we are able to take in a comma seperated list of server ip addresses as pool_servers
and iterate through those and append those to the servers variable.