# Variables from not-activated roles get set

**URL:** <https://forum.ansible.com/t/variables-from-not-activated-roles-get-set/45732>\
**Category:** Get Help\
**Tags:** variables\
**Created:** [April 24, 2026, 12:38am UTC](https://forum.ansible.com/t/variables-from-not-activated-roles-get-set/45732 "2026-04-24T00:38:49Z")\
**Posts on this page:** 20\
**Page:** 2

<div class="post-metadata">

**Author:** ![bcoca](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/bcoca/32/9269_2.png) [@bcoca](https://forum.ansible.com/u/bcoca)\
**Post date:** [April 24, 2026, 6:49pm UTC](https://forum.ansible.com/t/variables-from-not-activated-roles-get-set/45732/21 "2026-04-24T18:49:22Z")

</div>

use `include_role` for those cases, its not a bug, you are mixing the keywords in a way that does not work as you want it to.

---

<div class="post-metadata">

**Author:** ![mirabilos](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/mirabilos/32/9721_2.png) [@mirabilos](https://forum.ansible.com/u/mirabilos)\
**Post date:** [April 24, 2026, 6:54pm UTC](https://forum.ansible.com/t/variables-from-not-activated-roles-get-set/45732/22 "2026-04-24T18:54:20Z")

</div>

Doesn’t `include_role` run at the time the task is run, i.e. after the `common` role has already run?

That also only works if the `dhcpd4` role is one of the last ones and nothing else depends on it.

---

<div class="post-metadata">

**Author:** ![bcoca](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/bcoca/32/9269_2.png) [@bcoca](https://forum.ansible.com/u/bcoca)\
**Post date:** [April 24, 2026, 6:59pm UTC](https://forum.ansible.com/t/variables-from-not-activated-roles-get-set/45732/23 "2026-04-24T18:59:04Z")

</div>

I would use for any role you want to run conditionally, aka using `when: `. The import order and execution order are different things. Both `import_role` and `include_role` exist to give you fine grained control and different behavior options. The `roles` keyword is akin to a list of `import_role` entries, with less options all between `pre-tasks` and `tasks`, in return you have simpler and less verbose syntax. If you need the advanced functionality, you cannot stick to using `roles`.

---

<div class="post-metadata">

**Author:** ![mirabilos](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/mirabilos/32/9721_2.png) [@mirabilos](https://forum.ansible.com/u/mirabilos)\
**Post date:** [April 24, 2026, 6:59pm UTC](https://forum.ansible.com/t/variables-from-not-activated-roles-get-set/45732/24 "2026-04-24T18:59:52Z")

</div>

> [@mirabilos](#):
>
> Doesn’t `include_role` run at the time the task is run, i.e. after the `common` role has already run?

Yes. This makes it not work:

```auto
tg@tgb1:/tmp/mwe $ ansible-playbook -e run_extra_role=true testplay.yml  
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
 
PLAY [localhost] ************************************************************************************************
 
TASK [common : variable in common role] *************************************************************************
ok: [localhost] => {
    "msg": "variable foo=False"
}
 
TASK [include_role : extra] *************************************************************************************
included: extra for localhost
 
TASK [extra : hi from role extra] *******************************************************************************
ok: [localhost] => {
    "msg": "hi"
}
 
PLAY RECAP ******************************************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0   

```

Changed MWE for this:

```auto
tg@tgb1:/tmp/mwe $ find . -type f | sort | while IFS= read -r name; do echo === $name; cat $name; done
=== ./roles/common/tasks/main.yml
---
- name: variable in common role
  debug:
    msg: variable foo={{ foo | default(false) }}
=== ./roles/extra/defaults/main.yml
foo: true
=== ./roles/extra/tasks/main.yml
---
- name: hi from role extra
  debug: msg=hi
=== ./testplay.yml
- hosts: localhost
  become: false
  gather_facts: false
  roles:
  - role: common
  #- role: extra
  # when: run_extra_role | default(false) | bool
  tasks:
  - include_role: name=extra
    when: run_extra_role | default(false) | bool
  #- name: variable test
  # debug:
  # msg: variable foo={{ foo | default(false) }}

```

---

<div class="post-metadata">

**Author:** ![mirabilos](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/mirabilos/32/9721_2.png) [@mirabilos](https://forum.ansible.com/u/mirabilos)\
**Post date:** [April 24, 2026, 7:01pm UTC](https://forum.ansible.com/t/variables-from-not-activated-roles-get-set/45732/25 "2026-04-24T19:01:28Z")

</div>

> [@bcoca](#):
>
> The `roles` keyword is akin to a list of `import_role` entries

Not quite. The `roles` keyword changes ordering: the tasks of the role are ran before the tasks of the playbook including it, not at the time it is included, and, more importantly, the variables are available **to roles that run before it**.

This is why `include_role` cannot be used, see changed MWE.

---

<div class="post-metadata">

**Author:** ![bcoca](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/bcoca/32/9269_2.png) [@bcoca](https://forum.ansible.com/u/bcoca)\
**Post date:** [April 24, 2026, 7:04pm UTC](https://forum.ansible.com/t/variables-from-not-activated-roles-get-set/45732/26 "2026-04-24T19:04:48Z")

</div>

No, they are run before `tasks` but AFTER `pre-tasks` and AFTER implicit gathering, anything you import (either via `import_role` or `roles`) will export their variables to the play BEFORE any task runs. That is why if you don’t want variables exported, you can a) use `import_role` with `public: no` to avoid making the play level variables OR b) use `include_role` + `when` to avoid any role resource being loaded. When you `include` a role the variables become available AFTER the include happens, using `public` to control if they are exported or not to the play.

You have all the parts you need, you just need to use them correctly.

---

<div class="post-metadata">

**Author:** ![mirabilos](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/mirabilos/32/9721_2.png) [@mirabilos](https://forum.ansible.com/u/mirabilos)\
**Post date:** [April 24, 2026, 7:05pm UTC](https://forum.ansible.com/t/variables-from-not-activated-roles-get-set/45732/27 "2026-04-24T19:05:59Z")

</div>

> [@bcoca](#):
>
> That is why if you don’t want variables exported

Again: I _do_ want variables exported.

I want that the `common` role, which runs BEFORE the `extra` role, gets a variable set if and exactly if the `extra` role will run afterwards. See the changed MWE from the post above.

---

<div class="post-metadata">

**Author:** ![bcoca](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/bcoca/32/9269_2.png) [@bcoca](https://forum.ansible.com/u/bcoca)\
**Post date:** [April 24, 2026, 7:19pm UTC](https://forum.ansible.com/t/variables-from-not-activated-roles-get-set/45732/28 "2026-04-24T19:19:01Z")

</div>

play

```auto
- hosts: localhost
  gather_facts: false
  tasks:
    - import_role:
        name: first

    - include_role:
        name: second
      when: doit

```

first defines doit = false in defaults, and has debug task, second has debug task

```auto
#>ansible-playbook play.yml               
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [localhost] ***************************************************************************************************************************************

TASK [first : debug] ***********************************************************************************************************************************
ok: [localhost] => {
    "doit": false
}

TASK [include_role : second] ***************************************************************************************************************************
skipping: [localhost]

PLAY RECAP *********************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0   

```

and now to run second, i set doit as extra var:

```auto
#>ansible-playbook play.yml -e '{doit: True}'
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [localhost] ***************************************************************************************************************************************

TASK [first : debug] ***********************************************************************************************************************************
ok: [localhost] => {
    "doit": true
}

TASK [include_role : second] ***************************************************************************************************************************
included: second for localhost

TASK [second : debug] **********************************************************************************************************************************
ok: [localhost] => {
    "msg": "in second"
}

PLAY RECAP *********************************************************************************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0   

```

---

<div class="post-metadata">

**Author:** ![mirabilos](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/mirabilos/32/9721_2.png) [@mirabilos](https://forum.ansible.com/u/mirabilos)\
**Post date:** [April 24, 2026, 7:29pm UTC](https://forum.ansible.com/t/variables-from-not-activated-roles-get-set/45732/29 "2026-04-24T19:29:34Z")

</div>

That’s not what I need.

`second` is run, for this host group, depending on a host variable from the inventory. (For other hosts, it’s directly included, or even as a dependency of a directly-included role.)

If `second` is run, `first` needs to know about it.

And this completely works, _except_ if `second` is conditionally included.

---

<div class="post-metadata">

**Author:** ![kks](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/kks/32/10352_2.png) [@kks](https://forum.ansible.com/u/kks)\
**Post date:** [April 24, 2026, 7:53pm UTC](https://forum.ansible.com/t/variables-from-not-activated-roles-get-set/45732/30 "2026-04-24T19:53:20Z")

</div>

> [@mirabilos](#):
>
> If `second` is run, `first` needs to know about it.

In my universe this breaks casuality :]].  
No idea how to solve this.

---

<div class="post-metadata">

**Author:** ![mirabilos](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/mirabilos/32/9721_2.png) [@mirabilos](https://forum.ansible.com/u/mirabilos)\
**Post date:** [April 24, 2026, 8:01pm UTC](https://forum.ansible.com/t/variables-from-not-activated-roles-get-set/45732/31 "2026-04-24T20:01:50Z")

</div>

> [@kks](#):
>
> > [@mirabilos](#):
> >
> > If `second` is run, `first` needs to know about it.
> 
> In my universe this breaks casuality :]].

It already works, _except_ for conditionally-included roles.

---

<div class="post-metadata">

**Author:** ![bcoca](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/bcoca/32/9269_2.png) [@bcoca](https://forum.ansible.com/u/bcoca)\
**Post date:** [April 24, 2026, 8:39pm UTC](https://forum.ansible.com/t/variables-from-not-activated-roles-get-set/45732/32 "2026-04-24T20:39:33Z")

</div>

it doesn’t, you are conflating a role being imported and its variables being available to prior roles (import time) vs the role being selected for import/run (runtime). You can have both roles keyed on the same variable or expression that makes that decision, but not by it being activated in the 2nd role when you want that role to only be included conditionally.

---

<div class="post-metadata">

**Author:** ![mirabilos](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/mirabilos/32/9721_2.png) [@mirabilos](https://forum.ansible.com/u/mirabilos)\
**Post date:** [April 24, 2026, 8:43pm UTC](https://forum.ansible.com/t/variables-from-not-activated-roles-get-set/45732/33 "2026-04-24T20:43:41Z")

</div>

Oh well, this is quite a design deficiency. But the mapping of which hosts have which roles is… weird, anyway.

I’ll just bite the sour apple and have the backport repo enabled on more hosts than need it and not generalise this to more repos/distros. (Debian backports, when enabled, are not automatically used, but do automatically upgrade a package if newer than stable, so this is safe for this specific case.)

I’ve also walked through the `defaults/main.yml` files of all roles we do include conditionally and put warning comments into them. Other than the KEA DHCPD installation, they were safe.

---

<div class="post-metadata">

**Author:** ![utoddl](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/utoddl/32/249_2.png) [@utoddl](https://forum.ansible.com/u/utoddl)\
**Post date:** [April 26, 2026, 12:44am UTC](https://forum.ansible.com/t/variables-from-not-activated-roles-get-set/45732/34 "2026-04-26T00:44:05Z")

</div>

In your initial post, you stated your problem pretty clearly:

> [@mirabilos](#):
>
> **I want to have an Ansible-Jinja2 expression** with which I can detect whether a specific variable with a dynamic name is set in host/group vars or a role that is included. (Concretely, something like `repo_backports_$distroname: true` to enable the backports repo centrally. Anything that needs backports, playbook task or role, just sets it; even one suffices for presence; none setting it will cause removal.)

Everything after that was going off the rails demonstrating unexpected (by you) behavior of variables. But it is possible to do what you initially wanted, which is this magic Jinja expression.

A problem with your initial attempt that hasn’t been brought up before is the use one boolean to indicate multiple things. Each host (host\_vars), group (group\_vars) or role default can only assert whether _it_ requires repo\_backports, and each should do so with its own boolean. As you’ve demonstrated above, if they all use the same boolean variable, then the last one wins.

You indicated “something like `repo_backports_$distroname: true`”. Let’s assume your `$distroname` can be either one of `debian` or `ubuntu` just to keep this concrete, but really it’ll be whatever `{{ ansible_distribution|lower }}` resolves to.

Here’s a best practices thing that actually improves the variable naming problem. All role variables should start with the role name and an underscore. More generally, since these are a whole suite of values coming from various sources, you could adopt a naming scheme like  
`<source>_repo_backports_<distribution>`, so you’d have

```yaml
# from roles/dhcpd/defaults.yml
dhcpd_repo_backports_debian: false # I'm just guessing
dhcpd_repo_backports_ubuntu: false

# from roles/dhcpd4/defaults.yml
dhcpd4_repo_backports_debian: false
dhcpd4_repo_backports_ubuntu: true # I'm still just guessing

# from group_vars/babble.yaml
babble_repo_backports_ubuntu: true

# from playbook vars:
playvars_repo_backports_debian: '{{ [true, false] | random }}' # not even guessing!

```

Then in your `common` role’s `vars:` section you could have expressions like this:

```yaml
vars:
  v_names: "{{ q('ansible.builtin.varnames', '.+_repo_backports_.+_' ~ (ansible_distribution|lower)) | list }}"
  v_values: "{{ q('ansible.builtin.vars', *v_names) | list }}"
  common_repo_backports: "{{ (v_values | sum) > 0 }}" # true if any are true.

```

Note that last variable doesn’t match the regex  
`'.+_repo_backports_.+_' ~ (ansible_distribution|lower)`  
so it isn’t swept up with the others in that `v_names` query (`q(…)` above).

Note also that this doesn’t solve the problems with your initial attempt to use the `roles:` keyword to conditionally import roles based on facts from the future. It merely addresses your initial ask for a Jinja expression to evaluate whether _something_ has set one of these booleans, while not clobbering other similar settings from other sources.

Here’s one more factoid that I hadn’t appreciated until I started playing with this today: You can load role defaults before any roles are invoked by use of the `vars_files:` playbook keyword.

```yaml
  vars_files:
    - roles/dhcpd4/defaults/main.yml
    - roles/dhcpd/defaults/main.yml

```

Your `common` role may then be able to use some logic on those variables to determine what config to generate. You’re still left with figuring out which roles to include (rather than import, probably) eventually.

Cheers!

---

<div class="post-metadata">

**Author:** ![utoddl](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/utoddl/32/249_2.png) [@utoddl](https://forum.ansible.com/u/utoddl)\
**Post date:** [April 26, 2026, 12:54am UTC](https://forum.ansible.com/t/variables-from-not-activated-roles-get-set/45732/35 "2026-04-26T00:54:57Z")

</div>

> [@utoddl](#):
>
> You can load role defaults before any roles are invoked

As @bcoca pointed out above, role defaults are always loaded before any roles are invoked — if you list your roles under the `roles:` playbook keyword:

```yaml
  roles:
    - common
    - dhcpd
    - dhcpd4

```

But after all those role defaults are loaded, all the roles are invoked.

However, if you load the role defaults for, say roles `dhcpd` and `dhcpd4` with the `vars_files:` playbook keyword, and omit them from your `roles:` list, they won’t be invoked. You could conditionally include them in your `tasks:` section with `include_role:`.

I just wanted to clarify that distinction.

---

<div class="post-metadata">

**Author:** ![mirabilos](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/mirabilos/32/9721_2.png) [@mirabilos](https://forum.ansible.com/u/mirabilos)\
**Post date:** [April 27, 2026, 2:31pm UTC](https://forum.ansible.com/t/variables-from-not-activated-roles-get-set/45732/36 "2026-04-27T14:31:58Z")

</div>

> [@utoddl](#):
>
> A problem with your initial attempt that hasn’t been brought up before is the use one boolean to indicate multiple things. Each host (host\_vars), group (group\_vars) or role default can only assert whether _it_ requires repo\_backports, and each should do so with its own boolean. As you’ve demonstrated above, if they all use the same boolean variable, then the last one wins.

No, that’s not the problem (I only added the `: false` to debug) if you only have `true` and unset as possibilities.

The problem is exactly and precisely that `defaults/main.yml` of roles that are conditionally included and whose condition is false is still imported. Nothing more (at this point, where I observed that even static variables fail), nothing less.

---

<div class="post-metadata">

**Author:** ![mirabilos](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/mirabilos/32/9721_2.png) [@mirabilos](https://forum.ansible.com/u/mirabilos)\
**Post date:** [April 27, 2026, 2:32pm UTC](https://forum.ansible.com/t/variables-from-not-activated-roles-get-set/45732/37 "2026-04-27T14:32:50Z")

</div>

> [@utoddl](#):
>
> However, if you load the role defaults for, say roles `dhcpd` and `dhcpd4` with the `vars_files:` playbook keyword, and omit them from your `roles:` list, they won’t be invoked. You could conditionally include them in your `tasks:` section with `include_role:`.

No, because that is too late for the `common` role, see the modified MWE above.

---

<div class="post-metadata">

**Author:** ![utoddl](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/utoddl/32/249_2.png) [@utoddl](https://forum.ansible.com/u/utoddl)\
**Post date:** [April 27, 2026, 3:51pm UTC](https://forum.ansible.com/t/variables-from-not-activated-roles-get-set/45732/38 "2026-04-27T15:51:44Z")

</div>

Right. I didn’t claim it solved your “read the future” problem, only the jinja expression ask.

However, presumably there’s _some_ logic that determines whether a role eventually gets invoked. With the distinct variables from the role defaults in hand, your `common` role should be able to use that same logic to determine which if any extra repos need to be included.

If that can’t be done by the `common` role, then perhaps something that does should run before `common`.

---

<div class="post-metadata">

**Author:** ![mirabilos](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/mirabilos/32/9721_2.png) [@mirabilos](https://forum.ansible.com/u/mirabilos)\
**Post date:** [April 27, 2026, 5:04pm UTC](https://forum.ansible.com/t/variables-from-not-activated-roles-get-set/45732/39 "2026-04-27T17:04:05Z")

</div>

> [@utoddl](#):
>
> However, presumably there’s _some_ logic that determines whether a role eventually gets invoked.

Can be three different causes: direct role, dependency of another role (install host), or, for the pcengines, dependent on a host variable… and duplication of such logic is bad and WILL break things in the future.

Anyway, I just accepted, for this special case of trixie-backports, that having it enabled on more hosts than necessary is tolerable, and didn’t generalise this.

> [@utoddl](#):
>
> only the jinja expression ask.

I am already successfully using a `community.general.merge_variables` for lists, I thought having a simple bool flag was easier. (Turns out it is not harder, but the other problem of the conditional include not working threw off all my debugging to figure it out.)

---

<div class="post-metadata">

**Author:** ![utoddl](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/utoddl/32/249_2.png) [@utoddl](https://forum.ansible.com/u/utoddl)\
**Post date:** [April 27, 2026, 6:19pm UTC](https://forum.ansible.com/t/variables-from-not-activated-roles-get-set/45732/40 "2026-04-27T18:19:36Z")

</div>

> [@mirabilos](#):
>
> duplication of such logic is bad

Very true. Not suggesting duplicating the logic, but rather the same logic — literally the same. Logical expressions in variable values are evaluated each time the variables are used (lazy evaluation). Very handy for avoiding such duplication. That could be leveraged to good advantage here.

I’m sorry you haven’t found a satisfactory solution. I still think it’s possible with the tools at hand. At least what you have is functional without complicating the ansible config, and that’s worth a lot.

Good luck!

[Previous page](https://forum.ansible.com/t/variables-from-not-activated-roles-get-set/45732.md?page=1)

[Next page](https://forum.ansible.com/t/variables-from-not-activated-roles-get-set/45732.md?page=3)
