Ansible - run_once, set_fact and include_tasks - The pitfalls
- Get link
- X
- Other Apps
Ansible does provide a wonderful option to execute a module or command in only one host called run_once but the results and facts are applied to all the hosts involved in the play.
See definition and working:
This blog has good information on how it can be used to in various scenarios.
However, I don't want to talk about how it can be used and how its working (well, the blog calls it magic 😄) is quite useful. Rather, I want to share an interesting anomaly of run_once, when used in conjunction with either include_roles or include_tasks.
My scenario is quite simple:
set_fact with run_once:
My playbook:
$ cat run_once_set_fact.yml
---
- hosts: all
gather_facts: False
tasks:
- name: Set fact fact1
set_fact:
fact1: "DUMMY"
run_once: True
- debug:
msg: "fact1: {{ fact1 }}"
Output:
PLAY [all] ****************************************************************************************************************************************************************************************************************************************************************************************************************************************
TASK [Set fact fact1] *****************************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [host1]
TASK [debug] **************************************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [host1] => {
"msg": "fact1: DUMMY"
}
ok: [host2] => {
"msg": "fact1: DUMMY"
}
ok: [host3] => {
"msg": "fact1: DUMMY"
}
PLAY RECAP ****************************************************************************************************************************************************************************************************************************************************************************************************************************************
host1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host2 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host3 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
As you can see, even though I have set the fact - fact1 - only on one host using run_once, it's being set for all the hosts in the inventory. That's the beauty (or magic) or run_once.
Now, the problem with using run_once in conjunction with include_tasks:
My playbook:
$ cat run_once_set_fact_include_tasks.yml
---
- hosts: all
gather_facts: False
tasks:
- name: Set fact fact1
set_fact:
fact1: "DUMMY1"
run_once: True
- debug:
msg: "fact1: {{ fact1 }}"
- name: Include task file - task1.yml - which sets the fact fact2 to DUMMY2
include_tasks:
file: task1.yml
run_once: True
- debug:
msg: "fact2: {{ fact2 }}"
$ cat task1.yml
---
## Contents of task1.yml
- name: Set fact fact2
set_fact:
fact2: "DUMMY2"
Output:
PLAY [all] ****************************************************************************************************************************************************************************************************************************************************************************************************************************************
TASK [Set fact fact1] *****************************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [host1]
TASK [debug] **************************************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [host1] => {
"msg": "fact1: DUMMY1"
}
ok: [host3] => {
"msg": "fact1: DUMMY1"
}
ok: [host2] => {
"msg": "fact1: DUMMY1"
}
TASK [Include task file - task1.yml - which sets the fact fact2 to DUMMY2] ************************************************************************************************************************************************************************************************************************************************************************
included: /oracle/devops/automation/ansible/generic_ansible_scripts/task1.yml for host1
TASK [Set fact fact2] *****************************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [host1]
TASK [debug] **************************************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [host1] => {
"msg": "fact2: DUMMY2"
}
fatal: [host2]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'fact2' is undefined\n\nThe error appears to be in '/oracle/devops/automation/ansible/generic_ansible_scripts/run_once_set_fact_include_tasks.yml': line 18, column 11, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - debug:\n ^ here\n"}
fatal: [host3]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'fact2' is undefined\n\nThe error appears to be in '/oracle/devops/automation/ansible/generic_ansible_scripts/run_once_set_fact_include_tasks.yml': line 18, column 11, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - debug:\n ^ here\n"}
PLAY RECAP ****************************************************************************************************************************************************************************************************************************************************************************************************************************************
host1 : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host2 : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
host3 : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
Whoa! What just happened? The run_once did execute as you can see in the output. Then, why didn't the set_fact inside the task file work?
This is the pitfall or anomaly that I'm talking about.
Though the expectation is that run_once is actually applied on all the tasks mentioned inside the task1.yml file, the fact of the matter is that it isn't so. Rather, run_once is applied only to the "include_tasks" module and not to the tasks inside task1.yml.
More information on this anomaly in this github issue.
The explanation for this issue and the solution is mentioned in this comment.
Applying the same, here's the edited playbook and the output:
$ cat run_once_set_fact_include_tasks.yml
---
- hosts: all
gather_facts: False
tasks:
- name: Set fact fact1
set_fact:
fact1: "DUMMY1"
run_once: True
- debug:
msg: "fact1: {{ fact1 }}"
- name: Include task file - task1.yml - which sets the fact fact2 to DUMMY2
include_tasks:
file: task1.yml
# run_once: True
args:
apply:
run_once: True
- debug:
msg: "fact2: {{ fact2 }}"
$ cat task1.yml
---
## Contents of task1.yml
- name: Set fact fact2
set_fact:
fact2: "DUMMY2"
PLAY [all] ****************************************************************************************************************************************************************************************************************************************************************************************************************************************
TASK [Set fact fact1] *****************************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [host1]
TASK [debug] **************************************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [host1] => {
"msg": "fact1: DUMMY1"
}
ok: [host2] => {
"msg": "fact1: DUMMY1"
}
ok: [host3] => {
"msg": "fact1: DUMMY1"
}
TASK [Include task file - task1.yml - which sets the fact fact2 to DUMMY2] ************************************************************************************************************************************************************************************************************************************************************************
included: /oracle/devops/automation/ansible/generic_ansible_scripts/task1.yml for host1, host2, host3
TASK [Set fact fact2] *****************************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [host1]
TASK [debug] **************************************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [host1] => {
"msg": "fact2: DUMMY2"
}
ok: [host2] => {
"msg": "fact2: DUMMY2"
}
ok: [host3] => {
"msg": "fact2: DUMMY2"
}
PLAY RECAP ****************************************************************************************************************************************************************************************************************************************************************************************************************************************
host1 : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host2 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host3 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Voila! Now, everything is working as it should. Notice the include tasks is now included for all the 3 hosts and not just for one.
- Get link
- X
- Other Apps
Comments