Understanding variable precedence

1,829 views
Skip to first unread message

GW

unread,
Apr 20, 2013, 7:29:18 PM4/20/13
to ansible...@googlegroups.com
Hi,

Can somebody please explain to me why the variable precedence
(http://ansible.cc/docs/playbooks2.html#understanding-variable-precedence)
is the way it is?

Especially why `vars` and `vars_files` are in that order and so far
apart? Shouldn't one be just an inline replacement of the other?

To me the following variable precedence would seem more natural:

1. `--extra-vars` in command line
2. `vars` in a playbook
3. `vars_files` in a playbook
4. assigned variables with `register`
5. parameters passed to tasks/handlers list `include` statements (also
parameters to `roles`)
6. gathered facts
7. parameters passed to playlist `include` statements
8. inventory host variables
9. inventory group variables in inheritance order

Note that the above 4. and 6., 5. and 7. in current documentation are
joined together, but for clarity it would be better to separate them
(and probably change their order as I did above).

I also put `vars` in a playbook (2.) before `vars_files`, because it
feels more natural to have sane defaults in external files and override
them locally. Most programs behave similar to this when loading their
configuration. This way way the following use case would be much more
readable (because there would be no need to look in the additional
`vars_files` files to see what is going on or put things in inventory
variables or use parameterized roles):


# webservers.yml - assigns roles to hosts, allow specifying which
repository and branch to use
---
- hosts: app1-production
roles:
- webserver
- app1

- hosts: app1-devel
vars:
branch: devel
roles:
- webserver
- app1


# roles/app1/vars/main.yml - contains default variables for role app1
---
repository: https://.../app1.git
branch: master


Maybe directly setting Jinja2 variables with `{{ set aaa = "foo" }}`
should also be put on the variable priority list (position 0.)? Although
I wouldn't encourage people to overuse this.

Greetings,
gw

Michael DeHaan

unread,
Apr 20, 2013, 10:13:23 PM4/20/13
to ansible...@googlegroups.com
Different folks are going to have different preferences, but there's an error here -- vars and vars_files have essentially the same priority, so I'll fix the docs.

facts and registered variables will override them.






--
You received this message because you are subscribed to the Google Groups "Ansible Project" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ansible-project+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.





--
Michael DeHaan <mic...@ansibleworks.com>
CTO, AnsibleWorks, Inc.
http://www.ansibleworks.com/

Michael DeHaan

unread,
Apr 20, 2013, 10:47:56 PM4/20/13
to ansible...@googlegroups.com
Ok, I just rewrote this section and pushed the changes

Here is the new text that explains the *why*.   As we have thousands of playbooks in the wild I'm not going to be changing precedence order any, so I hope this explains how to do anything you would want to do -- and where to put something if you care for a default.

== start ==

You have already learned about inventory variables, ‘vars’, and ‘vars_files’. In the event the same variable name occurs in more than one place, what happens? There are really three tiers of precedence, and within those tiers, some minor ordering rules that you probably won’t even need to remember. We’ll explain them anyway though.

Variables that are set during the execution of the play have highest priority. This includes registered variables and facts, which are discovered pieces of information about remote hosts.

Descending in priority are variables defined in the playbook. ‘vars_files’ as defined in the playbook are next up, followed by variables as passed to ansible-playbook via –extra-vars (-e), then variables defined in the ‘vars’ section. These should all be taken to be basically the same thing – good places to define constants about what the play does to all hosts in the play.

Finally, inventory variables have the least priority. Variables about hosts override those about groups. If a variable is defined in multiple groups and one group is a child of the other, the child group variable will override the variable set in the parent.

This makes the ‘group_vars/all’ file the best place to define a default value you wish to override in another group, or even in a playbook. For example, your organization might set a default ntp server in group_vars/all and then override it based on a group based on a geographic region. However if you type ‘ntpserver: asdf.example.com’ in a vars section of a playbook, you know from reading the playbook that THAT specific value is definitely the one that is going to be used. You won’t be fooled by some variable from inventory sneaking up on you.

So, in short, if you want something easy to remember: facts beat playbook definitions, and playbook definitions beat inventory variables.


== end ==


Michael DeHaan

unread,
Apr 21, 2013, 3:14:20 AM4/21/13
to ansible...@googlegroups.com
Here is something some folks may also not know about:


GW

unread,
Apr 21, 2013, 7:44:46 AM4/21/13
to ansible...@googlegroups.com

Hi,

I noticed that the new text does not mention parameters to included tasks/handlers list and also not for parameters to included playbooks. Also `vars_prompt` could be added.

Yes, changing the precedence is dangerous, but I really wondering if any playbook in the wild ever uses:

vars:
  foo: "this will get overriden"
vars_files:
  - vars/file_overriding_foo.yml

Maybe in combination with conditional imports it would make a little sense (under one condition `foo` is overriden, otherwise not), but I doubt anyone is doing this. Or is there anyone on this list? Can you explain the use case?

Greetings,
   gw

To unsubscribe from this group and stop receiving emails from it, send an email to ansible-proje...@googlegroups.com.

Michael DeHaan

unread,
Apr 21, 2013, 10:11:53 AM4/21/13
to ansible...@googlegroups.com
Yep, it's useful.

The usage of vars_files conditionally (see the last link I shared) can use a fact to determine a path to a variable file.  

For instance, you could set up defaults in vars, and then have some OS specific variables loaded from something like this:

vars_files:
  -  "/vars/{{ ansible_os_family}}.yml"

As such, you *might* want to set the default in vars.

We're not changing the precedence orders :)


PJ

unread,
Apr 22, 2013, 10:12:17 AM4/22/13
to ansible...@googlegroups.com
How does it work with precedence with hosts/groups specified on command line versus hosts and groups in playbook-of-playbooks and/or playbooks?

Michael DeHaan

unread,
Apr 22, 2013, 10:35:37 AM4/22/13
to ansible...@googlegroups.com
I'm not sure what you mean by hosts/groups specified on the command line, can you elaborate?

Although, host/group variables are always lowest in terms of priority.

Playbooks including one another does not affect this.


On Mon, Apr 22, 2013 at 10:12 AM, PJ <pjoak...@gmail.com> wrote:
How does it work with precedence with hosts/groups specified on command line versus hosts and groups in playbook-of-playbooks and/or playbooks?

PJ

unread,
Apr 22, 2013, 10:52:40 AM4/22/13
to ansible...@googlegroups.com

# ansible-playbook -l host01 main.yml

main.yml: (hosts: defaultA)
  -> include sub_book1.yml (hosts: hostA)
  -> include sub_book2.yml (hosts: all)

It seems that group defaultA will be overridden by command line option, which is fine.
hostA in sub_book1.yml will be skipped as it's not match with commnad line, but required for main.yml to succeed.
sub_book2.yml will be skipped as host01 cannot be found in "all" or not matched with command line (most likely because of cobbler, still have to look at this).


Just a bit confused how it is intended to work.

PJ

unread,
Apr 22, 2013, 10:54:39 AM4/22/13
to ansible...@googlegroups.com
I'm lagging behind a bit (sitting on RHEL machine) so running 1.0

PJ

unread,
Apr 22, 2013, 11:08:39 AM4/22/13
to ansible...@googlegroups.com
correction (sorry, should learn to test properly first ;)):

defaultA will be skipped as host is not in the group.
hostA will be skipped as host doesn't match command-line (required to run playbook).
"all" will successfully single out the host.

Michael DeHaan

unread,
Apr 22, 2013, 1:20:24 PM4/22/13
to ansible...@googlegroups.com
Ok, so seems to be working then?  Good deal!

Let me know if not.

PJ

unread,
Apr 22, 2013, 8:04:15 PM4/22/13
to ansible...@googlegroups.com
Well... there's two issues still as I see it.

It would be really nice to have some sort of explanation about how it is intended to work with hosts/groups in the manual, just as with above excellent description of variable precedence... especially useful for noobs like me. :)

After spending an hour thinking about this with a severe split-brain at 02:00, I actually came to the conclusion (correct me if I'm wrong) that using ansible-playbook with the -l option, i.e. limit hosts/groups, will actually break the functionality of any 'hosts:' containing hostnames (intended as "only run on this host/group"), since it will not match what you specified on the command line.

In my case, hostA is actually a playbook checking out a git repository on a specific host, which will be skipped if I use the limit option.

Perhaps something like a "required_hosts:" or similar would solve it?

Michael DeHaan

unread,
Apr 22, 2013, 8:10:16 PM4/22/13
to ansible...@googlegroups.com
Your conclusion is not correct.

What --limit says is "of the hosts that are selected in the hosts definition, additionally confine them to these additional patterns".

It does not matter if groups or hostnames are used.

Skipping it if not in the group seems exactly like what it should do.


PJ

unread,
Apr 22, 2013, 8:27:16 PM4/22/13
to ansible...@googlegroups.com
I see...
Then how would I do if I wanted to run some playbook(s) against a specific host or hosts, without having to modify the playbook(s) every time?

Michael DeHaan

unread,
Apr 22, 2013, 11:10:49 PM4/22/13
to ansible...@googlegroups.com
Two options:

hosts: all

--limit foo.example.com (or any group or pattern, or multiple patterns colon seperated)

or:

hosts: $hosts

--extra-vars="hosts=<pattern goes here>"


PJ

unread,
Apr 23, 2013, 2:42:05 AM4/23/13
to ansible...@googlegroups.com
Too much trees to see the forest. duh!

Thanks... sorry for hijacking the thread. :)

PJ

unread,
Apr 23, 2013, 2:56:19 AM4/23/13
to ansible...@googlegroups.com
Just as a side note, using limit will not work as it will still skip any playbooks where hosts: has to contain a hostname.

Michael DeHaan

unread,
Apr 23, 2013, 8:23:23 AM4/23/13
to ansible...@googlegroups.com
That's not true, please stop by IRC if you would like to discuss/troubleshoot.
Reply all
Reply to author
Forward
0 new messages