when will the variable substitution happen?

411 views
Skip to first unread message

Sisyphus

unread,
Mar 31, 2021, 11:21:24 AM3/31/21
to Ansible Project

I cannot find any where in the document about when the variable substitution happen. I want to know this because we can define variables in several places and we can refer to variables in different scopes. For example, a group var or host var or even a extra var can refer to a variable defined in roles. This is some kind of unexpected. Because there is a variable override priority described here: https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#understanding-variable-precedence

Normally we would thought the variable in higher priority get initialized first and cannot refer to low priority variables (extra vars override group vars, so normal people would expect we cannot refer to a group var in extra var) but this is not true. Normally we can refer to a variable anywhere in any scope. Even extra variable `-e testvar04='t
estvar03:{{testvar03}}'
` can be expand in playbook. Here the testvar03 is defined in group vars and refer to a variable only defined in role defaults. So it looks like the variable substitution only happens at the very end right before the variable is used in a task. However, when I try to obtain variable in another host this rule is broken again: `hostvars[ansible_host].testvar02` expands to `var_in_role: {{var_in_role}}`. The var_in_role is defined in role vars. So I wonder what is the actual rule about variable substitution? When the substitution actually happens? Do we have any chapter in the document define this behaviour?

Here is any example:

we have project structure like following:

```
├── inventory
│   └── hosts.yml
├── roles
│   └── test_var_substitution
│       ├── defaults
│       │   └── main.yml
│       ├── tasks
│       │   └── main.yml
│       └── vars
│           └── main.yml
└── test_var_substitution.yml
```

hosts.yml
```yaml
all:
  children:
    local:
      hosts:
        localhost:
          ansible_connection: local
          hostname: localhost
          host_var01: "define in host file"
          host_var02: "group_local_var01: {{group_local_var01}}"
      vars:
        group_local_var01: "define in local group"
  vars:
    testvar01: "set in group"
    testvar02: "var_in_role: {{var_in_role}}"
    testvar03: "var_in_role_default: {{var_in_role_default}}"
```

`roles/test_var_substitution/defaults/main.yml`:

```yaml
var_in_role_default: "define in role default only"
```
`roles/test_var_substitution/vars/main.yml`:
```yaml
var_in_role: "define in role only"
```

`roles/test_var_substitution/tasks/main.yml`:
```yaml

- debug:
    var: ansible_host
- name: show hostvars[ansible_host].testvar02
  debug:
    msg: "{{hostvars[ansible_host].testvar02}}"
- name: show hostvars[ansible_host].testvar03
  debug:
    msg: "{{hostvars[ansible_host].testvar03}}"
- name: show hostvars[hostname].host_var02
  debug:
    msg: "{{hostvars[hostname].host_var02}}"
- debug:
    var: testvar01
- debug:
    var: testvar02
- debug:
    var: testvar03
- debug:
    var: host_var02
```
`test_var_substitution.yml`:
```yaml
- hosts: all
  connection: local
  roles:
    - test_var_substitution
  tasks:
    - debug:
        var: testvar01
    - debug:
        var: testvar02
    - debug:
        var: testvar03
    - debug:
        var: testvar04
    - debug:
        var: hostvars[ansible_host].testvar02
```

```
$ ansible-playbook -i inventory test_var_substitution.yml -e testvar04='testvar03:{{testvar03}}'

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

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

TASK [test_var_substitution : debug] ************************************************************************
ok: [localhost] => {
    "ansible_host": "localhost"
}

TASK [test_var_substitution : show hostvars[ansible_host].testvar02] ****************************************
ok: [localhost] => {
    "msg": "var_in_role: {{var_in_role}}"
}

TASK [test_var_substitution : show hostvars[ansible_host].testvar03] ****************************************
ok: [localhost] => {
    "msg": "var_in_role_default: {{var_in_role_default}}"
}

TASK [test_var_substitution : show hostvars[hostname].host_var02] *******************************************
ok: [localhost] => {
    "msg": "group_local_var01: define in local group"
}

TASK [test_var_substitution : debug] ************************************************************************
ok: [localhost] => {
    "testvar01": "set in group"
}

TASK [test_var_substitution : debug] ************************************************************************
ok: [localhost] => {
    "testvar02": "var_in_role: define in role only"
}

TASK [test_var_substitution : debug] ************************************************************************
ok: [localhost] => {
    "testvar03": "var_in_role_default: define in role default only"
}

TASK [test_var_substitution : debug] ************************************************************************
ok: [localhost] => {
    "host_var02": "group_local_var01: define in local group"
}

TASK [debug] ************************************************************************************************
ok: [localhost] => {
    "testvar01": "set in group"
}

TASK [debug] ************************************************************************************************
ok: [localhost] => {
    "testvar02": "var_in_role: define in role only"
}

TASK [debug] ************************************************************************************************
ok: [localhost] => {
    "testvar03": "var_in_role_default: define in role default only"
}

TASK [debug] ************************************************************************************************
ok: [localhost] => {
    "testvar04": "testvar03:var_in_role_default: define in role default only"
}

TASK [debug] ************************************************************************************************
ok: [localhost] => {
    "hostvars[ansible_host].testvar02": "var_in_role: {{var_in_role}}"
}

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

Vladimir Botka

unread,
Apr 1, 2021, 1:24:43 AM4/1/21
to Sisyphus, ansible...@googlegroups.com
> However, when I try to obtain variable in another host this rule is broken again: `
> hostvars[ansible_host].testvar02` expands to `var_in_role: {{var_in_role}}`.
> The var_in_role is defined in role vars.
>
> ...
>
> vars:
> testvar01: "set in group"
> testvar02: "var_in_role: {{var_in_role}}"
> testvar03: "var_in_role_default: {{var_in_role_default}}"
>
> ...
>
> - hosts: all
> connection: local
> roles:
> - test_var_substitution
> tasks:
> - debug:
> var: testvar02
> - debug:
> var: hostvars[ansible_host].testvar02
>
> ...
>
> TASK [debug]
> ************************************************************************************************
> ok: [localhost] => {
> "testvar02": "var_in_role: define in role only"
> }
>
> ...
>
> TASK [debug]
> ************************************************************************************************
> ok: [localhost] => {
> "hostvars[ansible_host].testvar02": "var_in_role: {{var_in_role}}"
> }


Use *set_fact* when you want to keep the "current" aka "instantiated"
(I'm not sure the wording is correct) value of the variable in
hostvars, e.g.

- set_fact:
testvar02: "{{ testvar02 }}"
- debug:
var: hostvars[ansible_host].testvar02

would give

hostvars[ansible_host].testvar02: 'var_in_role: define in role only'

--
Vladimir Botka

Brian Coca

unread,
Apr 1, 2021, 11:49:54 AM4/1/21
to Ansible Project, Sisyphus
Ansible has 'lazy' variable evaluation so it is 'on consumption'.
set_fact creates 'static values' since it is an action/task, which is
the 'lowest' possible consumption level.


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

Reply all
Reply to author
Forward
0 new messages