How is variable scope supposed to work with roles?
I have the same variable defined in different roles (say install_dir), when running a playbook which includes several roles, the value assumed during the execution of the tasks is always the one from the last defined role (the one at the bottom of the list). I expected it to take its value from the vars/main.yml for the role the task is defined in.
Is this how it is supposed to work, or a bug?
Thanks.
Roles pull in all variables from all vars directories before running any tasks in them.
It is not a bug.
I could see this causing behavior that may be difficult to troubleshoot.
We have multiple people writing roles right now and I could see cases where it might be easy to stomp on each other.
I think it would make sense to error immediately if:
- there are any variables defined with the same name in group_vars/ (all files except for group_vars/all)
- there are any variables defined with the same name roles//vars/
Thoughts?
You can configure variable merging in the config file if you want for hash values.
We clearly document what this does in the documentation – this is an automation around tasks/handlers/vars_files.
Yeah we use hash merging for precedence orders that are well defined such as the order of var_files or group_vars/all.
What I’m speaking about specifically are when two variables have the same name in group_vars/<group_name> where the precedence is not well defined (in that case the last file sorted alphabetically wins).
Another example is when two variables in two different roles have the same name in roles//vars/main.yml.
I believe that should throw an error if precedence isn’t well defined in that case.
We are probably not going to do that.
IIRC, variable assignment in child groups trump the assignment in the parent. Haven’t tested extensively, but I believe you have to make the relationship explicit with ‘[groupname:children]’ sections in the inventory file.
Groups are included based on depth order.
Variables in a sub-group override those of the parent group.
@Pol - Yeah we ran into the same problem which may be more of an issue when you have group generation that is more dynamic (like when group definitions come from the ec2.py inventory scripts).
In any case I think in cases where precedence isn’t defined/documented the right thing to do is throw an exception but it sounds like mdehaan has his mind made up about it.
I need to be extra careful since we have different people developing roles right now to be sure we don’t stomp on each other’s variable namespace.
Yeah I think we should probably adopt that convention.
We also have an interesting challenge in that we are trying to keep our configuration open-source which means overrides for sensitive bits.
group_vars/all ← variables that apply to all roles and sets a default/dummy $secure_dir path
group_vars/<group_name> ← overides $secure_dir which will point to a place on disk that’s not publically acceptable, may be different for different deploy groups.
role/*/vars ← default variable definitions for roles
And then our playbooks look like this:
var_files:
- {{ secure_dir }}/path/to/overrides.yml ← this overrides any of the default variables that are in role/*/vars
No, Ansible as originally designed (and still is) strives to be a minimal system.
This means as little as possible to remember in terms of conventions about things being named or labelled.
If you wish to implement namespacing by putting your variable down in a hash based on the role name, you are welcome and able to do so, which is also by design.
BTW, the no was to “we are not going to force this”.
But really, {{ apache.1234 }} is pretty easy:
from roles/apache/vars/main.yml:
apache:
port: 1234
much better than prefixing.
Grr, I’m posting too late, sorry for spam.
I obviously mean {{ apache.port }}
Ah, yes. That is a tidier way of namespacing
You must understand it takes huge acts balance to make something that pleases everyone.
As such, we are going to be very conservative when changing existing behaviors that are in place for thousands of users.
Variable overriding by group priority, hosts over groups, and the various default mechanisms in Ansible are important to everyone.
If there were theoretically some system that warned when the same variable were used in multiple groups at the same level, which is to say which is the right one and which is the warning? And then, it’s not an error, it’s a warning, so how to you convey that
if you have 10000 hosts and it only clashes for some.
Basically the machinery for this would be obsenely complex.
I’ll say this – submit a clean patch and I’ll think about it. But it better be very very very clean and break nothing else, and still work with variable merging behavior on and off, and still work with group precedence, and not change anything with the precedence order, nor change anything to cause false alarms. Many of these things are features where overriding is useful, you have identified ONE case where overriding may be bad… but might not be. Some folks might actually rely on the variables of two overriding one. So do you make the warning itself configurable? I would hope you don’t, as quickly the config file gets rather bloated quickly.
It’s harder than you think.
So yes, we’re conservative about making changes.
I would much rather just show in best practice type convention that if you want to define some variable “port” it is a good idea to namespace it somehow relative to the application.
–Michael
Nice to have that as a ‘convention’ or ‘best practice’, but I’d not want to enforce it in any way.
Those who ‘know what they are doing’ should still have the freedom to do otherwise.
BTW, the no was to "we are not going to force this".
But really, {{ apache.1234 }} is pretty easy:
from roles/apache/vars/main.yml:
apache:
port: 1234
Which is exactly what I have started to do. Another surprise for me:
website:
port: 8080
host: example.com
path: /api
url: http://${host}:${port}/${path} <=== does not work
url: http://${website.host}:${website.port}/${website.path} <=== works
I really expected the first form to work.
much better than prefixing.
I don't know... Because the tasks, templates and handlers are all local in scope (You can't import a task from another scope without using full path), I really expected vars to be local too. To me it seems more natural.
Another way I'm coping with this, is by adding the variables in my tasks/imports. This was suggested in a different thread on this mailing list months ago. I think we should add something to the documentation about this (an example) as it isn't obvious, especially for a newcomer:
- import: /opt/shared/some_general_install_task
vars:
install_dir: /usr/local/myproduct