Understanding variable precedence

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

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.

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 ==

Here is something some folks may also not know about:

http://ansible.cc/docs/playbooks2.html#conditional-imports

–Michael

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

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 :slight_smile:

How does it work with precedence with hosts/groups specified on command line versus hosts and groups in playbook-of-playbooks and/or playbooks?

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.

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.

I’m lagging behind a bit (sitting on RHEL machine) so running 1.0

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.

Ok, so seems to be working then? Good deal!

Let me know if not.

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. :slight_smile:

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?

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.

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?

Two options:

hosts: all

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

or:

hosts: $hosts

–extra-vars=“hosts=”

Too much trees to see the forest. duh!

Thanks… sorry for hijacking the thread. :slight_smile:

Just as a side note, using limit will not work as it will still skip any playbooks where hosts: has to contain a hostname.

That’s not true, please stop by IRC if you would like to discuss/troubleshoot.