Ansible variable precedence
I have been using Ansible professionally now for 15 months and let me first say that I really enjoy using Ansible over the other automation tools I have used in the past. However, with all tools, it is not without it’s quirks, particularly, Ansible variables. I would like expand on this and give a concrete example of how variable precedence has caused my current team some pain. All examples below can be found in this repo.
We will start with where variables can be set. When we set a variable via the command line for the run of the playbook, the variable is available in the playbook. This is demonstrated below and code availble via example 1.
ansible-playbook example-01.yml -i hosts -e 'variable_one=set-from-commandline'
# example-01.yml
---
- hosts: localhost
connection: local
tasks:
- debug:
var: variable_one
In example 2, we will still set the variable via the command line but also set via the playbook. What we learn here is that the command line declaration of the variable wins.
ansible-playbook example-02.yml -i hosts -e 'variable_two=set-from-commandline'
# example-02.yml
---
- hosts: localhost
connection: local
vars:
variable_two: 'set-via-vars-in-playbook'
tasks:
- debug:
var: variable_two
In example 3, we can see that vars section will be the first (and only) declaration of the variable.
ansible-playbook example-03.yml -i hosts
# example-03.yml
---
- hosts: localhost
connection: local
vars:
variable_three: 'set-via-vars-in-playbook'
tasks:
- debug:
var: variable_three
In this fourth example, we will set the variable via the vars
block and via
the set_fact
module. We will notice that the first encounter of the variable
is set to the vars
block value and when we do the set_fact
we can reset the
value to what we would like. So from this example, we will see that we can set
a variable to a new value during a playbook execution if needed. The code for
example 4.
ansible-playbook example-04.yml -i hosts
# example-04.yml
---
- hosts: localhost
connection: local
vars:
variable_four: 'set-via-vars-in-playbook'
tasks:
- debug:
var: variable_four
- set_fact:
variable_four: 'set-via-set-fact'
- debug:
var: variable_four
Now we will take a look at how and when variables are available from an
include_vars
file. In this example, I show that the variable is not available
until after we have processed the include_vars
command in the playbook. The
code for example 5.
ansible-playbook example-05.yml -i hosts
# example-05.yml
---
- hosts: localhost
connection: local
tasks:
- debug:
var: variable_include_vars_five
- include_vars: 'variable_include_vars_five.yml'
- debug:
var: variable_include_vars_five
# variable_include_vars_five.yml
---
variable_include_vars_five: 'set-via-include-vars-file'
Remember when we encounter the first declaration of a variable in a playbook,
it will be set to that until we do a set_fact
or change it via an
include_vars
. If we revist the vars
block (or set via the command line) and
do an include_vars
, the variable will take the new value from the
include_vars
. The code for example 6.
ansible-playbook example-06.yml -i hosts
# example-06.yml
---
- hosts: localhost
connection: local
vars:
variable_include_vars_six: 'set-via-vars-in-playbook'
tasks:
- debug:
var: variable_include_vars_six
- include_vars: 'variable_include_vars_six.yml'
- debug:
var: variable_include_vars_six
# variable_include_vars_six.yml
---
variable_include_vars_six: 'set-via-include-vars-file'
If we do multiple include_vars
and change the value in each, the variable
will change after each file is included. The code for example 7.
ansible-playbook example-07.yml -i hosts
# example-07.yml
---
- hosts: localhost
connection: local
tasks:
- debug:
var: variable_include_vars_seven
- include_vars: 'first_variable_include_vars_seven.yml'
- debug:
var: variable_include_vars_seven
- include_vars: 'second_variable_include_vars_seven.yml'
- debug:
var: variable_include_vars_seven
# first_variable_include_vars_seven.yml
---
variable_include_vars_seven: 'set-via-first-include-vars-file'
# second_variable_include_vars_seven.yml
---
variable_include_vars_seven: 'set-via-second-include-vars-file'
In example 8, we will return to setting variables from the command line. In
this example, we will set the variable from the command line and from an
include_vars
file. We will notice something interesting in that the variable
is always set to the command line value, even after the include_vars
.
ansible-playbook example-08.yml -i hosts -e "variable_eight=set-via-commandline"
# example-08.yml
---
- hosts: localhost
connection: local
tasks:
- debug:
var: variable_eight
- include_vars: 'variable_include_vars_eight.yml'
- debug:
var: variable_eight
# variable_include_vars_eight.yml
---
variable_eight: 'set-via-include-vars-file'
In example 9, we will again return to setting variables from the command
line. In this example, we will set the variable from the command line and from
the set_fact
module. We will notice something interesting in that the
variable is always set to the command line value, even after the set_fact
.
Now looking at the Ansible documentation for variable precedence, we will
notice (if you scroll down to the 2.x section) that extra vars
always win
precedence (where extra vars
are command line variables).
ansible-playbook example-09.yml -i hosts -e "variable_nine=set-via-commandline"
# example-09.yml
---
- hosts: localhost
connection: local
tasks:
- debug:
var: variable_nine
- set_fact:
variable_nine: 'set-via-set-fact'
- debug:
var: variable_nine
In example 10, I would like to show the example that caused us a lot of
trouble. If you read the section the documentation, you will notice that the
role defaults
takes the highest precedence, right behind extra vars
, in
both 1.x and 2.x versions of Ansible. Our team had not seen this section of the
documentation and had been expecting that the first declaration of a variable
was the value of that variable. So, we were very surprised when some of our
playbooks started failing for what seemed like odd reasons. You will see that
even before the role has been applied, the variables for that role are now in
the global scope and available to any pre_tasks
. We were doing a check to see
if a variable had been defined, and since the role defaults was defining the
variable it was always true, we would do some certain processing. This behavior
differs from what we see above with the include_vars
where the variable is
not available until after the include_vars
has been processed. We expected
the same behavior for the roles
.
ansible-playbook example-10.yml -i hosts
# example-10.yml
---
- hosts: localhost
connection: local
pre_tasks:
- debug:
msg: 'pre-tasks'
- debug:
var: variable_ten
roles:
- role: variable_role
tasks:
- debug:
msg: 'tasks'
- debug:
var: variable_ten
# roles/variable_role/defaults/main.yml
---
variable_ten: 'set-via-role-defaults'
# roles/variable_role/tasks/main.yml
---
- debug:
msg: 'role'
- debug:
var: variable_ten
In conclusion, I highly recommend reading the documentation for the tools that you use. While the Ansible documentation does warn us of the behavior we encounter. I hope that these examples provide living documentation. This was not an exhaustive list, but I wanted to highlight some of the many ways that we do use Ansible and it’s variable declaration and precedence and one scenario where it did not behave as we had originally anticipated.
comments powered by Disqus