Ansible for Dummies

Updated

Trying to learn something new again, might as well write it down for future reference.

$ python3 -m venv venv
$ echo "venv/" >> .gitignore
$ source venv/bin/activate
$ pip install ansible
$ mkdir -p group_vars roles
$ touch group_vars/common.yml production site.yml

# I don't need this yet BUT THE BOOK SAID SO
$ ansible-galaxy init roles/common
$ ansible-galaxy init roles/updating

# Add whatever hostnames you'd like to mess around with here
# Relevant: https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html
$ echo "localhost" >> production

$ ansible all -i production -m ping

Wow, now I’ve successfully abstracted away ping from my ping! Yay.

What I really wanted, tho:

$ ansible all -i production -m setup -a "filter=ansible_interfaces"
box67 | SUCCESS => {
    "ansible_facts": {
        "ansible_interfaces": [
            "lo0",
            "pflog0",
            "re0"
        ],
        "discovered_interpreter_python": "/usr/local/bin/python3.7"
    },
    "changed": false
}

OK, cool. Now you know of my currently defined network interfaces. I started out doing these runs on my firewall, but I quickly setup a VM to not mess anything up.

So I tried to use the syspatch module for ansible 2.9.9, and ran into an error immediately.

$ ansible-playbook -i production site.yml

PLAY [test] ************************************************************************************************************

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

TASK [roles/updating : Check for updates] ******************************************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: UnboundLocalError: local variable 'check_flag' referenced before assignment
fatal: [box67]: FAILED! => {"changed": false, "module_stderr": "Shared connection to 192.168.122.120 closed.\r\n", "module_stdout": "Traceback (most recent call last):\r\n  File \"/root/.ansible/tmp/ansible-tmp-1591220008.182737-58160-140628976587457/AnsiballZ_syspatch.py\", line 102, in <module>\r\n    _ansiballz_main()\r\n  File \"/root/.ansible/tmp/ansible-tmp-1591220008.182737-58160-140628976587457/AnsiballZ_syspatch.py\", line 94, in _ansiballz_main\r\n    invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)\r\n  File \"/root/.ansible/tmp/ansible-tmp-1591220008.182737-58160-140628976587457/AnsiballZ_syspatch.py\", line 40, in invoke_module\r\n    runpy.run_module(mod_name='ansible.modules.system.syspatch', init_globals=None, run_name='__main__', alter_sys=True)\r\n  File \"/usr/local/lib/python3.7/runpy.py\", line 205, in run_module\r\n    return _run_module_code(code, init_globals, run_name, mod_spec)\r\n  File \"/usr/local/lib/python3.7/runpy.py\", line 96, in _run_module_code\r\n    mod_name, mod_spec, pkg_name, script_name)\r\n  File \"/usr/local/lib/python3.7/runpy.py\", line 85, in _run_code\r\n    exec(code, run_globals)\r\n  File \"/tmp/ansible_syspatch_payload_wdtkmxc5/ansible_syspatch_payload.zip/ansible/modules/system/syspatch.py\", line 169, in <module>\r\n  File \"/tmp/ansible_syspatch_payload_wdtkmxc5/ansible_syspatch_payload.zip/ansible/modules/system/syspatch.py\", line 165, in main\r\n  File \"/tmp/ansible_syspatch_payload_wdtkmxc5/ansible_syspatch_payload.zip/ansible/modules/system/syspatch.py\", line 95, in run_module\r\n  File \"/tmp/ansible_syspatch_payload_wdtkmxc5/ansible_syspatch_payload.zip/ansible/modules/system/syspatch.py\", line 119, in syspatch_run\r\nUnboundLocalError: local variable 'check_flag' referenced before assignment\r\n", "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error", "rc": 1}

PLAY RECAP *************************************************************************************************************
box67                      : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

UnboundLocalError, wait what? Some googling and I found an issue on GitHub that was relevant. Tried the patch found there:

diff --git a/lib/ansible/modules/system/syspatch.py b/lib/ansible/modules/system/syspatch.py
index 3c75f2f2c0..2c808851cb 100644
--- a/lib/ansible/modules/system/syspatch.py
+++ b/lib/ansible/modules/system/syspatch.py
@@ -113,6 +113,8 @@ def syspatch_run(module):
     reboot_needed = False
     warnings = []

+    run_flag = []
+    check_flag = []
     # Setup command flags
     if module.params['revert']:
         check_flag = ['-l']
@@ -123,7 +125,7 @@ def syspatch_run(module):
             run_flag = ['-r']
     elif module.params['apply']:
         check_flag = ['-c']
-        run_flag = []
+

I have to admit that I just edited the damn file by hand, as I’ve never learned how to properly use patch. I then tested it:

$ ansible-playbook -i production site.yml

PLAY [test] ************************************************************************************************************

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

TASK [roles/updating : Check for updates] ******************************************************************************
changed: [box67]

PLAY RECAP *************************************************************************************************************
box67                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

WTH? I tried asking it not to apply any updates?

# roles/updating/tasks/main.yml
---
- name: Check for updates
  syspatch:
    apply: no

OK, this module probably isn’t the best module available. But my virtual machine is now (accidentally) updated.

I tried to do it myself, but since syspatch doesn’t seem to use return codes much this was another learning experience - but I found some inspiration on an old github gist.

# roles/updating/tasks/main.yml
---
- name: Check for updates manually
  shell: "syspatch -c"
  register: syspatch_updates
  changed_when: "syspatch_updates.stdout|length > 0"

- name: Apply updates conditionally
  shell: "syspatch"
  when: "syspatch_updates.stdout|length > 0 and really_apply_syspatch"

# roles/updating/vars/main.yml
---
really_apply_syspatch: false

We can pretend that figuring out the above yaml didn’t take a hour or so. I’m still not sure why I have to check the length of the output twice? Mentally marked as a future improvement.

ansible-playbook -i production site.yml

PLAY [test] ************************************************************************************************************

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

TASK [roles/updating : Check for updates manually] *********************************************************************
ok: [box67]

TASK [roles/updating : Apply updates conditionally] ********************************************************************
skipping: [box67]

PLAY RECAP *************************************************************************************************************
box67                      : ok=2    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

OK, lets override the default value before running the playbook:

$ ansible-playbook -i production site.yml --extra-vars "really_apply_syspatch=true"

PLAY [test] ************************************************************************************************************

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

TASK [roles/updating : Check for updates manually] *********************************************************************
changed: [box67]

TASK [roles/updating : Apply updates conditionally] ********************************************************************
changed: [box67]

PLAY RECAP *************************************************************************************************************
box67                      : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

OK. This is not terrible, I think I’m getting a basic understanding here, and I even ran it on my firewall to update it without having anything exploding.

Enough for today!