Scope gets polluted with variables from included roles even though private_role_vars = yes

122 views
Skip to first unread message

Jose Luis Fernández Pérez

unread,
Jul 13, 2017, 7:00:08 AM7/13/17
to Ansible Project
Hi!

Whenever I include a role using the meta dependencies tag I ended up with the scope of the included role in the current role, even though I set private_role_vars = yes

As an example, consider a project like this
├── ansible.cfg
├── roles
│   ├── some_other_role
│   │   └── vars
│   │       └── main.yml
│   └── some_role
│       ├── defaults
│       │   └── main.yml
│       ├── meta
│       │   └── main.yml
│       └── tasks
│           └── main.yml
└── test.yml


the playbook test.yml simply includes 'some_role'

- hosts: all
  tasks:
    - include_role:
        name: some_role

 
In some_role meta definition I include a dependency (some_role/meta/main.yml)

---
dependencies:
  - { role: some_other_role }


And also set a variable a default value (some_role/defaults/main.yml)

some_variable: SOME_VARIABLE

The main task for this role is simply showing the value of the var (some_role/tasks/main.yml)

---
- debug: var=some_variable


some_other_role simply defines a variable that happens to have the same name as a variable some_role (some_other_role/vars/main.yml)

some_variable: SOME_VARIABLE_WITH_VALUE_FROM_OUTSIDE

I have the private_role_vars = yes in my ansible.cfg

When I run the playbook...
ansible-playbook test.yml -c local

PLAY [all] ***********************************************************************************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************************************************************************
ok: [127.0.0.1]

TASK [some_role : debug] *********************************************************************************************************************************************************
ok: [127.0.0.1] => {
    "changed": false,
    "some_variable": "SOME_VARIABLE_WITH_VALUE_FROM_OUTSIDE"
}

PLAY RECAP ***********************************************************************************************************************************************************************
127.0.0.1                  : ok=2    changed=0    unreachable=0    failed=0   


BOOM!

Seems that even though I set the private_role_vars = yes, some_role scope gets poluted with the scope of some_other_role. If I change the definition to the vars instead of the defaults of the some_other_role it works but not because I dont get the scope, but because vars have precedence over a previously defined var.

Is this a bug? is this the desired behavior? How can overcome this?

Thanks in advance, Jose Fernandez

Brian Coca

unread,
Jul 13, 2017, 9:24:46 AM7/13/17
to Ansible Project
The private setting does not affect dependencies as those are
specifically 'included' by the role. It is only mean to keep role vars
from polluting the rest of the play.


----------
Brian Coca

Jose Luis Fernández Pérez

unread,
Jul 13, 2017, 9:45:49 AM7/13/17
to Ansible Project
Oh I see

I have changed the code and if I include the role by means of include_role in the main tasks I get the expected result.

---
- include_role:
    name: some_other_role

- debug: var=some_variable


Why specifying role dependencies in the meta, and explicitly including them has different results? Shouldnt they be the same?

I think this is a bug.

Jose Luis Fernández Pérez

unread,
Jul 13, 2017, 9:47:57 AM7/13/17
to Ansible Project
Funny thing is that if I refactor to explicitly include instead of meta dependencies I hit this bug, https://github.com/ansible/ansible/issues/23609.

Brian Coca

unread,
Jul 13, 2017, 9:54:06 AM7/13/17
to Ansible Project
Actually, include_role is currently bugged in that it does NOT expose
the vars. So I would not rely on that behaviour.

----------
Brian Coca

Jose Luis Fernández Pérez

unread,
Jul 13, 2017, 10:15:36 AM7/13/17
to Ansible Project
But still, I think that a proper mechanism for role scoping should be implemented, if you try to reuse code odds are that you end up having several roles with several variables declared inside each job. Things like 'path' or 'port' are expected to be declared inside those roles, either by vars statements while including or inside the role.

Imagine the following scenario

vars
(vars/main.yml)
porrt:3000


metafile
(meta/main.yml):
dependencies
:
 
- { role:nginx_server }

task
(tasks/main.yml):
-include_role:
    unicorn_server
 args
:
   listen
: {{ port }}
 

In the dependency you don't specify a role so an internal port variable is used (lets say port 80)

The second one has parameter that you set to port, but given that you misspelled it in the vars file you end up having an 80 here. This should be an 'undefined variable' because you should not bother with name clashing.

Jose Luis Fernández Pérez

unread,
Jul 13, 2017, 1:11:02 PM7/13/17
to Ansible Project
There is also a bigger issue in here. Variables are lazy evaluated (https://github.com/ansible/ansible/issues/10374). 

So if for example you include a role and use a variable evaluation in its args, you have absolutely no way if ensuring what is going to be the value when that arg is used inside the role.

roles/some_role/tasks/main.yml:

---
- include_role:
    name: some_other_role
  args:
    my_variable: "{{ some_variable }}"

roles/some_role/vars/main.yml:
some_variable: SOME_VARIABLE

roles/some_other_role/tasks/main.yml:

- debug: var=my_variable

roles/some_other_role/vars/main.yml:
some_variable: SOME_VARIABLE_WITH_VALUE_FROM_OUTSIDE



The result:

PLAY [all] ***********************************************************************************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************************************************************************
ok: [127.0.0.1]

TASK [some_role : include_role] **************************************************************************************************************************************************

TASK [some_other_role : debug] ***************************************************************************************************************************************************
ok: [127.0.0.1] => {
    "changed": false,
    "my_variable": "SOME_VARIABLE_WITH_VALUE_FROM_OUTSIDE"
}

PLAY RECAP ***********************************************************************************************************************************************************************
127.0.0.1                  : ok=2    changed=0    unreachable=0    failed=0   



So my_variable is lazy evaluated inside the role, and given that there is another variable defined with the same name, the value gets evaluated inside.

Conclusion, If you use a third party role (ansible galaxy?) and pass a variable as an argument, and that role happen to use a variable which clashes with one used in the args, the value is going to be whatever value it has inside the role.

That makes modules completely unusable, code cannot be reused in Ansible?.

Daniel C. Schmidt

unread,
Jul 13, 2017, 2:58:07 PM7/13/17
to Ansible Project
That makes modules completely unusable, code cannot be reused in Ansible?

It's not ideal, but a simple workaround is simply to prefix your role variables with the name of the role. For example, using the names "nginx_port" and "unicorn_port" instead of just "port". It's actually worked fine in practice; the trick is that Ansible's flexibility tempts you to buck the convention.

Daniel C. Schmidt

unread,
Jul 13, 2017, 3:32:42 PM7/13/17
to Ansible Project
Oh—just re-read the example. If that's actually the case then it would be a bug, since task vars are supposed to have higher precedence than role vars. The example doesn't work as-written, however, because the task should be called with "vars:" (not "args:"). Perhaps that's the source of the problem?
Reply all
Reply to author
Forward
0 new messages