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
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
:
var_in_role_default: "define in role default only"
roles/test_var_substitution/vars/main.yml
:
var_in_role: "define in role only"
roles/test_var_substitution/tasks/main.yml
:
- 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
:
- 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