# Explicitely use role defaults

**URL:** <https://forum.ansible.com/t/explicitely-use-role-defaults/44958>\
**Category:** Get Help\
**Tags:** variables, role, playbook, ansible-core\
**Created:** [December 5, 2025, 8:49am UTC](https://forum.ansible.com/t/explicitely-use-role-defaults/44958 "2025-12-05T08:49:37Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![VannTen](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/vannten/32/9911_2.png) [@VannTen](https://forum.ansible.com/u/VannTen)\
**Post date:** [December 5, 2025, 8:49am UTC](https://forum.ansible.com/t/explicitely-use-role-defaults/44958/1 "2025-12-05T08:49:37Z")

</div>

Hello,

I’m working on a playbook where I’d like to fetch some files from my hosts to the control node.

I want the destination (on the control node) to be controlled by a variable `local_release_dir` in the localhost context (aka, set in the inventory for localhost, or using role defaults).  
However, since the tasks run in the context of the host, if I just use `{{ local_release_dir }}`, I’ll have (roughly) `{{ hostvars[current_host]['local_release_dir'] | default(local_release_dir) # aka the role defaults }}`, which does not work, because `hostvars[current_host]['local_release_dir'] != hostvars['localhost']['local_release_dir']` is a situation I want to handle correctly.

The workaround I have at the moment is to add the default explicity, but it’s not great, because there is now 2 sources of truth who can diverge.

```auto
  - name: Download_file | Fetch file to localhost
    fetch:
      flat: true
      src: "{{ local_release_dir }}/{{ item.value.dest }}"
      # TODO: delete d() filter
      dest: "{{ (hostvars['localhost']['local_release_dir'] | d('/tmp/releases/')) + '/' }}" # the default ilter here should be replaced with a way to explicitly reference the role defaults. 
    loop: "{{ some jinja expression listing the needed files, per host }} 

```

Possibles workarounds:

- use `include_vars` with `name` to make the defaults variables available under a named variable ; I’m not a fan since I need to add a task.

Full context for anyone interested : [Refactor download (file) by VannTen · Pull Request #12299 · kubernetes-sigs/kubespray · GitHub](https://github.com/kubernetes-sigs/kubespray/pull/12299/files#diff-29c51562afb7b57067bcc858a250433e71efd8fc5dc6b5b698fd51e3a9bddc67R74-R95)

Would appreciate any ideas, including doing the same thing (getting files on localhost) in a different way / with a different modules.

---

<div class="post-metadata">

**Author:** ![vbotka](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/vbotka/32/359_2.png) [@vbotka](https://forum.ansible.com/u/vbotka)\
**Post date:** [December 6, 2025, 6:12am UTC](https://forum.ansible.com/t/explicitely-use-role-defaults/44958/2 "2025-12-06T06:12:42Z")

</div>

Given the role

```yaml
shell> tree roles/
roles/
└── role_A
    ├── defaults
    │ └── main.yml
    └── tasks
        └── main.yml

```

```yaml
shell> cat roles/role_A/defaults/main.yml 
local_release_dir: role_default_dir

```

```yaml
shell> cat roles/role_A/tasks/main.yml 
- debug:
    var: local_release_dir

```

and the inventory

```ini
shell> cat hosts
localhost local_release_dir=localhost_dir

[test]
foo
bar
baz

```

The play

```yaml
shell> cat pb.yml 
- hosts: test,localhost

  pre_tasks:

    - set_fact:
        local_release_dir: "{{ local_release_dir }}"
      delegate_to: localhost
      run_once: true

    - debug:
        var: local_release_dir

  roles:
    - role_A

```

limited to the localhost gives, as expected

```yaml
shell> ansible-playbook pb.yml -l localhost

PLAY [test,localhost] **********************************************************************************************

TASK [set_fact] ****************************************************************************************************
ok: [localhost]

TASK [debug] *******************************************************************************************************
ok: [localhost] => 
    local_release_dir: localhost_dir

TASK [role_A : debug] **********************************************************************************************
ok: [localhost] => 
    local_release_dir: localhost_dir

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

```

The pre-task `set_fact` should override `local_release_dir` from the role defaults for all hosts because of the delegation to the localhost and the [run\_once](https://docs.ansible.com/projects/ansible/latest/reference_appendices/playbooks_keywords.html) `"apply any results and facts to all active hosts in the same batch".`

But, running the play for all hosts

```yaml
shell> ansible-playbook pb.yml

PLAY [test,localhost] **********************************************************************************************

TASK [set_fact] ****************************************************************************************************
ok: [foo -> localhost]

TASK [debug] *******************************************************************************************************
ok: [foo] => 
    local_release_dir: role_default_dir
ok: [bar] => 
    local_release_dir: role_default_dir
ok: [baz] => 
    local_release_dir: role_default_dir
ok: [localhost] => 
    local_release_dir: role_default_dir

TASK [role_A : debug] **********************************************************************************************
ok: [foo] => 
    local_release_dir: role_default_dir
ok: [bar] => 
    local_release_dir: role_default_dir
ok: [baz] => 
    local_release_dir: role_default_dir
ok: [localhost] => 
    local_release_dir: role_default_dir

PLAY RECAP *********************************************************************************************************
bar : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0   
baz : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0   
foo : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0   
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0  

```

does not override `local_release_dir` even for the localhost! This is a bug because the variables from the [inventory files](https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbooks_variables.html#understanding-variable-precedence) should override the role deafults.

---

<div class="post-metadata">

**Author:** ![VannTen](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/vannten/32/9911_2.png) [@VannTen](https://forum.ansible.com/u/VannTen)\
**Post date:** [December 6, 2025, 9:06am UTC](https://forum.ansible.com/t/explicitely-use-role-defaults/44958/3 "2025-12-06T09:06:04Z")

</div>

It’s not a bug because when you delegate you get the variable from the original host, not the delegated\_to.

But that does not answer the question, which is how (if that’s possible) access the role default of a variable (so, not the value from inventory). I’ve seen some reference to a `defaults` dict on the bug tracker, but nothing conclusive.

---

<div class="post-metadata">

**Author:** ![vbotka](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/vbotka/32/359_2.png) [@vbotka](https://forum.ansible.com/u/vbotka)\
**Post date:** [December 6, 2025, 8:19pm UTC](https://forum.ansible.com/t/explicitely-use-role-defaults/44958/4 "2025-12-06T20:19:56Z")

</div>

> [@VannTen](#):
>
> when you delegate you get the variable from the original host

You’re right. Wouldn’t it help to reference the localhost then?

```yaml-
- hosts: test

  pre_tasks:

    - set_fact:
        local_release_dir: "{{ hostvars['localhost']['local_release_dir'] |
                               d(local_release_dir) }}"
      delegate_to: localhost
      run_once: true

    - debug:
        var: local_release_dir

  roles:
    - role_A

```

If the variable `local_release_dir` is not defined in the `localhost` context, you will get the role’s default.

---

<div class="post-metadata">

**Author:** ![VannTen](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/vannten/32/9911_2.png) [@VannTen](https://forum.ansible.com/u/VannTen)\
**Post date:** [December 7, 2025, 11:21am UTC](https://forum.ansible.com/t/explicitely-use-role-defaults/44958/5 "2025-12-07T11:21:49Z")

</div>

No, because with delegate\_to, you’re not in the delegated host context for variables, you’re in the original host context, as explained in the OP.

---

<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:** [December 7, 2025, 5:58pm UTC](https://forum.ansible.com/t/explicitely-use-role-defaults/44958/6 "2025-12-07T17:58:25Z")

</div>

Regardless of which target host’s context you’re in, `hostvars['localhost']['local_release_dir']` is unambiguously from `localhost`’s context. The `d(local_release_dir)` _will_ depend on the specific target host, but since `local_release_dir` is always defined for `localhost` (by your inventory), the `|default` part of the expression should never come into play.

Also, `set_fact` with `run_once: true` sets the `local_release_dir` for all hosts. It is in fact ambiguous which target host’s context such a step will run in, but it doesn’t matter. While that target host will generally be the first target host which is still active in the play, but I wouldn’t rely on that. And the expression from @vbotka’s post doesn’t — ignoring the `|default()` part what, again, doesn’t come into play.

---

<div class="post-metadata">

**Author:** ![VannTen](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/vannten/32/9911_2.png) [@VannTen](https://forum.ansible.com/u/VannTen)\
**Post date:** [December 7, 2025, 7:31pm UTC](https://forum.ansible.com/t/explicitely-use-role-defaults/44958/7 "2025-12-07T19:31:58Z")

</div>

No, I precisely can’t assume that the variable is defined in the inventory for localhost, that’s what I’m trying to say. If I could, of course this wouldn’t be needed.

And in that case the hostvars[‘localhost’][‘local\_release\_dir’] is not defined, hence the default filter.

---

<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:** [December 8, 2025, 1:04pm UTC](https://forum.ansible.com/t/explicitely-use-role-defaults/44958/8 "2025-12-08T13:04:39Z")

</div>

If you want to maintain the original role default regardless of overrides, be explicit about it so it makes sense to future you.

In your `role/download/defaults/main.yml`, do this:

```yaml
---
# roles/download/defaults/main.yml
# Note: variables renamed so as to avoid the
# "Variables names from within roles should use download_ as a prefix."
# message from `ansible_lint`.

download_local_release_dir_role_default: &dlrdrd /value/from/roles/download/defaults/main.yml
download_local_release_dir: *dlrdrd

```

Then when `hostvars[‘localhost’][‘download_local_release_dir’]` is not defined, use `download_local_release_dir_role_default`, like so:

```auto
{{ hostvars['localhost']['download_local_release_dir']
   | default('download_local_release_dir_role_default') }}

```

The “`&dlrdrd`” defines a YAML node anchor, while “`*dlrdrd`” is a reference to that anchor. They only exist in the YAML parsing phase. I’m pretty sure Ansible itself will be oblivious to it, and that Ansible will see them as two variables which happen to have identical values. I only used the anchor and reference to be explicit about them being initialized with the same value. (And if you change the value, you don’t have to change it in two places.)

You could just as easily say

```yaml
download_local_release_dir_role_default: /value/from/roles/download/defaults/main.yml
download_local_release_dir: /value/from/roles/download/defaults/main.yml # noqa yaml[colons]

```

or this:

```yaml
download_local_release_dir_role_default: /value/from/roles/download/defaults/main.yml
download_local_release_dir: "{{ download_local_release_dir_role_default }}"

```

I find both of those alternatives aesthetically offensive, but they’ll both work. (Note that in the 2nd alternative, if someone changes the first variable elsewhere, they’ll also change the second – intentionally or not – because of lazy evaluation. That won’t be the case if you use the anchor/reference technique.)

---

<div class="post-metadata">

**Author:** ![VannTen](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/vannten/32/9911_2.png) [@VannTen](https://forum.ansible.com/u/VannTen)\
**Post date:** [December 8, 2025, 3:09pm UTC](https://forum.ansible.com/t/explicitely-use-role-defaults/44958/9 "2025-12-08T15:09:06Z")

</div>

This is a pretty interesting idea for working around the lack of native access to defaults, hadn’t though about that.

I think I’ll put the “true defaults” variable in /vars instead of /defaults, in that case, since it should not be modified.

Thanks for the inspiration !

---

<div class="post-metadata">

**Author:** ![system](https://us1.discourse-cdn.com/ansible/original/1X/1882d9a01da5d8996a862359b0913cc568a39b84.svg) [@system](https://forum.ansible.com/u/system)\
**Post date:** [January 7, 2026, 3:09pm UTC](https://forum.ansible.com/t/explicitely-use-role-defaults/44958/10 "2026-01-07T15:09:29Z")

</div>

Closed on January 7, 2026, 3:09pm UTC

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.
