Ansible - run_once, set_fact and include_tasks - The pitfalls

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.


Comments

Popular posts from this blog

java.lang.ExceptionInInitializerError while trying to Access login page

Solution to "End Program - WMS Idle"

WGET shell Script for downloading patches