AWS using Ansible? Yes, it's possible!

  Although, I've used Ansible extensively for a lot of automation and orchestration tasks, using Ansible for AWS was indeed, a new territory for me.  This turned out to be a blessing, since along with using Ansible for AWS tasks, I also learnt how to use WSL (Windows Subsystem for Linux) on a Windows machine. Though WSL's been around for some time, I still hadn't come around to using it since I was mostly using my Macbook pro. Not anymore, though!  Anyway, I have listed below the steps to: Install WSL on Windows 11 23H2 patch Install AWS CLI on Ubuntu 22.04 (Exact version - 22.04.3 LTS) Install Ansible and the amazon.aws collection Use AWS CLI to get the list of  VPCs in the region - us-east-1 (or a region of your choice) Create a python file/script to get the list of VPCs in the region - us-east-1 (or a region of your choice) Create an Ansible playbook to get the list of VPCs in the region - us-east-1 (or a region of your choice.   You may download the comple...

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

Check if UTL_FILE and FND_FILE are working fine

Modify retention period of workflow queues

Clone database home (clone.pl) deprecated in Oracle 19c