Var precedence

There are plenty of ways to set defaults.

You can put your systems into a group, and put the variables into group_vars/.

This has the lowest precedence of anything in Ansible, and you can even have defaults for those in group_vars/.

Yes, things included in your playbook absolutely are used. You may say it’s backwards but for other people it’s quite logical and important, and we’re not changing it :slight_smile: If you read a variable in a playbook, you KNOW it is being set.

Finally, you can just do basic stuff like so:

  • hosts: blarg
    vars_files:

  • defaults.yml

  • blarg.yml

  • hosts: glorp
    vars_files:

  • defaults.yml

  • glorp.yml

Which isn’t using group_vars (IMHO you should use group_vars), but effectively does the same thing a bit more explicitly.

also sprach Michael DeHaan <michael@ansibleworks.com> [2013.05.24.2301 +0200]:

Yes, things included in your playbook absolutely are used. You
may say it's backwards but for other people it's quite logical and
important, and we're not changing it :slight_smile:

Please show me a use-case where the existing behaviour makes sense.
I really don't think there is a way to look at it "forwards".

Roles exist to encourage code reuse. I think they should work on the
"least assumptions" principle. If I add a role to my playbook,
I expect it to just work. Then I can start tweaking parameters
exported by the role.

Look at your NTP example in the docs: the NTP server URI is a good
example of such a parameter.

The author of the NTP role does not want to expect users to have to
define an NTP server, so s/he provides a default.

The user, however, will want to change that default based on groups
and hostnames. Conveniently, a look into roles/ntp/vars/main.yml
suffices to know which parameters exist and what their defaults are.
The user hence chooses to overwrite $ntp_server like so:

  1. group_vars/all sets e.g. $ntp_server to pool.ntp.org
  2. group_vars/de sets $ntp_server to de.pool.ntp.org
  3. host_vars/home-router sets $ntp_server to my DCF77 clock at
     home.

However, ansible doesn't work that way.

Instead, the author needs to define $default_ntp_server in
vars/main.yml and always remember to use

  {{ $ntp_server | default $default_ntp_server }}

in jinja2 templates, or whatever construct is necessary to give
precedence to a variable that may or may not exist, before resorting
to the default. This is extra work, and makes templates and code and
task lists more complex than they need to be.

The user now cannot copy-paste from vars/main.yml anymore, but needs
to remember to strip the "default_" prefix from variables s/he wants
to override.

On the other hand, it is possible to do things like this in
a playbook:

Martin,

I haven’t had time to play with ansible 1.2 yet (but soon will now that it’s released!); but would something like this work:

  1. role vars / set $default_ntp_server to pool.ntp.org
  2. group_vars/all sets e.g. $ntp_server to {{ $default_ntp_server | default pool.ntp.org }}
  3. group_vars/de sets $ntp_server to de.pool.ntp.org
  4. host_vars/home-router sets $ntp_server to my DCF77 clock at
    home.

Basically is uses your example but the global group variables will look for the role’s default_ntp_server and use that before falling back to its own default for $ntp_server

also sprach Brice Burgess <briceburg@gmail.com> [2013.06.11.1715 +0200]:

I haven't had time to play with ansible 1.2 yet (but soon will now that
it's released!); but would something like this work:

  1. role vars / set $default_ntp_server to pool.ntp.org
  2. group_vars/all sets e.g. $ntp_server to {{ $default_ntp_server |
default pool.ntp.org }} <http://pool.ntp.org>
  3. group_vars/de sets $ntp_server to de.pool.ntp.org
  4. host_vars/home-router sets $ntp_server to my DCF77 clock at
     home.

Maybe this will work, maybe not. In my opinion, we should not be
expecting (re-)users of roles to jump through such hoops.

I also think Ansible doesn't do it right, but that is the unintuitive way how you have to deal with variables here.

Greetings,
   gw

also sprach GW <gw.2013@tnode.com> [2013.06.11.2144 +0200]:

I also think Ansible doesn't do it right, but that is the
unintuitive way how you have to deal with variables here.

As Michael said, it's only unintuitive to me. I would really like to
hear someone to whom the current way is intuitive, and be able to
learn.

Works for me, but I use single inventory and use group vars to handle envs.

Brian Coca

also sprach Brian Coca <briancoca@gmail.com> [2013.06.12.0027 +0200]:

Works for me, but I use single inventory and use group vars to handle envs.

You are not using roles, right? I think var precedence is okay in
Ansible, but the way roles are handled is not yet.

Maybe it helps to make the following thoughts:

If you want playbooks / roles / tasks to be parametrized with variables
from the inventory then you use inside them variables that are meant to
be set in the inventory.

Reusing a role in your specific context involves including it in your
playbook as well as:
1) Adding the variables needed for that role in your inventory, in which
case your playbook becomes also reusable, since inventory vars can
always be organised as needed.
or
2) Passing variable overrides at the 'include' step in your playbook, in
which case the playbook may become less reusable, if the passed
overrides are too specific.

also sprach Petros Moisiadis <ernest0x@yahoo.gr> [2013.06.12.1019 +0200]:

If you want playbooks / roles / tasks to be parametrized with
variables from the inventory then you use inside them variables
that are meant to be set in the inventory.

Right, but doesn't this mean that I have to set all variables
exported by all roles in the inventory, unless I can assume that
every role can handle the absence of a variable and provide sensible
defaults.

This requires role authors to maintain a documentation about which
variables are set, which is extra work and could easily get out of
sync. If all variables are initialised to defaults in vars/main.yml,
then this file itself serves as reference for which parameters can
be set.

But also, look at it from another angle:

If variables set within a role always override variables from the
inventory, why would the role author even care setting a variable?
Yes, of course, it's better to parametrise your source code's
tunables just like you might factor out e.g. NUM_PLAYERS to the top
of a C source code file for a game, instead of hard-coding the
number of players in the code. But if that cannot be overridden by
the user of the code, then it's not a variable but a constant.

So while I still think that role variables should have lower
precedence than inventory variables (which should have lower
precedence than variables set in playbooks and include parameters),
if Michael and others disagree, then maybe vars/main.yml should be
renamed to constants/main.yml?

As I posted on May 24th, “group_vars” is a great place to stick defaults, and as of 1.2, they can also live in your playbook directory.

Variables in roles/foo/main.yml do get loaded. It’s true, they will be used.

However, there’s nothing to say you can’t set parameters to the role in your playbook, like so, too… and is a great way to pass required parameters:

roles:

We’re not going to be changing the order of inventory precedence.

also sprach Michael DeHaan <michael@ansibleworks.com> [2013.06.12.1301 +0200]:

As I posted on May 24th, "group_vars" is a great place to stick
defaults, and as of 1.2, they can also live in your playbook
directory.

But neither of those are under control of the author of a role, and
since roles are supposed to encourage reuse of work, they need to be
self-contained.

However, there's nothing to say you can't set parameters to the role in
your playbook, like so, too... and is a great way to pass required
parameters:

roles:
    - { role: foo, x: 42, y: asdf.example.com }

Yes, I am aware of this, although I wonder about the difference in
syntax between this and the way includes get parametrised.

Using this approach, it is possible to override role parameters
per-playbook, and hence for the group(s) to which the playbook
applies.

However, if the playbook applies to two groups and you want to
specify different parameters for each group, or you want to be able
to use host-specific parameters, you have to resort to something
like

  - { role: foo, x: 42, ntp_server: $my_url }

and define $my_url in group_vars/host_vars, rather than just
defining $ntp_server there. That's an extra level of indirection and
hence an extra source of error and confusion…

We're not going to be changing the order of inventory precedence.

I am not asking you to do that, especially not what is documented
already. However, roles are a new concept, and they aren't even
included in the docs on variable precedence yet. Therefore, I don't
think we are looking to change existing behaviour, but rather
discussing a new feature. And I think that the way this feature
currently interacts with variable precedence is non-intuitive and
suboptimal.

Am I not succeeding in describing why, and what could be done
instead?

roles can have ‘requried’ values and still be reusable, I don’t see the need of having all needed values populated in the role when it makes more sense to pass them as input.

Yep, if the parameter is required, don’t have a default :slight_smile:

also sprach Brian Coca <briancoca@gmail.com> [2013.06.12.1754 +0200]:

roles can have 'requried' values and still be reusable, I don't
see the need of having all needed values populated in the role
when it makes more sense to pass them as input.

Agreed, but then what is vars/main.yml to be used for anyway? Am
I right in assuming that it's really constants/main.yml?

It’s for defining any variables you want the role to pull in to vars_files.

There is no slash “constants/”, it’s called “vars/” and you can put whatever you like in there.

I'm late to this thread and new-ish to roles, but let me articulate
what I would find least surprising:

(1) role main.yml variables provide defaults

(2) parameters passed to a role override the variables in main.yml

(3) to the extent that I want the role variable to be dynamic by
host/group, I have a way to pass a variable as a parameter value to
the role, e.g. something like:

   { role: foo, x: 42, ntp_server: $my_url }

(I'm not actually sure if Ansible works this way or if #3 actually works or not)

In other words, I want to be able to reason about roles like a
subroutine call. "Execute yourself with these parameters". Global
variables are bad.

I'm actually not entirely sure how ansible scopes variables. What if
two roles use the same parameter name? What if a role uses a
parameter and a host or group var of the same name is used elsewhere?
If everything is globally scoped, then re-usability is harder because
anything might stomp on the variable name of something else.

How do I write ordinary (not Ansible) programs with external config?
I load external config into variables (or a single data structure,
whatever). I then pass those to functions as needed.

Ansible is pretty much a procedural language and it should encourage
good variable scoping practices, just as we would for "real" programs.

David

also sprach Michael DeHaan <michael@ansibleworks.com> [2013.06.13.1309 +0200]:

It's for defining any variables you want the role to pull in to
vars_files.

There's a bit of light at the end of the tunnel. Are you suggesting
that just like roles can be used to factor out tasks and handlers
from playbooks, vars/main.yml in the role will essentially be
appended to the vars_files list of the playbook?

How does this interact with an existing vars_files list in the
playbook? Are the role var_files prepended or appended to the
existing list?

What happens when multiple roles are used?

Thanks,

also sprach David Golden <xdg@xdg.me> [2013.06.13.1443 +0200]:

(1) role main.yml variables provide defaults
(2) parameters passed to a role override the variables in main.yml
(3) to the extent that I want the role variable to be dynamic by
host/group, I have a way to pass a variable as a parameter value to
the role, e.g. something like:

   { role: foo, x: 42, ntp_server: $my_url }

I think this is precisely how things currently work.

In other words, I want to be able to reason about roles like
a subroutine call. "Execute yourself with these parameters".
Global variables are bad.

This is a good point! However, it would require proper scoping, but…

I'm actually not entirely sure how ansible scopes variables.

It does not. If, in the example above, *any* role defines $my_url,
it will override the value if that was set earlier by a group_vars
or host_vars file or statement.

What if two roles use the same parameter name?

In that case, one will overwrite the other. I assume that the one
that's loaded later will overwrite the former.

Maybe the real solution is to scope all variables within Ansible.

  - Role variables should be available only within a role;
  - Playbook variables should only be available to that playbook.
    I wouldn't even make them available to included playbooks then.
  - Group and Host variables are available all the time, but maybe
    they can be scoped to group_vars:: and host_vars:: respectively.
  - Ansible facts should probably also be scoped, although they
    already are in some ways through the $ansible_ convention. There
    ought to be a transitional period with DeprecationWarnings,
    IMHO.

Ansible intentionally lacks vars_scoping.

It’s all part of the simplicity of things to not have to have conversations about scoping.

If you want something named foo_port, don’t name the variable port, etc.

I recognize that this is different from what some people want, but at the same time being exactly what some people want.

–Michael