A simple approach to locally scoped facts; asking for feedback

For the last few weeks I’ve been using Ansible quite extensively, and I realized for the first time that facts set in roles are not locally scoped. It so happens I like to name my variables as generically as the context permits, so instead of opting for prefixing all my role variables with company and role name, I tried something else.

I’ve documented a proof of concept on GitHub that aims to provide roles with a locally scoped fact named local, allowing one to create simply named facts without worry of them being overwritten by including roles.

I’ve been looking around for similar approaches, but did not find them, which leaves me to wonder whether I’m missing something about Ansible that makes the proposal unnecessary.

My question is basically whether the proposal is viable as a solution, or, alternatively, there are important reasons not to go down this path.

Edit: I realized that it might be very desirable to allow local scope without changing the way the role is consumed, so I added a set of roles that demonstrate this.

This doesn’t help directly, and also has the disadvantage that the caller needs to enable it (by setting the config, or using the new public parameter), but it might still be interesting - in case you haven’t already seen it: Ansible Configuration Settings — Ansible Community Documentation

There’s also the Roles 2.0 proposal by @bcoca: Roles 2.0 · Issue #191 · ansible/proposals · GitHub

Thanks. I did notice the DEFAULT_PRIVATE_ROLE_VARS setting, but my test pointed out that it does nothing to intermediate values set using set_facts.

Is the proposal by bcoca stil alive? Either way, it does not seem to touch on facts set during execution either.

The problem I’m trying to solve concerns the scope of facts set using set_facts / register, specifically.

I turned it into a collection so that it’s easier for people to try out; it’s available at the GitHub repo link in the original post.

It enables usage like the following:

- name: Set some locally scoped facts
  pieterjanv.localscope.set:
    updates:
      some_key: some value

- name: Setup call to nested role
  pieterjanv.localscope.set:
    updates:
      call_args:
        name: some_one.some_collection.some_role
        tasks_from: main

# Now call the role through the `call` role
- name: Include a nested role
  ansible.builtin.include_role:
    name: pieterjanv.localscope.call

- name: Use the locally scoped fact knowing it has not been overwritten
  debug:
    var: local.some_key

You have 2 main scopes in Ansible, one associated to the playbook objects, that works as you expect (mostly) and one associated to the host, set_fact sets variables in ‘host scope’ and is independent of the playbook objects. If you want to set variables to stay local to the role use vars:.

The role 2.0 proposal is a wish item, not currently planned, I also have another to revamp variables themselves redesign variable interface · Issue #127 · ansible/proposals · GitHub in the same state, which might be more appropriate to this discussion.

vars are visible to included modules

vars: take you a long way, but given they are visible to all included roles, they are not local in the sense that variables defined in a function in typical programming languages are. This is a hindrance to naming things generically, as there seems to be no way to distinguish between vars passed directly to your role, and vars by the same name that happened to be set higher up the inclusion chain, and the more generic your name, the likelier someone used it in a conflicting context.

undef() can help

I did just find out there is a cumbersome and error-prone way to work around this particular issue - the undef() function - that the caller can use to unset any settable vars if he wishes to not pass them. I actually found a use for it improve the api of my collection.

other vars limitations

I think I found a somewhat clean api to accomplish setting local facts that behave as I expect, but I don’t see a way to do this for vars:

  • I see no way to automatically undef() all vars; I can’t iterate over them.
  • Vars are evaluated lazily, so by the time they are evaluated, vars defined in terms of other vars may have values assigned to them in a conflicting context.

Only facts can be made local at the moment

I think the proposal you linked covers these issues, as you mention locally scoped vars and eager evaluation. I hope at least those parts can be integrated!

Until then, I see no other option for local definitions besides setting facts.